test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java
branchdatagramsocketimpl-branch
changeset 58678 9cf78a70fa4f
parent 51058 44c355346475
child 58679 9c3209ff7550
equal deleted inserted replaced
58677:13588c901957 58678:9cf78a70fa4f
     1 /*
     1 /*
     2  * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
     2  * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     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
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.
     7  * published by the Free Software Foundation.
    21  * questions.
    21  * questions.
    22  */
    22  */
    23 
    23 
    24 /*
    24 /*
    25  * @test
    25  * @test
    26  * @summary This test is used to verify the compatibility on jarsigner cross
    26  * @bug 8217375
       
    27  * @summary This test is used to verify the compatibility of jarsigner across
    27  *     different JDK releases. It also can be used to check jar signing (w/
    28  *     different JDK releases. It also can be used to check jar signing (w/
    28  *     and w/o TSA) and verifying on some specific key algorithms and digest
    29  *     and w/o TSA) and to verify some specific signing and digest algorithms.
    29  *     algorithms.
    30  *     Note that this is a manual test. For more details about the test and
    30  *     Note that, this is a manual test. For more details about the test and
    31  *     its usages, please look through the README.
    31  *     its usages, please look through README.
       
    32  *
    32  *
    33  * @modules java.base/sun.security.pkcs
    33  * @library /test/lib ../warnings
    34  *          java.base/sun.security.timestamp
       
    35  *          java.base/sun.security.tools.keytool
       
    36  *          java.base/sun.security.util
       
    37  *          java.base/sun.security.x509
       
    38  * @library /test/lib /lib/testlibrary ../warnings
       
    39  * @compile -source 1.7 -target 1.7 JdkUtils.java
    34  * @compile -source 1.7 -target 1.7 JdkUtils.java
    40  * @run main/manual/othervm Compatibility
    35  * @run main/manual/othervm Compatibility
    41  */
    36  */
    42 
    37 
       
    38 import static java.nio.charset.StandardCharsets.UTF_8;
       
    39 
    43 import java.io.BufferedReader;
    40 import java.io.BufferedReader;
    44 import java.io.File;
    41 import java.io.File;
       
    42 import java.io.FileOutputStream;
    45 import java.io.FileReader;
    43 import java.io.FileReader;
    46 import java.io.FileWriter;
    44 import java.io.FileWriter;
    47 import java.io.IOException;
    45 import java.io.IOException;
       
    46 import java.io.OutputStream;
    48 import java.io.PrintStream;
    47 import java.io.PrintStream;
       
    48 import java.nio.file.Files;
       
    49 import java.nio.file.Path;
    49 import java.text.DateFormat;
    50 import java.text.DateFormat;
    50 import java.text.SimpleDateFormat;
    51 import java.text.SimpleDateFormat;
    51 import java.util.ArrayList;
    52 import java.util.ArrayList;
       
    53 import java.util.Arrays;
    52 import java.util.Calendar;
    54 import java.util.Calendar;
    53 import java.util.Date;
    55 import java.util.Date;
    54 import java.util.HashMap;
    56 import java.util.HashMap;
    55 import java.util.HashSet;
    57 import java.util.HashSet;
    56 import java.util.List;
    58 import java.util.List;
       
    59 import java.util.Locale;
    57 import java.util.Map;
    60 import java.util.Map;
    58 import java.util.Set;
    61 import java.util.Set;
    59 import java.util.concurrent.TimeUnit;
    62 import java.util.concurrent.TimeUnit;
    60 import java.util.regex.Matcher;
    63 import java.util.function.Consumer;
    61 import java.util.regex.Pattern;
    64 import java.util.function.Function;
       
    65 import java.util.jar.Attributes.Name;
       
    66 import java.util.jar.Manifest;
       
    67 import java.util.stream.Collectors;
       
    68 import java.util.stream.IntStream;
    62 
    69 
    63 import jdk.test.lib.process.OutputAnalyzer;
    70 import jdk.test.lib.process.OutputAnalyzer;
    64 import jdk.test.lib.process.ProcessTools;
    71 import jdk.test.lib.process.ProcessTools;
    65 import jdk.test.lib.util.JarUtils;
    72 import jdk.test.lib.util.JarUtils;
    66 
    73 
    67 public class Compatibility {
    74 public class Compatibility {
    68 
    75 
    69     private static final String TEST_JAR_NAME = "test.jar";
       
    70 
       
    71     private static final String TEST_SRC = System.getProperty("test.src");
    76     private static final String TEST_SRC = System.getProperty("test.src");
    72     private static final String TEST_CLASSES = System.getProperty("test.classes");
    77     private static final String TEST_CLASSES = System.getProperty("test.classes");
    73     private static final String TEST_JDK = System.getProperty("test.jdk");
    78     private static final String TEST_JDK = System.getProperty("test.jdk");
    74     private static final String TEST_JARSIGNER = jarsignerPath(TEST_JDK);
    79     private static JdkInfo TEST_JDK_INFO;
    75 
    80 
    76     private static final String PROXY_HOST = System.getProperty("proxyHost");
    81     private static final String PROXY_HOST = System.getProperty("proxyHost");
    77     private static final String PROXY_PORT = System.getProperty("proxyPort", "80");
    82     private static final String PROXY_PORT = System.getProperty("proxyPort", "80");
    78 
    83 
    79     // An alternative security properties file.
    84     // An alternative security properties file.
    82     // jdk.jar.disabledAlgorithms=MD2, MD5
    87     // jdk.jar.disabledAlgorithms=MD2, MD5
    83     private static final String JAVA_SECURITY = System.getProperty(
    88     private static final String JAVA_SECURITY = System.getProperty(
    84             "javaSecurityFile", TEST_SRC + "/java.security");
    89             "javaSecurityFile", TEST_SRC + "/java.security");
    85 
    90 
    86     private static final String PASSWORD = "testpass";
    91     private static final String PASSWORD = "testpass";
    87     private static final String KEYSTORE = "testKeystore";
    92     private static final String KEYSTORE = "testKeystore.jks";
    88 
    93 
    89     private static final String RSA = "RSA";
    94     private static final String RSA = "RSA";
    90     private static final String DSA = "DSA";
    95     private static final String DSA = "DSA";
    91     private static final String EC = "EC";
    96     private static final String EC = "EC";
    92     private static final String[] KEY_ALGORITHMS = new String[] {
    97     private static String[] KEY_ALGORITHMS;
       
    98     private static final String[] DEFAULT_KEY_ALGORITHMS = new String[] {
    93             RSA,
    99             RSA,
    94             DSA,
   100             DSA,
    95             EC};
   101             EC};
    96 
   102 
    97     private static final String SHA1 = "SHA-1";
   103     private static final String SHA1 = "SHA-1";
    98     private static final String SHA256 = "SHA-256";
   104     private static final String SHA256 = "SHA-256";
       
   105     private static final String SHA384 = "SHA-384";
    99     private static final String SHA512 = "SHA-512";
   106     private static final String SHA512 = "SHA-512";
   100     private static final String DEFAULT = "DEFAULT";
   107     private static final String DEFAULT = "DEFAULT";
   101     private static final String[] DIGEST_ALGORITHMS = new String[] {
   108     private static String[] DIGEST_ALGORITHMS;
       
   109     private static final String[] DEFAULT_DIGEST_ALGORITHMS = new String[] {
   102             SHA1,
   110             SHA1,
   103             SHA256,
   111             SHA256,
   104             SHA512,
   112             SHA384,
       
   113             SHA512, // note: digests break onto continuation line in manifest
   105             DEFAULT};
   114             DEFAULT};
   106 
   115 
   107     private static final boolean[] EXPIRED = new boolean[] {
   116     private static final boolean[] EXPIRED =
   108             false,
   117             Boolean.valueOf(System.getProperty("expired", "true")) ?
   109             true};
   118                     new boolean[] { false, true } : new boolean[] { false };
       
   119 
       
   120     private static final boolean TEST_COMPREHENSIVE_JAR_CONTENTS =
       
   121             Boolean.valueOf(System.getProperty(
       
   122                     "testComprehensiveJarContents", "false"));
       
   123 
       
   124     private static final boolean TEST_JAR_UPDATE =
       
   125             Boolean.valueOf(System.getProperty("testJarUpdate", "false"));
       
   126 
       
   127     private static final boolean STRICT =
       
   128             Boolean.valueOf(System.getProperty("strict", "false"));
   110 
   129 
   111     private static final Calendar CALENDAR = Calendar.getInstance();
   130     private static final Calendar CALENDAR = Calendar.getInstance();
   112     private static final DateFormat DATE_FORMAT
   131     private static final DateFormat DATE_FORMAT
   113             = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
   132             = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
   114 
   133 
   117     private static final int CERT_VALIDITY
   136     private static final int CERT_VALIDITY
   118             = Integer.valueOf(System.getProperty("certValidity", "1440"));
   137             = Integer.valueOf(System.getProperty("certValidity", "1440"));
   119     static {
   138     static {
   120         if (CERT_VALIDITY < 1 || CERT_VALIDITY > 1440) {
   139         if (CERT_VALIDITY < 1 || CERT_VALIDITY > 1440) {
   121             throw new RuntimeException(
   140             throw new RuntimeException(
   122                     "certValidity if out of range [1, 1440]: " + CERT_VALIDITY);
   141                     "certValidity out of range [1, 1440]: " + CERT_VALIDITY);
   123         }
   142         }
   124     }
   143     }
   125 
   144 
   126     // If true, an additional verifying will be triggered after all of
   145     // If true, an additional verifying will be triggered after all of
   127     // valid certificates expire. The default value is false.
   146     // valid certificates expire. The default value is false.
   130 
   149 
   131     private static long lastCertStartTime;
   150     private static long lastCertStartTime;
   132 
   151 
   133     private static DetailsOutputStream detailsOutput;
   152     private static DetailsOutputStream detailsOutput;
   134 
   153 
   135     public static void main(String[] args) throws Throwable {
   154     private static int sigfileCounter;
       
   155 
       
   156     private static String nextSigfileName(String alias, String u, String s) {
       
   157         String sigfileName = "" + (++sigfileCounter);
       
   158         System.out.println("using sigfile " + sigfileName + " for alias "
       
   159                     + alias + " signing " + u + ".jar to " + s + ".jar");
       
   160         return sigfileName;
       
   161     }
       
   162 
       
   163     public static void main(String... args) throws Throwable {
   136         // Backups stdout and stderr.
   164         // Backups stdout and stderr.
   137         PrintStream origStdOut = System.out;
   165         PrintStream origStdOut = System.out;
   138         PrintStream origStdErr = System.err;
   166         PrintStream origStdErr = System.err;
   139 
   167 
   140         detailsOutput = new DetailsOutputStream();
   168         detailsOutput = new DetailsOutputStream(outfile());
   141 
   169 
   142         // Redirects the system output to a custom one.
   170         // Redirects the system output to a custom one.
   143         PrintStream printStream = new PrintStream(detailsOutput);
   171         PrintStream printStream = new PrintStream(detailsOutput);
   144         System.setOut(printStream);
   172         System.setOut(printStream);
   145         System.setErr(printStream);
   173         System.setErr(printStream);
   146 
   174 
       
   175         TEST_JDK_INFO = new JdkInfo(TEST_JDK);
       
   176 
   147         List<TsaInfo> tsaList = tsaInfoList();
   177         List<TsaInfo> tsaList = tsaInfoList();
   148         if (tsaList.size() == 0) {
       
   149             throw new RuntimeException("TSA service is mandatory.");
       
   150         }
       
   151 
       
   152         List<JdkInfo> jdkInfoList = jdkInfoList();
   178         List<JdkInfo> jdkInfoList = jdkInfoList();
   153         List<CertInfo> certList = createCertificates(jdkInfoList);
   179         List<CertInfo> certList = createCertificates(jdkInfoList);
   154         createJar();
   180         List<SignItem> signItems =
   155         List<SignItem> signItems = test(jdkInfoList, tsaList, certList);
   181                 test(jdkInfoList, tsaList, certList, createJars());
   156 
   182 
   157         boolean failed = generateReport(tsaList, signItems);
   183         boolean failed = generateReport(jdkInfoList, tsaList, signItems);
   158 
   184 
   159         // Restores the original stdout and stderr.
   185         // Restores the original stdout and stderr.
   160         System.setOut(origStdOut);
   186         System.setOut(origStdOut);
   161         System.setErr(origStdErr);
   187         System.setErr(origStdErr);
   162 
   188 
   165                     + "Please check the failed row(s) in report.html "
   191                     + "Please check the failed row(s) in report.html "
   166                     + "or failedReport.html.");
   192                     + "or failedReport.html.");
   167         }
   193         }
   168     }
   194     }
   169 
   195 
   170     // Creates a jar file that contains an empty file.
   196     private static SignItem createJarFile(String jar, Manifest m,
   171     private static void createJar() throws IOException {
   197             String... files) throws IOException {
   172         String testFile = "test";
   198         JarUtils.createJarFile(Path.of(jar), m, Path.of("."),
   173         new File(testFile).createNewFile();
   199                 Arrays.stream(files).map(Path::of).toArray(Path[]::new));
   174         JarUtils.createJar(TEST_JAR_NAME, testFile);
   200         return SignItem.build()
       
   201                 .signedJar(jar.replaceAll("[.]jar$", ""))
       
   202             .addContentFiles(Arrays.stream(files).collect(Collectors.toList()));
       
   203     }
       
   204 
       
   205     private static String createDummyFile(String name) throws IOException {
       
   206         if (name.contains("/")) new File(name).getParentFile().mkdir();
       
   207         try (OutputStream fos = new FileOutputStream(name)) {
       
   208             fos.write(name.getBytes(UTF_8));
       
   209         }
       
   210         return name;
       
   211     }
       
   212 
       
   213     // Creates one or more jar files to test
       
   214     private static List<SignItem> createJars() throws IOException {
       
   215         List<SignItem> jarList = new ArrayList<>();
       
   216 
       
   217         Manifest m = new Manifest();
       
   218         m.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
       
   219 
       
   220         // creates a jar file that contains a dummy file
       
   221         jarList.add(createJarFile("test.jar", m, createDummyFile("dummy")));
       
   222 
       
   223         if (TEST_COMPREHENSIVE_JAR_CONTENTS) {
       
   224 
       
   225             // empty jar file so that jarsigner will add a default manifest
       
   226             jarList.add(createJarFile("empty.jar", m));
       
   227 
       
   228             // jar file that contains only an empty manifest with empty main
       
   229             // attributes (due to missing "Manifest-Version" header)
       
   230             JarUtils.createJar("nomainatts.jar");
       
   231             jarList.add(SignItem.build().signedJar("nomainatts"));
       
   232 
       
   233             // creates a jar file that contains several files.
       
   234             jarList.add(createJarFile("files.jar", m,
       
   235                     IntStream.range(1, 9).boxed().map(i -> {
       
   236                         try {
       
   237                             return createDummyFile("dummy" + i);
       
   238                         } catch (IOException e) {
       
   239                             throw new RuntimeException(e);
       
   240                         }
       
   241                     }).toArray(String[]::new)
       
   242             ));
       
   243 
       
   244             // forces a line break by exceeding the line width limit of 72 bytes
       
   245             // in the filename and hence manifest entry name
       
   246             jarList.add(createJarFile("longfilename.jar", m,
       
   247                     createDummyFile("test".repeat(20))));
       
   248 
       
   249             // another interesting case is with different digest algorithms
       
   250             // resulting in digests broken across line breaks onto continuation
       
   251             // lines. these however are set with the 'digestAlgs' option or
       
   252             // include all digest algorithms by default, see SignTwice.java.
       
   253         }
       
   254 
       
   255         return jarList;
       
   256     }
       
   257 
       
   258     // updates a signed jar file by adding another file
       
   259     private static List<SignItem> updateJar(SignItem prev) throws IOException {
       
   260         List<SignItem> jarList = new ArrayList<>();
       
   261 
       
   262         // sign unmodified jar again
       
   263         Files.copy(Path.of(prev.signedJar + ".jar"),
       
   264                 Path.of(prev.signedJar + "-signagainunmodified.jar"));
       
   265         jarList.add(SignItem.build(prev)
       
   266                 .signedJar(prev.signedJar + "-signagainunmodified"));
       
   267 
       
   268         String oldJar = prev.signedJar;
       
   269         String newJar = oldJar + "-addfile";
       
   270         String triggerUpdateFile = "addfile";
       
   271         JarUtils.updateJar(oldJar + ".jar", newJar + ".jar", triggerUpdateFile);
       
   272         jarList.add(SignItem.build(prev).signedJar(newJar)
       
   273                 .addContentFiles(Arrays.asList(triggerUpdateFile)));
       
   274 
       
   275         return jarList;
   175     }
   276     }
   176 
   277 
   177     // Creates a key store that includes a set of valid/expired certificates
   278     // Creates a key store that includes a set of valid/expired certificates
   178     // with various algorithms.
   279     // with various algorithms.
   179     private static List<CertInfo> createCertificates(List<JdkInfo> jdkInfoList)
   280     private static List<CertInfo> createCertificates(List<JdkInfo> jdkInfoList)
   180             throws Throwable {
   281             throws Throwable {
   181         List<CertInfo> certList = new ArrayList<CertInfo>();
   282         List<CertInfo> certList = new ArrayList<>();
   182         Set<String> expiredCertFilter = new HashSet<String>();
   283         Set<String> expiredCertFilter = new HashSet<>();
   183 
   284 
   184         for(JdkInfo jdkInfo : jdkInfoList) {
   285         for (JdkInfo jdkInfo : jdkInfoList) {
   185             for(String keyAlgorithm : KEY_ALGORITHMS) {
   286             for (String keyAlgorithm : keyAlgs()) {
   186                 for(String digestAlgorithm : DIGEST_ALGORITHMS) {
   287                 if (!jdkInfo.supportsKeyAlg(keyAlgorithm)) continue;
   187                     for(int keySize : keySizes(keyAlgorithm)) {
   288                 for (int keySize : keySizes(keyAlgorithm)) {
       
   289                     for (String digestAlgorithm : digestAlgs()) {
   188                         for(boolean expired : EXPIRED) {
   290                         for(boolean expired : EXPIRED) {
   189                             // It creates only one expired certificate for one
   291                             // It creates only one expired certificate for one
   190                             // key algorithm.
   292                             // key algorithm.
   191                             if (expired
   293                             if (expired
   192                                     && !expiredCertFilter.add(keyAlgorithm)) {
   294                                     && !expiredCertFilter.add(keyAlgorithm)) {
   193                                 continue;
   295                                 continue;
   194                             }
   296                             }
   195 
   297 
   196                             CertInfo certInfo = new CertInfo(
   298                             CertInfo certInfo = new CertInfo(
   197                                     jdkInfo.version,
   299                                     jdkInfo,
   198                                     keyAlgorithm,
   300                                     keyAlgorithm,
   199                                     digestAlgorithm,
   301                                     digestAlgorithm,
   200                                     keySize,
   302                                     keySize,
   201                                     expired);
   303                                     expired);
   202                             if (!certList.contains(certInfo)) {
   304                             // If the signature algorithm is not supported by the
   203                                 String alias = createCertificate(
   305                             // JDK, it cannot try to sign jar with this algorithm.
   204                                         jdkInfo.jdkPath, certInfo);
   306                             String sigalg = certInfo.sigalg();
   205                                 if (alias != null) {
   307                             if (sigalg != null &&
   206                                     certList.add(certInfo);
   308                                     !jdkInfo.isSupportedSigalg(sigalg)) {
   207                                 }
   309                                 continue;
   208                             }
   310                             }
       
   311                             createCertificate(jdkInfo, certInfo);
       
   312                             certList.add(certInfo);
   209                         }
   313                         }
   210                     }
   314                     }
   211                 }
   315                 }
   212             }
   316             }
   213         }
   317         }
   214 
   318 
       
   319         System.out.println("the keystore contents:");
       
   320         for (JdkInfo jdkInfo : jdkInfoList) {
       
   321             execTool(jdkInfo.jdkPath + "/bin/keytool", new String[] {
       
   322                     "-v",
       
   323                     "-storetype",
       
   324                     "jks",
       
   325                     "-storepass",
       
   326                     PASSWORD,
       
   327                     "-keystore",
       
   328                     KEYSTORE,
       
   329                     "-list"
       
   330             });
       
   331         }
       
   332 
   215         return certList;
   333         return certList;
   216     }
   334     }
   217 
   335 
   218     // Creates/Updates a key store that adds a certificate with specific algorithm.
   336     // Creates/Updates a key store that adds a certificate with specific algorithm.
   219     private static String createCertificate(String jdkPath, CertInfo certInfo)
   337     private static void createCertificate(JdkInfo jdkInfo, CertInfo certInfo)
   220             throws Throwable {
   338             throws Throwable {
   221         String alias = certInfo.alias();
   339         List<String> arguments = new ArrayList<>();
   222 
       
   223         List<String> arguments = new ArrayList<String>();
       
   224         arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
   340         arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
   225         arguments.add("-v");
   341         arguments.add("-v");
       
   342         arguments.add("-debug");
   226         arguments.add("-storetype");
   343         arguments.add("-storetype");
   227         arguments.add("jks");
   344         arguments.add("jks");
   228         arguments.add("-genkey");
   345         arguments.add("-keystore");
       
   346         arguments.add(KEYSTORE);
       
   347         arguments.add("-storepass");
       
   348         arguments.add(PASSWORD);
       
   349         arguments.add(jdkInfo.majorVersion < 6 ? "-genkey" : "-genkeypair");
   229         arguments.add("-keyalg");
   350         arguments.add("-keyalg");
   230         arguments.add(certInfo.keyAlgorithm);
   351         arguments.add(certInfo.keyAlgorithm);
   231         String sigalg = sigalg(certInfo.digestAlgorithm, certInfo.keyAlgorithm);
   352         String sigalg = certInfo.sigalg();
   232         if (sigalg != null) {
   353         if (sigalg != null) {
   233             arguments.add("-sigalg");
   354             arguments.add("-sigalg");
   234             arguments.add(sigalg);
   355             arguments.add(sigalg);
   235         }
   356         }
   236         if (certInfo.keySize != 0) {
   357         if (certInfo.keySize != 0) {
   237             arguments.add("-keysize");
   358             arguments.add("-keysize");
   238             arguments.add(certInfo.keySize + "");
   359             arguments.add(certInfo.keySize + "");
   239         }
   360         }
   240         arguments.add("-dname");
   361         arguments.add("-dname");
   241         arguments.add("CN=Test");
   362         arguments.add("CN=" + certInfo);
   242         arguments.add("-alias");
   363         arguments.add("-alias");
   243         arguments.add(alias);
   364         arguments.add(certInfo.alias());
   244         arguments.add("-keypass");
   365         arguments.add("-keypass");
   245         arguments.add(PASSWORD);
       
   246         arguments.add("-storepass");
       
   247         arguments.add(PASSWORD);
   366         arguments.add(PASSWORD);
   248 
   367 
   249         arguments.add("-startdate");
   368         arguments.add("-startdate");
   250         arguments.add(startDate(certInfo.expired));
   369         arguments.add(startDate(certInfo.expired));
   251         arguments.add("-validity");
   370         arguments.add("-validity");
       
   371 //        arguments.add(DELAY_VERIFY ? "1" : "222"); // > six months no warn
   252         arguments.add("1");
   372         arguments.add("1");
   253         arguments.add("-keystore");
       
   254         arguments.add(KEYSTORE);
       
   255 
   373 
   256         OutputAnalyzer outputAnalyzer = execTool(
   374         OutputAnalyzer outputAnalyzer = execTool(
   257                 jdkPath + "/bin/keytool",
   375                 jdkInfo.jdkPath + "/bin/keytool",
   258                 arguments.toArray(new String[arguments.size()]));
   376                 arguments.toArray(new String[arguments.size()]));
   259         if (outputAnalyzer.getExitValue() == 0
   377         if (outputAnalyzer.getExitValue() != 0
   260                 && !outputAnalyzer.getOutput().matches("[Ee]xception")) {
   378                 || outputAnalyzer.getOutput().matches("[Ee]xception")
   261             return alias;
   379                 || outputAnalyzer.getOutput().matches(Test.ERROR + " ?")) {
   262         } else {
   380             System.out.println(outputAnalyzer.getOutput());
   263             return null;
   381             throw new Exception("error generating a key pair: " + arguments);
   264         }
   382         }
   265     }
       
   266 
       
   267     private static String sigalg(String digestAlgorithm, String keyAlgorithm) {
       
   268         if (digestAlgorithm == DEFAULT) {
       
   269             return null;
       
   270         }
       
   271 
       
   272         String keyName = keyAlgorithm == EC ? "ECDSA" : keyAlgorithm;
       
   273         return digestAlgorithm.replace("-", "") + "with" + keyName;
       
   274     }
   383     }
   275 
   384 
   276     // The validity period of a certificate always be 1 day. For creating an
   385     // The validity period of a certificate always be 1 day. For creating an
   277     // expired certificate, the start date is the time before 1 day, then the
   386     // expired certificate, the start date is the time before 1 day, then the
   278     // certificate expires immediately. And for creating a valid certificate,
   387     // certificate expires immediately. And for creating a valid certificate,
   279     // the start date is the time before (1 day - CERT_VALIDITY minutes), then
   388     // the start date is the time before (1 day - CERT_VALIDITY minutes), then
   280     // the certificate will expires in CERT_VALIDITY minutes.
   389     // the certificate will expires in CERT_VALIDITY minutes.
   281     private static String startDate(boolean expiredCert) {
   390     private static String startDate(boolean expiredCert) {
   282         CALENDAR.setTime(new Date());
   391         CALENDAR.setTime(new Date());
   283         CALENDAR.add(Calendar.DAY_OF_MONTH, -1);
   392         if (DELAY_VERIFY || expiredCert) {
       
   393             // corresponds to '-validity 1'
       
   394             CALENDAR.add(Calendar.DAY_OF_MONTH, -1);
       
   395         }
       
   396         if (DELAY_VERIFY && !expiredCert) {
       
   397             CALENDAR.add(Calendar.MINUTE, CERT_VALIDITY);
       
   398         }
       
   399         Date startDate = CALENDAR.getTime();
   284         if (!expiredCert) {
   400         if (!expiredCert) {
   285             CALENDAR.add(Calendar.MINUTE, CERT_VALIDITY);
   401             lastCertStartTime = startDate.getTime();
   286         }
   402         }
   287         Date startDate = CALENDAR.getTime();
       
   288         lastCertStartTime = startDate.getTime();
       
   289         return DATE_FORMAT.format(startDate);
   403         return DATE_FORMAT.format(startDate);
   290     }
   404     }
   291 
   405 
   292     // Retrieves JDK info from the file which is specified by property jdkListFile,
   406     private static String outfile() {
   293     // or from property jdkList if jdkListFile is not available.
   407         return System.getProperty("o");
       
   408     }
       
   409 
       
   410     // Retrieves JDK info from the file which is specified by property
       
   411     // jdkListFile, or from property jdkList if jdkListFile is not available.
   294     private static List<JdkInfo> jdkInfoList() throws Throwable {
   412     private static List<JdkInfo> jdkInfoList() throws Throwable {
   295         String[] jdkList = list("jdkList");
   413         String[] jdkList = list("jdkList");
   296         if (jdkList.length == 0) {
   414         if (jdkList.length == 0) {
   297             jdkList = new String[] { TEST_JDK };
   415             jdkList = new String[] { "TEST_JDK" };
   298         }
   416         }
   299 
   417 
   300         List<JdkInfo> jdkInfoList = new ArrayList<JdkInfo>();
   418         List<JdkInfo> jdkInfoList = new ArrayList<>();
       
   419         int index = 0;
   301         for (String jdkPath : jdkList) {
   420         for (String jdkPath : jdkList) {
   302             JdkInfo jdkInfo = new JdkInfo(jdkPath);
   421             JdkInfo jdkInfo = "TEST_JDK".equalsIgnoreCase(jdkPath) ?
       
   422                     TEST_JDK_INFO : new JdkInfo(jdkPath);
   303             // The JDK version must be unique.
   423             // The JDK version must be unique.
   304             if (!jdkInfoList.contains(jdkInfo)) {
   424             if (!jdkInfoList.contains(jdkInfo)) {
       
   425                 jdkInfo.index = index++;
       
   426                 jdkInfo.version = String.format(
       
   427                         "%s(%d)", jdkInfo.version, jdkInfo.index);
   305                 jdkInfoList.add(jdkInfo);
   428                 jdkInfoList.add(jdkInfo);
   306             } else {
   429             } else {
   307                 System.out.println("The JDK version is duplicate: " + jdkPath);
   430                 System.out.println("The JDK version is duplicate: " + jdkPath);
   308             }
   431             }
   309         }
   432         }
   310         return jdkInfoList;
   433         return jdkInfoList;
       
   434     }
       
   435 
       
   436     private static List<String> keyAlgs() throws IOException {
       
   437         if (KEY_ALGORITHMS == null) KEY_ALGORITHMS = list("keyAlgs");
       
   438         if (KEY_ALGORITHMS.length == 0)
       
   439             return Arrays.asList(DEFAULT_KEY_ALGORITHMS);
       
   440         return Arrays.stream(KEY_ALGORITHMS).map(a -> a.split(";")[0])
       
   441                 .collect(Collectors.toList());
       
   442     }
       
   443 
       
   444     // Return key sizes according to the specified key algorithm.
       
   445     private static int[] keySizes(String keyAlgorithm) throws IOException {
       
   446         if (KEY_ALGORITHMS == null) KEY_ALGORITHMS = list("keyAlgs");
       
   447         for (String keyAlg : KEY_ALGORITHMS) {
       
   448             String[] split = (keyAlg + " ").split(";");
       
   449             if (keyAlgorithm.equals(split[0].trim()) && split.length > 1) {
       
   450                 int sizes[] = new int[split.length - 1];
       
   451                 for (int i = 1; i <= sizes.length; i++)
       
   452                     sizes[i - 1] = split[i].isBlank() ? 0 : // default
       
   453                         Integer.parseInt(split[i].trim());
       
   454                 return sizes;
       
   455             }
       
   456         }
       
   457 
       
   458         // defaults
       
   459         if (RSA.equals(keyAlgorithm) || DSA.equals(keyAlgorithm)) {
       
   460             return new int[] { 1024, 2048, 0 }; // 0 is no keysize specified
       
   461         } else if (EC.equals(keyAlgorithm)) {
       
   462             return new int[] { 384, 571, 0 }; // 0 is no keysize specified
       
   463         } else {
       
   464             throw new RuntimeException("problem determining key sizes");
       
   465         }
       
   466     }
       
   467 
       
   468     private static List<String> digestAlgs() throws IOException {
       
   469         if (DIGEST_ALGORITHMS == null) DIGEST_ALGORITHMS = list("digestAlgs");
       
   470         if (DIGEST_ALGORITHMS.length == 0)
       
   471             return Arrays.asList(DEFAULT_DIGEST_ALGORITHMS);
       
   472         return Arrays.asList(DIGEST_ALGORITHMS);
   311     }
   473     }
   312 
   474 
   313     // Retrieves TSA info from the file which is specified by property tsaListFile,
   475     // Retrieves TSA info from the file which is specified by property tsaListFile,
   314     // or from property tsaList if tsaListFile is not available.
   476     // or from property tsaList if tsaListFile is not available.
   315     private static List<TsaInfo> tsaInfoList() throws IOException {
   477     private static List<TsaInfo> tsaInfoList() throws IOException {
   316         String[] tsaList = list("tsaList");
   478         String[] tsaList = list("tsaList");
   317 
   479 
   318         List<TsaInfo> tsaInfoList = new ArrayList<TsaInfo>();
   480         List<TsaInfo> tsaInfoList = new ArrayList<>();
   319         for (int i = 0; i < tsaList.length; i++) {
   481         for (int i = 0; i < tsaList.length; i++) {
   320             String[] values = tsaList[i].split(";digests=");
   482             String[] values = tsaList[i].split(";digests=");
   321 
   483 
   322             String[] digests = new String[0];
   484             String[] digests = new String[0];
   323             if (values.length == 2) {
   485             if (values.length == 2) {
   324                 digests = values[1].split(",");
   486                 digests = values[1].split(",");
   325             }
   487             }
   326 
   488 
   327             TsaInfo bufTsa = new TsaInfo(i, values[0]);
   489             String tsaUrl = values[0];
   328 
   490             if (tsaUrl.isEmpty() || tsaUrl.equalsIgnoreCase("notsa")) {
       
   491                 tsaUrl = null;
       
   492             }
       
   493             TsaInfo bufTsa = new TsaInfo(i, tsaUrl);
   329             for (String digest : digests) {
   494             for (String digest : digests) {
   330                 bufTsa.addDigest(digest);
   495                 bufTsa.addDigest(digest.toUpperCase());
   331             }
   496             }
   332 
       
   333             tsaInfoList.add(bufTsa);
   497             tsaInfoList.add(bufTsa);
   334         }
   498         }
   335 
   499 
       
   500         if (tsaInfoList.size() == 0) {
       
   501             throw new RuntimeException("TSA service is mandatory unless "
       
   502                     + "'notsa' specified explicitly.");
       
   503         }
   336         return tsaInfoList;
   504         return tsaInfoList;
   337     }
   505     }
   338 
   506 
   339     private static String[] list(String listProp)
   507     private static String[] list(String listProp) throws IOException {
   340             throws IOException {
       
   341         String listFileProp = listProp + "File";
   508         String listFileProp = listProp + "File";
   342         String listFile = System.getProperty(listFileProp);
   509         String listFile = System.getProperty(listFileProp);
   343         if (!isEmpty(listFile)) {
   510         if (!isEmpty(listFile)) {
   344             System.out.println(listFileProp + "=" + listFile);
   511             System.out.println(listFileProp + "=" + listFile);
   345             List<String> list = new ArrayList<String>();
   512             List<String> list = new ArrayList<>();
   346             BufferedReader reader = new BufferedReader(
   513             BufferedReader reader = new BufferedReader(
   347                     new FileReader(listFile));
   514                     new FileReader(listFile));
   348             String line;
   515             String line;
   349             while ((line = reader.readLine()) != null) {
   516             while ((line = reader.readLine()) != null) {
   350                 String item = line.trim();
   517                 String item = line.trim();
   367 
   534 
   368     // A JDK (signer) signs a jar with a variety of algorithms, and then all of
   535     // A JDK (signer) signs a jar with a variety of algorithms, and then all of
   369     // JDKs (verifiers), including the signer itself, try to verify the signed
   536     // JDKs (verifiers), including the signer itself, try to verify the signed
   370     // jars respectively.
   537     // jars respectively.
   371     private static List<SignItem> test(List<JdkInfo> jdkInfoList,
   538     private static List<SignItem> test(List<JdkInfo> jdkInfoList,
   372             List<TsaInfo> tsaInfoList, List<CertInfo> certList)
   539             List<TsaInfo> tsaInfoList, List<CertInfo> certList,
   373             throws Throwable {
   540             List<SignItem> jars) throws Throwable {
   374         detailsOutput.transferPhase();
   541         detailsOutput.transferPhase();
   375         List<SignItem> signItems = signing(jdkInfoList, tsaInfoList, certList);
   542         List<SignItem> signItems = new ArrayList<>();
       
   543         signItems.addAll(signing(jdkInfoList, tsaInfoList, certList, jars));
       
   544         if (TEST_JAR_UPDATE) {
       
   545             signItems.addAll(signing(jdkInfoList, tsaInfoList, certList,
       
   546                     updating(signItems.stream().filter(
       
   547                             x -> x.status != Status.ERROR)
       
   548                     .collect(Collectors.toList()))));
       
   549         }
   376 
   550 
   377         detailsOutput.transferPhase();
   551         detailsOutput.transferPhase();
   378         for (SignItem signItem : signItems) {
   552         for (SignItem signItem : signItems) {
   379             for (JdkInfo verifierInfo : jdkInfoList) {
   553             for (JdkInfo verifierInfo : jdkInfoList) {
   380                 // JDK 6 doesn't support EC
   554                 if (!verifierInfo.supportsKeyAlg(
   381                 if (!verifierInfo.isJdk6()
   555                         signItem.certInfo.keyAlgorithm)) continue;
   382                         || signItem.certInfo.keyAlgorithm != EC) {
   556                 VerifyItem verifyItem = VerifyItem.build(verifierInfo);
   383                     verifying(signItem, VerifyItem.build(verifierInfo));
   557                 verifyItem.addSignerCertInfos(signItem);
   384                 }
   558                 signItem.addVerifyItem(verifyItem);
   385             }
   559                 verifying(signItem, verifyItem);
       
   560             }
       
   561         }
       
   562 
       
   563         // if lastCertExpirationTime passed already now, probably some
       
   564         // certificate was already expired during jar signature verification
       
   565         // (jarsigner -verify) and the test should probably be repeated with an
       
   566         // increased validity period -DcertValidity CERT_VALIDITY
       
   567         long lastCertExpirationTime = lastCertStartTime + 24 * 60 * 60 * 1000;
       
   568         if (lastCertExpirationTime < System.currentTimeMillis()) {
       
   569             throw new AssertionError("CERT_VALIDITY (" + CERT_VALIDITY
       
   570                     + " [minutes]) was too short. "
       
   571                     + "Creating and signing the jars took longer, "
       
   572                     + "presumably at least "
       
   573                     + ((lastCertExpirationTime - System.currentTimeMillis())
       
   574                             / 60 * 1000 + CERT_VALIDITY) + " [minutes].");
   386         }
   575         }
   387 
   576 
   388         if (DELAY_VERIFY) {
   577         if (DELAY_VERIFY) {
   389             detailsOutput.transferPhase();
   578             detailsOutput.transferPhase();
   390             System.out.print("Waiting for delay verifying");
   579             System.out.print("Waiting for delay verifying");
   391             long lastCertExpirationTime = lastCertStartTime + 24 * 60 * 60 * 1000;
       
   392             while (System.currentTimeMillis() < lastCertExpirationTime) {
   580             while (System.currentTimeMillis() < lastCertExpirationTime) {
   393                 TimeUnit.SECONDS.sleep(30);
   581                 TimeUnit.SECONDS.sleep(30);
   394                 System.out.print(".");
   582                 System.out.print(".");
   395             }
   583             }
   396             System.out.println();
   584             System.out.println();
   402                 }
   590                 }
   403             }
   591             }
   404         }
   592         }
   405 
   593 
   406         detailsOutput.transferPhase();
   594         detailsOutput.transferPhase();
   407 
       
   408         return signItems;
   595         return signItems;
   409     }
   596     }
   410 
   597 
   411     private static List<SignItem> signing(List<JdkInfo> jdkInfos,
   598     private static List<SignItem> signing(List<JdkInfo> jdkInfos,
   412             List<TsaInfo> tsaList, List<CertInfo> certList) throws Throwable {
   599             List<TsaInfo> tsaList, List<CertInfo> certList,
   413         List<SignItem> signItems = new ArrayList<SignItem>();
   600             List<SignItem> unsignedJars) throws Throwable {
   414 
   601         List<SignItem> signItems = new ArrayList<>();
   415         Set<String> signFilter = new HashSet<String>();
   602 
   416 
   603         for (CertInfo certInfo : certList) {
   417         for (JdkInfo signerInfo : jdkInfos) {
   604             JdkInfo signerInfo = certInfo.jdkInfo;
   418             for (String keyAlgorithm : KEY_ALGORITHMS) {
   605             String keyAlgorithm = certInfo.keyAlgorithm;
   419                 // JDK 6 doesn't support EC
   606             String sigDigestAlgorithm = certInfo.digestAlgorithm;
   420                 if (signerInfo.isJdk6() && keyAlgorithm == EC) {
   607             int keySize = certInfo.keySize;
   421                     continue;
   608             boolean expired = certInfo.expired;
       
   609 
       
   610             for (String jarDigestAlgorithm : digestAlgs()) {
       
   611                 if (DEFAULT.equals(jarDigestAlgorithm)) {
       
   612                     jarDigestAlgorithm = null;
   422                 }
   613                 }
   423 
   614 
   424                 for (String digestAlgorithm : DIGEST_ALGORITHMS) {
   615                 for (TsaInfo tsaInfo : tsaList) {
   425                     String sigalg = sigalg(digestAlgorithm, keyAlgorithm);
   616                     String tsaUrl = tsaInfo.tsaUrl;
   426                     // If the signature algorithm is not supported by the JDK,
   617 
   427                     // it cannot try to sign jar with this algorithm.
   618                     List<String> tsaDigestAlgs = digestAlgs();
   428                     if (sigalg != null && !signerInfo.isSupportedSigalg(sigalg)) {
   619                     // no point in specifying a tsa digest algorithm
   429                         continue;
   620                     // for no TSA, except maybe it would issue a warning.
       
   621                     if (tsaUrl == null) tsaDigestAlgs = Arrays.asList(DEFAULT);
       
   622                     // If the JDK doesn't support option -tsadigestalg, the
       
   623                     // associated cases can just be ignored.
       
   624                     if (!signerInfo.supportsTsadigestalg) {
       
   625                         tsaDigestAlgs = Arrays.asList(DEFAULT);
   430                     }
   626                     }
   431 
   627                     for (String tsaDigestAlg : tsaDigestAlgs) {
   432                     // If the JDK doesn't support option -tsadigestalg, the
   628                         if (DEFAULT.equals(tsaDigestAlg)) {
   433                     // associated cases just be ignored.
   629                             tsaDigestAlg = null;
   434                     if (digestAlgorithm != DEFAULT
   630                         } else if (!tsaInfo.isDigestSupported(tsaDigestAlg)) {
   435                             && !signerInfo.supportsTsadigestalg) {
   631                             // It has to ignore the digest algorithm, which
   436                         continue;
   632                             // is not supported by the TSA server.
   437                     }
   633                             continue;
   438 
   634                         }
   439                     for (int keySize : keySizes(keyAlgorithm)) {
   635 
   440                         for (boolean expired : EXPIRED) {
   636                         if (tsaUrl != null && TsaFilter.filter(
   441                             CertInfo certInfo = new CertInfo(
   637                                 signerInfo.version,
   442                                     signerInfo.version,
   638                                 tsaDigestAlg,
   443                                     keyAlgorithm,
   639                                 expired,
   444                                     digestAlgorithm,
   640                                 tsaInfo.index)) {
   445                                     keySize,
   641                             continue;
   446                                     expired);
   642                         }
   447                             if (!certList.contains(certInfo)) {
   643 
   448                                 continue;
   644                         for (SignItem prevSign : unsignedJars) {
       
   645                             String unsignedJar = prevSign.signedJar;
       
   646 
       
   647                             SignItem signItem = SignItem.build(prevSign)
       
   648                                     .certInfo(certInfo)
       
   649                                     .jdkInfo(signerInfo);
       
   650                             String signedJar = unsignedJar + "-" + "JDK_" + (
       
   651                                     signerInfo.version + "-CERT_" + certInfo).
       
   652                                     replaceAll("[^a-z_0-9A-Z.]+", "-");
       
   653 
       
   654                             if (jarDigestAlgorithm != null) {
       
   655                                 signedJar += "-DIGESTALG_" + jarDigestAlgorithm;
       
   656                                 signItem.digestAlgorithm(jarDigestAlgorithm);
   449                             }
   657                             }
   450 
   658                             if (tsaUrl == null) {
   451                             String tsadigestalg = digestAlgorithm != DEFAULT
   659                                 signItem.tsaIndex(-1);
   452                                                 ? digestAlgorithm
   660                             } else {
   453                                                 : null;
   661                                 signedJar += "-TSA_" + tsaInfo.index;
   454 
   662                                 signItem.tsaIndex(tsaInfo.index);
   455                             for (TsaInfo tsaInfo : tsaList) {
   663                                 if (tsaDigestAlg != null) {
   456                                 // It has to ignore the digest algorithm, which
   664                                     signedJar += "-TSADIGALG_" + tsaDigestAlg;
   457                                 // is not supported by the TSA server.
   665                                     signItem.tsaDigestAlgorithm(tsaDigestAlg);
   458                                 if(!tsaInfo.isDigestSupported(tsadigestalg)) {
       
   459                                     continue;
       
   460                                 }
   666                                 }
   461 
       
   462                                 String tsaUrl = tsaInfo.tsaUrl;
       
   463                                 if (TsaFilter.filter(
       
   464                                         signerInfo.version,
       
   465                                         digestAlgorithm,
       
   466                                         expired,
       
   467                                         tsaInfo.index)) {
       
   468                                     tsaUrl = null;
       
   469                                 }
       
   470 
       
   471                                 String signedJar = "JDK_"
       
   472                                         + signerInfo.version + "-CERT_"
       
   473                                         + certInfo
       
   474                                         + (tsaUrl == null
       
   475                                            ? ""
       
   476                                            : "-TSA_" + tsaInfo.index);
       
   477 
       
   478                                 // It has to ignore the same jar signing.
       
   479                                 if (!signFilter.add(signedJar)) {
       
   480                                     continue;
       
   481                                 }
       
   482 
       
   483                                 SignItem signItem = SignItem.build()
       
   484                                         .certInfo(certInfo)
       
   485                                         .version(signerInfo.version)
       
   486                                         .signatureAlgorithm(sigalg)
       
   487                                         .tsaDigestAlgorithm(
       
   488                                                 tsaUrl == null
       
   489                                                 ? null
       
   490                                                 : tsadigestalg)
       
   491                                         .tsaIndex(
       
   492                                                 tsaUrl == null
       
   493                                                 ? -1
       
   494                                                 : tsaInfo.index)
       
   495                                         .signedJar(signedJar);
       
   496                                 String signingId = signingId(signItem);
       
   497                                 detailsOutput.writeAnchorName(signingId,
       
   498                                         "Signing: " + signingId);
       
   499 
       
   500                                 OutputAnalyzer signOA = signJar(
       
   501                                         signerInfo.jarsignerPath,
       
   502                                         sigalg,
       
   503                                         tsadigestalg,
       
   504                                         tsaUrl,
       
   505                                         certInfo.alias(),
       
   506                                         signedJar);
       
   507                                 Status signingStatus = signingStatus(signOA);
       
   508                                 signItem.status(signingStatus);
       
   509 
       
   510                                 if (signingStatus != Status.ERROR) {
       
   511                                     // Using the testing JDK, which is specified
       
   512                                     // by jtreg option "-jdk", to verify the
       
   513                                     // signed jar and extract the signature
       
   514                                     // algorithm and timestamp digest algorithm.
       
   515                                     String output = verifyJar(TEST_JARSIGNER,
       
   516                                             signedJar).getOutput();
       
   517                                     signItem.extractedSignatureAlgorithm(
       
   518                                             extract(output,
       
   519                                                     " *Signature algorithm.*",
       
   520                                                     ".*: |,.*"));
       
   521                                     signItem.extractedTsaDigestAlgorithm(
       
   522                                             extract(output,
       
   523                                                     " *Timestamp digest algorithm.*",
       
   524                                                     ".*: "));
       
   525                                 }
       
   526 
       
   527                                 signItems.add(signItem);
       
   528                             }
   667                             }
       
   668                             signItem.signedJar(signedJar);
       
   669 
       
   670                             String signingId = signingId(signItem);
       
   671                             detailsOutput.writeAnchorName(signingId,
       
   672                                     "Signing: " + signingId);
       
   673 
       
   674                             OutputAnalyzer signOA = signJar(
       
   675                                     signerInfo.jarsignerPath,
       
   676                                     certInfo.sigalg(),
       
   677                                     jarDigestAlgorithm,
       
   678                                     tsaDigestAlg,
       
   679                                     tsaUrl,
       
   680                                     certInfo.alias(),
       
   681                                     unsignedJar,
       
   682                                     signedJar);
       
   683                             Status signingStatus = signingStatus(signOA,
       
   684                                     tsaUrl != null);
       
   685                             signItem.status(signingStatus);
       
   686                             signItems.add(signItem);
   529                         }
   687                         }
   530                     }
   688                     }
   531                 }
   689                 }
   532             }
   690             }
   533         }
   691         }
   534 
   692 
   535         return signItems;
   693         return signItems;
   536     }
   694     }
   537 
   695 
       
   696     private static List<SignItem> updating(List<SignItem> prevSignItems)
       
   697             throws IOException {
       
   698         List<SignItem> updateItems = new ArrayList<>();
       
   699         for (SignItem prevSign : prevSignItems) {
       
   700             updateItems.addAll(updateJar(prevSign));
       
   701         }
       
   702         return updateItems;
       
   703     }
       
   704 
   538     private static void verifying(SignItem signItem, VerifyItem verifyItem)
   705     private static void verifying(SignItem signItem, VerifyItem verifyItem)
   539             throws Throwable {
   706             throws Throwable {
   540         boolean delayVerify = verifyItem.status == Status.NONE;
   707         // TODO: how will be ensured that the first verification is not after valid period expired which is only one minute?
   541         String verifyingId = verifyingId(signItem, verifyItem, !delayVerify);
   708         boolean delayVerify = verifyItem.status != Status.NONE;
       
   709         String verifyingId = verifyingId(signItem, verifyItem, delayVerify);
   542         detailsOutput.writeAnchorName(verifyingId, "Verifying: " + verifyingId);
   710         detailsOutput.writeAnchorName(verifyingId, "Verifying: " + verifyingId);
   543 
       
   544         OutputAnalyzer verifyOA = verifyJar(verifyItem.jdkInfo.jarsignerPath,
   711         OutputAnalyzer verifyOA = verifyJar(verifyItem.jdkInfo.jarsignerPath,
   545                 signItem.signedJar);
   712                 signItem.signedJar, verifyItem.certInfo == null ? null :
   546         Status verifyingStatus = verifyingStatus(verifyOA);
   713                 verifyItem.certInfo.alias());
   547 
   714         Status verifyingStatus = verifyingStatus(signItem, verifyItem, verifyOA);
   548         // It checks if the default timestamp digest algorithm is SHA-256.
   715 
   549         if (verifyingStatus != Status.ERROR
   716         try {
   550                 && signItem.tsaDigestAlgorithm == null) {
   717             String match = "^  ("
   551             verifyingStatus = signItem.extractedTsaDigestAlgorithm != null
   718                     + "  Signature algorithm: " + signItem.certInfo.
   552                                     && !signItem.extractedTsaDigestAlgorithm.matches("SHA-?256")
   719                             expectedSigalg() + ", " + signItem.certInfo.
   553                             ? Status.ERROR
   720                             expectedKeySize() + "-bit key"
   554                             : verifyingStatus;
   721                     + ")|("
   555             if (verifyingStatus == Status.ERROR) {
   722                     + "  Digest algorithm: " + signItem.expectedDigestAlg()
   556                 System.out.println("The default tsa digest is not SHA-256: "
   723                     + (signItem.tsaIndex < 0 ? "" :
   557                     + signItem.extractedTsaDigestAlgorithm);
   724                       ")|("
   558             }
   725                     + "Timestamped by \".+\" on .*"
   559         }
   726                     + ")|("
   560 
   727                     + "  Timestamp digest algorithm: "
   561         if (delayVerify) {
   728                             + signItem.expectedTsaDigestAlg()
   562             signItem.addVerifyItem(verifyItem.status(verifyingStatus));
   729                     + ")|("
       
   730                     + "  Timestamp signature algorithm: .*"
       
   731                       )
       
   732                     + ")$";
       
   733             verifyOA.stdoutShouldMatchByLine(
       
   734                     "^- Signed by \"CN=" +  signItem.certInfo.toString()
       
   735                             .replaceAll("[.]", "[.]") + "\"$",
       
   736                     "^(- Signed by \"CN=.+\")?$",
       
   737                     match);
       
   738         } catch (Throwable e) {
       
   739             e.printStackTrace();
       
   740             verifyingStatus = Status.ERROR;
       
   741         }
       
   742 
       
   743         if (!delayVerify) {
       
   744             verifyItem.status(verifyingStatus);
   563         } else {
   745         } else {
   564             verifyItem.delayStatus(verifyingStatus);
   746             verifyItem.delayStatus(verifyingStatus);
   565         }
   747         }
   566     }
   748 
   567 
   749         if (verifyItem.prevVerify != null) {
   568     // Return key sizes according to the specified key algorithm.
   750             verifying(signItem, verifyItem.prevVerify);
   569     private static int[] keySizes(String keyAlgorithm) {
   751         }
   570         if (keyAlgorithm == RSA || keyAlgorithm == DSA) {
       
   571             return new int[] { 1024, 2048, 0 };
       
   572         } else if (keyAlgorithm == EC) {
       
   573             return new int[] { 384, 571, 0 };
       
   574         }
       
   575 
       
   576         return null;
       
   577     }
   752     }
   578 
   753 
   579     // Determines the status of signing.
   754     // Determines the status of signing.
   580     private static Status signingStatus(OutputAnalyzer outputAnalyzer) {
   755     private static Status signingStatus(OutputAnalyzer outputAnalyzer,
   581         if (outputAnalyzer.getExitValue() == 0) {
   756             boolean tsa) {
   582             if (outputAnalyzer.getOutput().contains(Test.WARNING)) {
   757         if (outputAnalyzer.getExitValue() != 0) {
   583                 return Status.WARNING;
   758             return Status.ERROR;
   584             } else {
   759         }
   585                 return Status.NORMAL;
   760         if (!outputAnalyzer.getOutput().contains(Test.JAR_SIGNED)) {
   586             }
   761             return Status.ERROR;
       
   762         }
       
   763 
       
   764         boolean warning = false;
       
   765         for (String line : outputAnalyzer.getOutput().lines()
       
   766                 .toArray(String[]::new)) {
       
   767             if (line.matches(Test.ERROR + " ?")) return Status.ERROR;
       
   768             if (line.matches(Test.WARNING + " ?")) warning = true;
       
   769         }
       
   770         return warning ? Status.WARNING : Status.NORMAL;
       
   771     }
       
   772 
       
   773     // Determines the status of verifying.
       
   774     private static Status verifyingStatus(SignItem signItem, VerifyItem
       
   775             verifyItem, OutputAnalyzer outputAnalyzer) {
       
   776         List<String> expectedSignedContent = new ArrayList<>();
       
   777         if (verifyItem.certInfo == null) {
       
   778             expectedSignedContent.addAll(signItem.jarContents);
   587         } else {
   779         } else {
       
   780             SignItem i = signItem;
       
   781             while (i != null) {
       
   782                 if (i.certInfo != null && i.certInfo.equals(verifyItem.certInfo)) {
       
   783                     expectedSignedContent.addAll(i.jarContents);
       
   784                 }
       
   785                 i = i.prevSign;
       
   786             }
       
   787         }
       
   788         List<String> expectedUnsignedContent =
       
   789                 new ArrayList<>(signItem.jarContents);
       
   790         expectedUnsignedContent.removeAll(expectedSignedContent);
       
   791 
       
   792         int expectedExitCode = !STRICT || expectedUnsignedContent.isEmpty() ? 0 : 32;
       
   793         if (outputAnalyzer.getExitValue() != expectedExitCode) {
       
   794             System.out.println("verifyingStatus: error: exit code != " + expectedExitCode + ": " + outputAnalyzer.getExitValue() + " != " + expectedExitCode);
   588             return Status.ERROR;
   795             return Status.ERROR;
   589         }
   796         }
   590     }
   797         String expectedSuccessMessage = expectedUnsignedContent.isEmpty() ?
   591 
   798                 Test.JAR_VERIFIED : Test.JAR_VERIFIED_WITH_SIGNER_ERRORS;
   592     // Determines the status of verifying.
   799         if (!outputAnalyzer.getOutput().contains(expectedSuccessMessage)) {
   593     private static Status verifyingStatus(OutputAnalyzer outputAnalyzer) {
   800             System.out.println("verifyingStatus: error: expectedSuccessMessage not found: " + expectedSuccessMessage);
   594         if (outputAnalyzer.getExitValue() == 0) {
   801             return Status.ERROR;
   595             String output = outputAnalyzer.getOutput();
   802         }
   596             if (!output.contains(Test.JAR_VERIFIED)) {
   803 
       
   804         boolean tsa = signItem.tsaIndex >= 0;
       
   805         boolean warning = false;
       
   806         for (String line : outputAnalyzer.getOutput().lines()
       
   807                 .toArray(String[]::new)) {
       
   808             if (line.isBlank()) continue;
       
   809             if (Test.JAR_VERIFIED.equals(line)) continue;
       
   810             if (line.matches(Test.ERROR + " ?") && expectedExitCode == 0) {
       
   811                 System.out.println("verifyingStatus: error: line.matches(" + Test.ERROR + "\" ?\"): " + line);
   597                 return Status.ERROR;
   812                 return Status.ERROR;
   598             } else if (output.contains(Test.WARNING)) {
   813             }
   599                 return Status.WARNING;
   814             if (line.matches(Test.WARNING + " ?")) {
   600             } else {
   815                 warning = true;
   601                 return Status.NORMAL;
   816                 continue;
   602             }
   817             }
   603         } else {
   818             if (!warning) continue;
   604             return Status.ERROR;
   819             line = line.strip();
   605         }
   820             if (Test.NOT_YET_VALID_CERT_SIGNING_WARNING.equals(line)) continue;
   606     }
   821             if (Test.HAS_EXPIRING_CERT_SIGNING_WARNING.equals(line)) continue;
   607 
   822             if (Test.HAS_EXPIRING_CERT_VERIFYING_WARNING.equals(line)) continue;
   608     // Extracts string from text by specified patterns.
   823             if (line.matches("^" + Test.NO_TIMESTAMP_SIGNING_WARN_TEMPLATE
   609     private static String extract(String text, String linePattern,
   824                     .replaceAll(
   610             String replacePattern) {
   825                         "\\(%1\\$tY-%1\\$tm-%1\\$td\\)", "\\\\([^\\\\)]+\\\\)"
   611         Matcher lineMatcher = Pattern.compile(linePattern).matcher(text);
   826                         + "( or after any future revocation date)?")
   612         if (lineMatcher.find()) {
   827                     .replaceAll("[.]", "[.]") + "$") && !tsa) continue;
   613             String line = lineMatcher.group(0);
   828             if (line.matches("^" + Test.NO_TIMESTAMP_VERIFYING_WARN_TEMPLATE
   614             return line.replaceAll(replacePattern, "");
   829                     .replaceAll("\\(as early as %1\\$tY-%1\\$tm-%1\\$td\\)",
   615         } else {
   830                         "\\\\([^\\\\)]+\\\\)"
   616             return null;
   831                         + "( or after any future revocation date)?")
   617         }
   832                     .replaceAll("[.]", "[.]") + "$") && !tsa) continue;
       
   833             if (line.matches("^This jar contains signatures that do(es)? not "
       
   834                     + "include a timestamp[.] Without a timestamp, users may "
       
   835                     + "not be able to validate this jar after the signer "
       
   836                     + "certificate's expiration date \\([^\\)]+\\) or after "
       
   837                     + "any future revocation date[.]") && !tsa) continue;
       
   838             if (Test.CERTIFICATE_SELF_SIGNED.equals(line)) continue;
       
   839             if (Test.HAS_EXPIRED_CERT_VERIFYING_WARNING.equals(line)
       
   840                     && signItem.certInfo.expired) continue;
       
   841             System.out.println("verifyingStatus: unexpected line: " + line);
       
   842             return Status.ERROR; // treat unexpected warnings as error
       
   843         }
       
   844         return warning ? Status.WARNING : Status.NORMAL;
   618     }
   845     }
   619 
   846 
   620     // Using specified jarsigner to sign the pre-created jar with specified
   847     // Using specified jarsigner to sign the pre-created jar with specified
   621     // algorithms.
   848     // algorithms.
   622     private static OutputAnalyzer signJar(String jarsignerPath, String sigalg,
   849     private static OutputAnalyzer signJar(String jarsignerPath, String sigalg,
   623             String tsadigestalg, String tsa, String alias, String signedJar)
   850             String jarDigestAlgorithm,
   624             throws Throwable {
   851             String tsadigestalg, String tsa, String alias, String unsignedJar,
   625         List<String> arguments = new ArrayList<String>();
   852             String signedJar) throws Throwable {
       
   853         List<String> arguments = new ArrayList<>();
   626 
   854 
   627         if (PROXY_HOST != null && PROXY_PORT != null) {
   855         if (PROXY_HOST != null && PROXY_PORT != null) {
   628             arguments.add("-J-Dhttp.proxyHost=" + PROXY_HOST);
   856             arguments.add("-J-Dhttp.proxyHost=" + PROXY_HOST);
   629             arguments.add("-J-Dhttp.proxyPort=" + PROXY_PORT);
   857             arguments.add("-J-Dhttp.proxyPort=" + PROXY_PORT);
   630             arguments.add("-J-Dhttps.proxyHost=" + PROXY_HOST);
   858             arguments.add("-J-Dhttps.proxyHost=" + PROXY_HOST);
   631             arguments.add("-J-Dhttps.proxyPort=" + PROXY_PORT);
   859             arguments.add("-J-Dhttps.proxyPort=" + PROXY_PORT);
   632         }
   860         }
   633         arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
   861         arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
   634         arguments.add("-debug");
   862         arguments.add("-debug");
   635         arguments.add("-verbose");
   863         arguments.add("-verbose");
       
   864         if (jarDigestAlgorithm != null) {
       
   865             arguments.add("-digestalg");
       
   866             arguments.add(jarDigestAlgorithm);
       
   867         }
   636         if (sigalg != null) {
   868         if (sigalg != null) {
   637             arguments.add("-sigalg");
   869             arguments.add("-sigalg");
   638             arguments.add(sigalg);
   870             arguments.add(sigalg);
   639         }
   871         }
   640         if (tsa != null) {
   872         if (tsa != null) {
   647         }
   879         }
   648         arguments.add("-keystore");
   880         arguments.add("-keystore");
   649         arguments.add(KEYSTORE);
   881         arguments.add(KEYSTORE);
   650         arguments.add("-storepass");
   882         arguments.add("-storepass");
   651         arguments.add(PASSWORD);
   883         arguments.add(PASSWORD);
       
   884         arguments.add("-sigfile");
       
   885         arguments.add(nextSigfileName(alias, unsignedJar, signedJar));
   652         arguments.add("-signedjar");
   886         arguments.add("-signedjar");
   653         arguments.add(signedJar + ".jar");
   887         arguments.add(signedJar + ".jar");
   654         arguments.add(TEST_JAR_NAME);
   888         arguments.add(unsignedJar + ".jar");
   655         arguments.add(alias);
   889         arguments.add(alias);
   656 
   890 
   657         OutputAnalyzer outputAnalyzer = execTool(
   891         OutputAnalyzer outputAnalyzer = execTool(jarsignerPath,
   658                 jarsignerPath,
       
   659                 arguments.toArray(new String[arguments.size()]));
   892                 arguments.toArray(new String[arguments.size()]));
   660         return outputAnalyzer;
   893         return outputAnalyzer;
   661     }
   894     }
   662 
   895 
   663     // Using specified jarsigner to verify the signed jar.
   896     // Using specified jarsigner to verify the signed jar.
   664     private static OutputAnalyzer verifyJar(String jarsignerPath,
   897     private static OutputAnalyzer verifyJar(String jarsignerPath,
   665             String signedJar) throws Throwable {
   898             String signedJar, String alias) throws Throwable {
   666         OutputAnalyzer outputAnalyzer = execTool(
   899         List<String> arguments = new ArrayList<>();
   667                 jarsignerPath,
   900         arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
   668                 "-J-Djava.security.properties=" + JAVA_SECURITY,
   901         arguments.add("-debug");
   669                 "-debug",
   902         arguments.add("-verbose");
   670                 "-verbose",
   903         arguments.add("-certs");
   671                 "-certs",
   904         arguments.add("-keystore");
   672                 "-keystore", KEYSTORE,
   905         arguments.add(KEYSTORE);
   673                 "-verify", signedJar + ".jar");
   906         arguments.add("-verify");
       
   907         if (STRICT) arguments.add("-strict");
       
   908         arguments.add(signedJar + ".jar");
       
   909         if (alias != null) arguments.add(alias);
       
   910         OutputAnalyzer outputAnalyzer = execTool(jarsignerPath,
       
   911                 arguments.toArray(new String[arguments.size()]));
   674         return outputAnalyzer;
   912         return outputAnalyzer;
   675     }
   913     }
   676 
   914 
   677     // Generates the test result report.
   915     // Generates the test result report.
   678     private static boolean generateReport(List<TsaInfo> tsaList,
   916     private static boolean generateReport(List<JdkInfo> jdkList, List<TsaInfo> tsaList,
   679             List<SignItem> signItems) throws IOException {
   917             List<SignItem> signItems) throws IOException {
   680         System.out.println("Report is being generated...");
   918         System.out.println("Report is being generated...");
   681 
   919 
   682         StringBuilder report = new StringBuilder();
   920         StringBuilder report = new StringBuilder();
   683         report.append(HtmlHelper.startHtml());
   921         report.append(HtmlHelper.startHtml());
   684         report.append(HtmlHelper.startPre());
   922         report.append(HtmlHelper.startPre());
       
   923 
       
   924         // Generates JDK list
       
   925         report.append("JDK list:\n");
       
   926         for(JdkInfo jdkInfo : jdkList) {
       
   927             report.append(String.format("%d=%s%n",
       
   928                     jdkInfo.index,
       
   929                     jdkInfo.runtimeVersion));
       
   930         }
       
   931 
   685         // Generates TSA URLs
   932         // Generates TSA URLs
   686         report.append("TSA list:\n");
   933         report.append("TSA list:\n");
   687         for(TsaInfo tsaInfo : tsaList) {
   934         for(TsaInfo tsaInfo : tsaList) {
   688             report.append(
   935             report.append(
   689                     String.format("%d=%s%n", tsaInfo.index, tsaInfo.tsaUrl));
   936                     String.format("%d=%s%n", tsaInfo.index,
       
   937                             tsaInfo.tsaUrl == null ? "notsa" : tsaInfo.tsaUrl));
   690         }
   938         }
   691         report.append(HtmlHelper.endPre());
   939         report.append(HtmlHelper.endPre());
   692 
   940 
   693         report.append(HtmlHelper.startTable());
   941         report.append(HtmlHelper.startTable());
   694         // Generates report headers.
   942         // Generates report headers.
   695         List<String> headers = new ArrayList<String>();
   943         List<String> headers = new ArrayList<>();
   696         headers.add("[Certificate]");
   944         headers.add("[Jarfile]");
       
   945         headers.add("[Signing Certificate]");
   697         headers.add("[Signer JDK]");
   946         headers.add("[Signer JDK]");
   698         headers.add("[Signature Algorithm]");
   947         headers.add("[Signature Algorithm]");
   699         headers.add("[TSA Digest]");
   948         headers.add("[Jar Digest Algorithm]");
       
   949         headers.add("[TSA Digest Algorithm]");
   700         headers.add("[TSA]");
   950         headers.add("[TSA]");
   701         headers.add("[Signing Status]");
   951         headers.add("[Signing Status]");
   702         headers.add("[Verifier JDK]");
   952         headers.add("[Verifier JDK]");
       
   953         headers.add("[Verifying Certificate]");
   703         headers.add("[Verifying Status]");
   954         headers.add("[Verifying Status]");
   704         if (DELAY_VERIFY) {
   955         if (DELAY_VERIFY) {
   705             headers.add("[Delay Verifying Status]");
   956             headers.add("[Delay Verifying Status]");
   706         }
   957         }
   707         headers.add("[Failed]");
   958         headers.add("[Failed]");
   708         report.append(HtmlHelper.htmlRow(
   959         report.append(HtmlHelper.htmlRow(
   709                 headers.toArray(new String[headers.size()])));
   960                 headers.toArray(new String[headers.size()])));
   710 
   961 
   711         StringBuilder failedReport = new StringBuilder(report.toString());
   962         StringBuilder failedReport = new StringBuilder(report.toString());
   712 
   963 
   713         boolean failed = false;
   964         boolean failed = signItems.isEmpty();
   714 
   965 
   715         // Generates report rows.
   966         // Generates report rows.
   716         for (SignItem signItem : signItems) {
   967         for (SignItem signItem : signItems) {
       
   968             failed = failed || signItem.verifyItems.isEmpty();
   717             for (VerifyItem verifyItem : signItem.verifyItems) {
   969             for (VerifyItem verifyItem : signItem.verifyItems) {
   718                 String reportRow = reportRow(signItem, verifyItem);
   970                 String reportRow = reportRow(signItem, verifyItem);
   719                 report.append(reportRow);
   971                 report.append(reportRow);
   720                 boolean isFailedCase = isFailed(signItem, verifyItem);
   972                 boolean isFailedCase = isFailed(signItem, verifyItem);
   721                 if (isFailedCase) {
   973                 if (isFailedCase) {
   765 
  1017 
   766     // Executes the specified JDK tools, such as keytool and jarsigner, and
  1018     // Executes the specified JDK tools, such as keytool and jarsigner, and
   767     // ensures the output is in US English.
  1019     // ensures the output is in US English.
   768     private static OutputAnalyzer execTool(String toolPath, String... args)
  1020     private static OutputAnalyzer execTool(String toolPath, String... args)
   769             throws Throwable {
  1021             throws Throwable {
   770         String[] cmd = new String[args.length + 4];
  1022         long start = System.currentTimeMillis();
   771         cmd[0] = toolPath;
  1023         try {
   772         cmd[1] = "-J-Duser.language=en";
  1024 
   773         cmd[2] = "-J-Duser.country=US";
  1025             String[] cmd = new String[args.length + 4];
   774         cmd[3] = "-J-Djava.security.egd=file:/dev/./urandom";
  1026             cmd[0] = toolPath;
   775         System.arraycopy(args, 0, cmd, 4, args.length);
  1027             cmd[1] = "-J-Duser.language=en";
   776         return ProcessTools.executeCommand(cmd);
  1028             cmd[2] = "-J-Duser.country=US";
       
  1029             cmd[3] = "-J-Djava.security.egd=file:/dev/./urandom";
       
  1030             System.arraycopy(args, 0, cmd, 4, args.length);
       
  1031             return ProcessTools.executeCommand(cmd);
       
  1032 
       
  1033         } finally {
       
  1034             long end = System.currentTimeMillis();
       
  1035             System.out.println("child process duration [ms]: " + (end - start));
       
  1036         }
   777     }
  1037     }
   778 
  1038 
   779     private static class JdkInfo {
  1039     private static class JdkInfo {
   780 
  1040 
       
  1041         private int index;
   781         private final String jdkPath;
  1042         private final String jdkPath;
   782         private final String jarsignerPath;
  1043         private final String jarsignerPath;
   783         private final String version;
  1044         private final String runtimeVersion;
       
  1045         private String version;
       
  1046         private final int majorVersion;
   784         private final boolean supportsTsadigestalg;
  1047         private final boolean supportsTsadigestalg;
   785 
  1048 
   786         private Map<String, Boolean> sigalgMap = new HashMap<String, Boolean>();
  1049         private Map<String, Boolean> sigalgMap = new HashMap<>();
   787 
  1050 
   788         private JdkInfo(String jdkPath) throws Throwable {
  1051         private JdkInfo(String jdkPath) throws Throwable {
   789             this.jdkPath = jdkPath;
  1052             this.jdkPath = jdkPath;
   790             version = execJdkUtils(jdkPath, JdkUtils.M_JAVA_RUNTIME_VERSION);
  1053             jarsignerPath = jarsignerPath(jdkPath);
   791             if (version == null || version.trim().isEmpty()) {
  1054             runtimeVersion = execJdkUtils(jdkPath, JdkUtils.M_JAVA_RUNTIME_VERSION);
       
  1055             if (runtimeVersion == null || runtimeVersion.isBlank()) {
   792                 throw new RuntimeException(
  1056                 throw new RuntimeException(
   793                         "Cannot determine the JDK version: " + jdkPath);
  1057                         "Cannot determine the JDK version: " + jdkPath);
   794             }
  1058             }
   795             jarsignerPath = jarsignerPath(jdkPath);
  1059             version = execJdkUtils(jdkPath, JdkUtils.M_JAVA_VERSION);
       
  1060             majorVersion = Integer.parseInt((runtimeVersion.matches("^1[.].*") ?
       
  1061                     runtimeVersion.substring(2) : runtimeVersion).replaceAll("[^0-9].*$", ""));
   796             supportsTsadigestalg = execTool(jarsignerPath, "-help")
  1062             supportsTsadigestalg = execTool(jarsignerPath, "-help")
   797                     .getOutput().contains("-tsadigestalg");
  1063                     .getOutput().contains("-tsadigestalg");
   798         }
  1064         }
   799 
  1065 
   800         private boolean isSupportedSigalg(String sigalg) throws Throwable {
  1066         private boolean isSupportedSigalg(String sigalg) throws Throwable {
   801             if (!sigalgMap.containsKey(sigalg)) {
  1067             if (!sigalgMap.containsKey(sigalg)) {
   802                 boolean isSupported = "true".equalsIgnoreCase(
  1068                 boolean isSupported = Boolean.parseBoolean(
   803                         execJdkUtils(
  1069                         execJdkUtils(
   804                                 jdkPath,
  1070                                 jdkPath,
   805                                 JdkUtils.M_IS_SUPPORTED_SIGALG,
  1071                                 JdkUtils.M_IS_SUPPORTED_SIGALG,
   806                                 sigalg));
  1072                                 sigalg));
   807                 sigalgMap.put(sigalg, isSupported);
  1073                 sigalgMap.put(sigalg, isSupported);
   808             }
  1074             }
   809 
  1075 
   810             return sigalgMap.get(sigalg);
  1076             return sigalgMap.get(sigalg);
   811         }
  1077         }
   812 
  1078 
   813         private boolean isJdk6() {
  1079         private boolean isAtLeastMajorVersion(int minVersion) {
   814             return version.startsWith("1.6");
  1080             return majorVersion >= minVersion;
       
  1081         }
       
  1082 
       
  1083         private boolean supportsKeyAlg(String keyAlgorithm) {
       
  1084             // JDK 6 doesn't support EC
       
  1085             return isAtLeastMajorVersion(6) || !EC.equals(keyAlgorithm);
   815         }
  1086         }
   816 
  1087 
   817         @Override
  1088         @Override
   818         public int hashCode() {
  1089         public int hashCode() {
   819             final int prime = 31;
  1090             final int prime = 31;
   820             int result = 1;
  1091             int result = 1;
   821             result = prime * result
  1092             result = prime * result
   822                     + ((version == null) ? 0 : version.hashCode());
  1093                     + ((runtimeVersion == null) ? 0 : runtimeVersion.hashCode());
   823             return result;
  1094             return result;
   824         }
  1095         }
   825 
  1096 
   826         @Override
  1097         @Override
   827         public boolean equals(Object obj) {
  1098         public boolean equals(Object obj) {
   830             if (obj == null)
  1101             if (obj == null)
   831                 return false;
  1102                 return false;
   832             if (getClass() != obj.getClass())
  1103             if (getClass() != obj.getClass())
   833                 return false;
  1104                 return false;
   834             JdkInfo other = (JdkInfo) obj;
  1105             JdkInfo other = (JdkInfo) obj;
   835             if (version == null) {
  1106             if (runtimeVersion == null) {
   836                 if (other.version != null)
  1107                 if (other.runtimeVersion != null)
   837                     return false;
  1108                     return false;
   838             } else if (!version.equals(other.version))
  1109             } else if (!runtimeVersion.equals(other.runtimeVersion))
   839                 return false;
  1110                 return false;
   840             return true;
  1111             return true;
   841         }
  1112         }
       
  1113 
       
  1114         @Override
       
  1115         public String toString() {
       
  1116             return "JdkInfo[" + runtimeVersion + ", " + jdkPath + "]";
       
  1117         }
   842     }
  1118     }
   843 
  1119 
   844     private static class TsaInfo {
  1120     private static class TsaInfo {
   845 
  1121 
   846         private final int index;
  1122         private final int index;
   847         private final String tsaUrl;
  1123         private final String tsaUrl;
   848         private Set<String> digestList = new HashSet<String>();
  1124         private Set<String> digestList = new HashSet<>();
   849 
  1125 
   850         private TsaInfo(int index, String tsa) {
  1126         private TsaInfo(int index, String tsa) {
   851             this.index = index;
  1127             this.index = index;
   852             this.tsaUrl = tsa;
  1128             this.tsaUrl = tsa;
   853         }
  1129         }
   854 
  1130 
   855         private void addDigest(String digest) {
  1131         private void addDigest(String digest) {
   856             if (!ignore(digest)) {
  1132             digestList.add(digest);
   857                 digestList.add(digest);
       
   858             }
       
   859         }
       
   860 
       
   861         private static boolean ignore(String digest) {
       
   862             return !SHA1.equalsIgnoreCase(digest)
       
   863                     && !SHA256.equalsIgnoreCase(digest)
       
   864                     && !SHA512.equalsIgnoreCase(digest);
       
   865         }
  1133         }
   866 
  1134 
   867         private boolean isDigestSupported(String digest) {
  1135         private boolean isDigestSupported(String digest) {
   868             return digest == null || digestList.isEmpty()
  1136             return digest == null || digestList.isEmpty()
   869                     || digestList.contains(digest);
  1137                     || digestList.contains(digest);
   870         }
  1138         }
       
  1139 
       
  1140         @Override
       
  1141         public String toString() {
       
  1142             return "TsaInfo[" + index + ", " + tsaUrl + "]";
       
  1143         }
   871     }
  1144     }
   872 
  1145 
   873     private static class CertInfo {
  1146     private static class CertInfo {
   874 
  1147 
   875         private final String jdkVersion;
  1148         private static int certCounter;
       
  1149 
       
  1150         // nr distinguishes cert CNs in jarsigner -verify output
       
  1151         private final int nr = ++certCounter;
       
  1152         private final JdkInfo jdkInfo;
   876         private final String keyAlgorithm;
  1153         private final String keyAlgorithm;
   877         private final String digestAlgorithm;
  1154         private final String digestAlgorithm;
   878         private final int keySize;
  1155         private final int keySize;
   879         private final boolean expired;
  1156         private final boolean expired;
   880 
  1157 
   881         private CertInfo(String jdkVersion, String keyAlgorithm,
  1158         private CertInfo(JdkInfo jdkInfo, String keyAlgorithm,
   882                 String digestAlgorithm, int keySize, boolean expired) {
  1159                 String digestAlgorithm, int keySize, boolean expired) {
   883             this.jdkVersion = jdkVersion;
  1160             this.jdkInfo = jdkInfo;
   884             this.keyAlgorithm = keyAlgorithm;
  1161             this.keyAlgorithm = keyAlgorithm;
   885             this.digestAlgorithm = digestAlgorithm;
  1162             this.digestAlgorithm = digestAlgorithm;
   886             this.keySize = keySize;
  1163             this.keySize = keySize;
   887             this.expired = expired;
  1164             this.expired = expired;
       
  1165         }
       
  1166 
       
  1167         private String sigalg() {
       
  1168             return DEFAULT.equals(digestAlgorithm) ? null : expectedSigalg();
       
  1169         }
       
  1170 
       
  1171         private String expectedSigalg() {
       
  1172             return (DEFAULT.equals(this.digestAlgorithm) ? this.digestAlgorithm
       
  1173                     : "SHA-256").replace("-", "") + "with" +
       
  1174                     keyAlgorithm + (EC.equals(keyAlgorithm) ? "DSA" : "");
       
  1175         }
       
  1176 
       
  1177         private int expectedKeySize() {
       
  1178             if (keySize != 0) return keySize;
       
  1179 
       
  1180             // defaults
       
  1181             if (RSA.equals(keyAlgorithm) || DSA.equals(keyAlgorithm)) {
       
  1182                 return 2048;
       
  1183             } else if (EC.equals(keyAlgorithm)) {
       
  1184                 return 256;
       
  1185             } else {
       
  1186                 throw new RuntimeException("problem determining key size");
       
  1187             }
   888         }
  1188         }
   889 
  1189 
   890         @Override
  1190         @Override
   891         public int hashCode() {
  1191         public int hashCode() {
   892             final int prime = 31;
  1192             final int prime = 31;
   893             int result = 1;
  1193             int result = 1;
   894             result = prime * result
  1194             result = prime * result
   895                     + ((digestAlgorithm == null) ? 0 : digestAlgorithm.hashCode());
  1195                     + (digestAlgorithm == null ? 0 : digestAlgorithm.hashCode());
   896             result = prime * result + (expired ? 1231 : 1237);
  1196             result = prime * result + (expired ? 1231 : 1237);
   897             result = prime * result
  1197             result = prime * result
   898                     + ((jdkVersion == null) ? 0 : jdkVersion.hashCode());
  1198                     + (jdkInfo == null ? 0 : jdkInfo.hashCode());
   899             result = prime * result
  1199             result = prime * result
   900                     + ((keyAlgorithm == null) ? 0 : keyAlgorithm.hashCode());
  1200                     + (keyAlgorithm == null ? 0 : keyAlgorithm.hashCode());
   901             result = prime * result + keySize;
  1201             result = prime * result + keySize;
   902             return result;
  1202             return result;
   903         }
  1203         }
   904 
  1204 
   905         @Override
  1205         @Override
   916                     return false;
  1216                     return false;
   917             } else if (!digestAlgorithm.equals(other.digestAlgorithm))
  1217             } else if (!digestAlgorithm.equals(other.digestAlgorithm))
   918                 return false;
  1218                 return false;
   919             if (expired != other.expired)
  1219             if (expired != other.expired)
   920                 return false;
  1220                 return false;
   921             if (jdkVersion == null) {
  1221             if (jdkInfo == null) {
   922                 if (other.jdkVersion != null)
  1222                 if (other.jdkInfo != null)
   923                     return false;
  1223                     return false;
   924             } else if (!jdkVersion.equals(other.jdkVersion))
  1224             } else if (!jdkInfo.equals(other.jdkInfo))
   925                 return false;
  1225                 return false;
   926             if (keyAlgorithm == null) {
  1226             if (keyAlgorithm == null) {
   927                 if (other.keyAlgorithm != null)
  1227                 if (other.keyAlgorithm != null)
   928                     return false;
  1228                     return false;
   929             } else if (!keyAlgorithm.equals(other.keyAlgorithm))
  1229             } else if (!keyAlgorithm.equals(other.keyAlgorithm))
   932                 return false;
  1232                 return false;
   933             return true;
  1233             return true;
   934         }
  1234         }
   935 
  1235 
   936         private String alias() {
  1236         private String alias() {
   937             return jdkVersion + "_" + toString();
  1237             return (jdkInfo.version + "_" + toString())
       
  1238                     // lower case for jks due to
       
  1239                     // sun.security.provider.JavaKeyStore.JDK.convertAlias
       
  1240                     .toLowerCase(Locale.ENGLISH);
   938         }
  1241         }
   939 
  1242 
   940         @Override
  1243         @Override
   941         public String toString() {
  1244         public String toString() {
   942             return keyAlgorithm + "_" + digestAlgorithm
  1245             return "nr" + nr + "_"
       
  1246                     + keyAlgorithm + "_" + digestAlgorithm
   943                     + (keySize == 0 ? "" : "_" + keySize)
  1247                     + (keySize == 0 ? "" : "_" + keySize)
   944                     + (expired ? "_Expired" : "");
  1248                     + (expired ? "_Expired" : "");
   945         }
  1249         }
   946     }
  1250     }
   947 
  1251 
   948     // It does only one timestamping for the same JDK, digest algorithm and
  1252     // It does only one timestamping for the same JDK, digest algorithm and
   949     // TSA service with an arbitrary valid/expired certificate.
  1253     // TSA service with an arbitrary valid/expired certificate.
   950     private static class TsaFilter {
  1254     private static class TsaFilter {
   951 
  1255 
   952         private static final Set<Condition> SET = new HashSet<Condition>();
  1256         private static final Set<Condition> SET = new HashSet<>();
   953 
  1257 
   954         private static boolean filter(String signerVersion,
  1258         private static boolean filter(String signerVersion,
   955                 String digestAlgorithm, boolean expiredCert, int tsaIndex) {
  1259                 String digestAlgorithm, boolean expiredCert, int tsaIndex) {
   956             return !SET.add(new Condition(signerVersion, digestAlgorithm,
  1260             return !SET.add(new Condition(signerVersion, digestAlgorithm,
   957                     expiredCert, tsaIndex));
  1261                     expiredCert, tsaIndex));
  1027         NORMAL
  1331         NORMAL
  1028     }
  1332     }
  1029 
  1333 
  1030     private static class SignItem {
  1334     private static class SignItem {
  1031 
  1335 
       
  1336         private SignItem prevSign;
  1032         private CertInfo certInfo;
  1337         private CertInfo certInfo;
  1033         private String version;
  1338         private JdkInfo jdkInfo;
  1034         private String signatureAlgorithm;
  1339         private String digestAlgorithm;
  1035         // Signature algorithm that is extracted from verification output.
       
  1036         private String extractedSignatureAlgorithm;
       
  1037         private String tsaDigestAlgorithm;
  1340         private String tsaDigestAlgorithm;
  1038         // TSA digest algorithm that is extracted from verification output.
       
  1039         private String extractedTsaDigestAlgorithm;
       
  1040         private int tsaIndex;
  1341         private int tsaIndex;
  1041         private Status status;
  1342         private Status status;
       
  1343         private String unsignedJar;
  1042         private String signedJar;
  1344         private String signedJar;
  1043 
  1345         private List<String> jarContents = new ArrayList<>();
  1044         private List<VerifyItem> verifyItems = new ArrayList<VerifyItem>();
  1346 
       
  1347         private List<VerifyItem> verifyItems = new ArrayList<>();
  1045 
  1348 
  1046         private static SignItem build() {
  1349         private static SignItem build() {
  1047             return new SignItem();
  1350             return new SignItem()
       
  1351                     .addContentFiles(Arrays.asList("META-INF/MANIFEST.MF"));
       
  1352         }
       
  1353 
       
  1354         private static SignItem build(SignItem prevSign) {
       
  1355             return build().prevSign(prevSign).unsignedJar(prevSign.signedJar)
       
  1356                     .addContentFiles(prevSign.jarContents);
       
  1357         }
       
  1358 
       
  1359         private SignItem prevSign(SignItem prevSign) {
       
  1360             this.prevSign = prevSign;
       
  1361             return this;
  1048         }
  1362         }
  1049 
  1363 
  1050         private SignItem certInfo(CertInfo certInfo) {
  1364         private SignItem certInfo(CertInfo certInfo) {
  1051             this.certInfo = certInfo;
  1365             this.certInfo = certInfo;
  1052             return this;
  1366             return this;
  1053         }
  1367         }
  1054 
  1368 
  1055         private SignItem version(String version) {
  1369         private SignItem jdkInfo(JdkInfo jdkInfo) {
  1056             this.version = version;
  1370             this.jdkInfo = jdkInfo;
  1057             return this;
  1371             return this;
  1058         }
  1372         }
  1059 
  1373 
  1060         private SignItem signatureAlgorithm(String signatureAlgorithm) {
  1374         private SignItem digestAlgorithm(String digestAlgorithm) {
  1061             this.signatureAlgorithm = signatureAlgorithm;
  1375             this.digestAlgorithm = digestAlgorithm;
  1062             return this;
  1376             return this;
  1063         }
  1377         }
  1064 
  1378 
  1065         private SignItem extractedSignatureAlgorithm(
  1379         String expectedDigestAlg() {
  1066                 String extractedSignatureAlgorithm) {
  1380             return digestAlgorithm != null ? digestAlgorithm : "SHA-256";
  1067             this.extractedSignatureAlgorithm = extractedSignatureAlgorithm;
       
  1068             return this;
       
  1069         }
  1381         }
  1070 
  1382 
  1071         private SignItem tsaDigestAlgorithm(String tsaDigestAlgorithm) {
  1383         private SignItem tsaDigestAlgorithm(String tsaDigestAlgorithm) {
  1072             this.tsaDigestAlgorithm = tsaDigestAlgorithm;
  1384             this.tsaDigestAlgorithm = tsaDigestAlgorithm;
  1073             return this;
  1385             return this;
  1074         }
  1386         }
  1075 
  1387 
  1076         private SignItem extractedTsaDigestAlgorithm(
  1388         String expectedTsaDigestAlg() {
  1077                 String extractedTsaDigestAlgorithm) {
  1389             return tsaDigestAlgorithm != null ? tsaDigestAlgorithm : "SHA-256";
  1078             this.extractedTsaDigestAlgorithm = extractedTsaDigestAlgorithm;
       
  1079             return this;
       
  1080         }
  1390         }
  1081 
  1391 
  1082         private SignItem tsaIndex(int tsaIndex) {
  1392         private SignItem tsaIndex(int tsaIndex) {
  1083             this.tsaIndex = tsaIndex;
  1393             this.tsaIndex = tsaIndex;
  1084             return this;
  1394             return this;
  1087         private SignItem status(Status status) {
  1397         private SignItem status(Status status) {
  1088             this.status = status;
  1398             this.status = status;
  1089             return this;
  1399             return this;
  1090         }
  1400         }
  1091 
  1401 
       
  1402         private SignItem unsignedJar(String unsignedJar) {
       
  1403             this.unsignedJar = unsignedJar;
       
  1404             return this;
       
  1405         }
       
  1406 
  1092         private SignItem signedJar(String signedJar) {
  1407         private SignItem signedJar(String signedJar) {
  1093             this.signedJar = signedJar;
  1408             this.signedJar = signedJar;
  1094             return this;
  1409             return this;
  1095         }
  1410         }
  1096 
  1411 
       
  1412         private SignItem addContentFiles(List<String> files) {
       
  1413             this.jarContents.addAll(files);
       
  1414             return this;
       
  1415         }
       
  1416 
  1097         private void addVerifyItem(VerifyItem verifyItem) {
  1417         private void addVerifyItem(VerifyItem verifyItem) {
  1098             verifyItems.add(verifyItem);
  1418             verifyItems.add(verifyItem);
  1099         }
  1419         }
       
  1420 
       
  1421         private boolean isErrorInclPrev() {
       
  1422             if (prevSign != null && prevSign.isErrorInclPrev()) {
       
  1423                 System.out.println("SignItem.isErrorInclPrev: returning true from previous");
       
  1424                 return true;
       
  1425             }
       
  1426 
       
  1427             return status == Status.ERROR;
       
  1428         }
       
  1429         private List<String> toStringWithPrev(Function<SignItem,String> toStr) {
       
  1430             List<String> s = new ArrayList<>();
       
  1431             if (prevSign != null) {
       
  1432                 s.addAll(prevSign.toStringWithPrev(toStr));
       
  1433             }
       
  1434             if (status != null) { // no status means jar creation or update item
       
  1435                 s.add(toStr.apply(this));
       
  1436             }
       
  1437             return s;
       
  1438         }
  1100     }
  1439     }
  1101 
  1440 
  1102     private static class VerifyItem {
  1441     private static class VerifyItem {
  1103 
  1442 
       
  1443         private VerifyItem prevVerify;
       
  1444         private CertInfo certInfo;
  1104         private JdkInfo jdkInfo;
  1445         private JdkInfo jdkInfo;
  1105         private Status status = Status.NONE;
  1446         private Status status = Status.NONE;
  1106         private Status delayStatus = Status.NONE;
  1447         private Status delayStatus = Status.NONE;
  1107 
  1448 
  1108         private static VerifyItem build(JdkInfo jdkInfo) {
  1449         private static VerifyItem build(JdkInfo jdkInfo) {
  1109             VerifyItem verifyItem = new VerifyItem();
  1450             VerifyItem verifyItem = new VerifyItem();
  1110             verifyItem.jdkInfo = jdkInfo;
  1451             verifyItem.jdkInfo = jdkInfo;
  1111             return verifyItem;
  1452             return verifyItem;
  1112         }
  1453         }
  1113 
  1454 
       
  1455         private VerifyItem certInfo(CertInfo certInfo) {
       
  1456             this.certInfo = certInfo;
       
  1457             return this;
       
  1458         }
       
  1459 
       
  1460         private void addSignerCertInfos(SignItem signItem) {
       
  1461             VerifyItem prevVerify = this;
       
  1462             CertInfo lastCertInfo = null;
       
  1463             while (signItem != null) {
       
  1464                 // (signItem.certInfo == null) means create or update jar step
       
  1465                 if (signItem.certInfo != null
       
  1466                         && !signItem.certInfo.equals(lastCertInfo)) {
       
  1467                     lastCertInfo = signItem.certInfo;
       
  1468                     prevVerify = prevVerify.prevVerify =
       
  1469                             build(jdkInfo).certInfo(signItem.certInfo);
       
  1470                 }
       
  1471                 signItem = signItem.prevSign;
       
  1472             }
       
  1473         }
       
  1474 
  1114         private VerifyItem status(Status status) {
  1475         private VerifyItem status(Status status) {
  1115             this.status = status;
  1476             this.status = status;
  1116             return this;
  1477             return this;
  1117         }
  1478         }
  1118 
  1479 
       
  1480         private boolean isErrorInclPrev() {
       
  1481             if (prevVerify != null && prevVerify.isErrorInclPrev()) {
       
  1482                 System.out.println("VerifyItem.isErrorInclPrev: returning true from previous");
       
  1483                 return true;
       
  1484             }
       
  1485 
       
  1486             return status == Status.ERROR || delayStatus == Status.ERROR;
       
  1487         }
       
  1488 
  1119         private VerifyItem delayStatus(Status status) {
  1489         private VerifyItem delayStatus(Status status) {
  1120             this.delayStatus = status;
  1490             this.delayStatus = status;
  1121             return this;
  1491             return this;
  1122         }
  1492         }
       
  1493 
       
  1494         private List<String> toStringWithPrev(
       
  1495                 Function<VerifyItem,String> toStr) {
       
  1496             List<String> s = new ArrayList<>();
       
  1497             if (prevVerify != null) {
       
  1498                 s.addAll(prevVerify.toStringWithPrev(toStr));
       
  1499             }
       
  1500             s.add(toStr.apply(this));
       
  1501             return s;
       
  1502         }
  1123     }
  1503     }
  1124 
  1504 
  1125     // The identifier for a specific signing.
  1505     // The identifier for a specific signing.
  1126     private static String signingId(SignItem signItem) {
  1506     private static String signingId(SignItem signItem) {
  1127         return signItem.signedJar;
  1507         return signItem.signedJar;
  1128     }
  1508     }
  1129 
  1509 
  1130     // The identifier for a specific verifying.
  1510     // The identifier for a specific verifying.
  1131     private static String verifyingId(SignItem signItem, VerifyItem verifyItem,
  1511     private static String verifyingId(SignItem signItem, VerifyItem verifyItem,
  1132             boolean delayVerify) {
  1512             boolean delayVerify) {
  1133         return "S_" + signingId(signItem) + "-" + (delayVerify ? "DV" : "V")
  1513         return signingId(signItem) + (delayVerify ? "-DV" : "-V")
  1134                 + "_" + verifyItem.jdkInfo.version;
  1514                 + "_" + verifyItem.jdkInfo.version +
       
  1515                 (verifyItem.certInfo == null ? "" : "_" + verifyItem.certInfo);
  1135     }
  1516     }
  1136 
  1517 
  1137     private static String reportRow(SignItem signItem, VerifyItem verifyItem) {
  1518     private static String reportRow(SignItem signItem, VerifyItem verifyItem) {
  1138         List<String> values = new ArrayList<String>();
  1519         List<String> values = new ArrayList<>();
  1139         values.add(signItem.certInfo.toString());
  1520         Consumer<Function<SignItem, String>> s_values_add = f -> {
  1140         values.add(signItem.version);
  1521             values.add(String.join("<br/><br/>", signItem.toStringWithPrev(f)));
  1141         values.add(null2Default(signItem.signatureAlgorithm,
  1522         };
  1142                 signItem.extractedSignatureAlgorithm));
  1523         Consumer<Function<VerifyItem, String>> v_values_add = f -> {
  1143         values.add(signItem.tsaIndex == -1
  1524             values.add(String.join("<br/><br/>", verifyItem.toStringWithPrev(f)));
  1144                    ? ""
  1525         };
  1145                    : null2Default(signItem.tsaDigestAlgorithm,
  1526         s_values_add.accept(i -> i.unsignedJar + " -> " + i.signedJar);
  1146                         signItem.extractedTsaDigestAlgorithm));
  1527         s_values_add.accept(i -> i.certInfo.toString());
  1147         values.add(signItem.tsaIndex == -1 ? "" : signItem.tsaIndex + "");
  1528         s_values_add.accept(i -> i.jdkInfo.version);
  1148         values.add(HtmlHelper.anchorLink(
  1529         s_values_add.accept(i -> i.certInfo.expectedSigalg());
       
  1530         s_values_add.accept(i ->
       
  1531                 null2Default(i.digestAlgorithm, i.expectedDigestAlg()));
       
  1532         s_values_add.accept(i -> i.tsaIndex == -1 ? "" :
       
  1533                 null2Default(i.tsaDigestAlgorithm, i.expectedTsaDigestAlg()));
       
  1534         s_values_add.accept(i -> i.tsaIndex == -1 ? "" : i.tsaIndex + "");
       
  1535         s_values_add.accept(i -> HtmlHelper.anchorLink(
  1149                 PhaseOutputStream.fileName(PhaseOutputStream.Phase.SIGNING),
  1536                 PhaseOutputStream.fileName(PhaseOutputStream.Phase.SIGNING),
  1150                 signingId(signItem),
  1537                 signingId(i),
  1151                 signItem.status.toString()));
  1538                 "" + i.status));
  1152         values.add(verifyItem.jdkInfo.version);
  1539         values.add(verifyItem.jdkInfo.version);
  1153         values.add(HtmlHelper.anchorLink(
  1540         v_values_add.accept(i ->
       
  1541                 i.certInfo == null ? "no alias" : "" + i.certInfo);
       
  1542         v_values_add.accept(i -> HtmlHelper.anchorLink(
  1154                 PhaseOutputStream.fileName(PhaseOutputStream.Phase.VERIFYING),
  1543                 PhaseOutputStream.fileName(PhaseOutputStream.Phase.VERIFYING),
  1155                 verifyingId(signItem, verifyItem, false),
  1544                 verifyingId(signItem, i, false),
  1156                 verifyItem.status.toString()));
  1545                 "" + i.status.toString()));
  1157         if (DELAY_VERIFY) {
  1546         if (DELAY_VERIFY) {
  1158             values.add(HtmlHelper.anchorLink(
  1547             v_values_add.accept(i -> HtmlHelper.anchorLink(
  1159                     PhaseOutputStream.fileName(
  1548                     PhaseOutputStream.fileName(
  1160                             PhaseOutputStream.Phase.DELAY_VERIFYING),
  1549                             PhaseOutputStream.Phase.DELAY_VERIFYING),
  1161                     verifyingId(signItem, verifyItem, true),
  1550                     verifyingId(signItem, verifyItem, true),
  1162                     verifyItem.delayStatus.toString()));
  1551                     verifyItem.delayStatus.toString()));
  1163         }
  1552         }
  1164         values.add(isFailed(signItem, verifyItem) ? "X" : "");
  1553         values.add(isFailed(signItem, verifyItem) ? "X" : "");
  1165         return HtmlHelper.htmlRow(values.toArray(new String[values.size()]));
  1554         return HtmlHelper.htmlRow(values.toArray(new String[values.size()]));
  1166     }
  1555     }
  1167 
  1556 
  1168     private static boolean isFailed(SignItem signItem,
  1557     private static boolean isFailed(SignItem signItem, VerifyItem verifyItem) {
  1169             VerifyItem verifyItem) {
  1558         System.out.println("isFailed: signItem = " + signItem + ", verifyItem = " + verifyItem);
  1170         return signItem.status == Status.ERROR
  1559         // TODO: except known failing cases
  1171                 || verifyItem.status == Status.ERROR
  1560 
  1172                 || verifyItem.delayStatus == Status.ERROR;
  1561         // Note about isAtLeastMajorVersion in the following conditions:
       
  1562         // signItem.jdkInfo is the jdk which signed the jar last and
       
  1563         // signItem.prevSign.jdkInfo is the jdk which signed the jar first
       
  1564         // assuming only two successive signatures as there actually are now.
       
  1565         // the first signature always works and always has. subject here is
       
  1566         // the update of an already signed jar. the following conditions always
       
  1567         // depend on the second jdk that updated the jar with another signature
       
  1568         // and the first one (signItem(.prevSign)+.jdkInfo) can be ignored.
       
  1569         // this is different for verifyItem. verifyItem.prevVerify refers to
       
  1570         // the first signature created by signItem(.prevSign)+.jdkInfo.
       
  1571         // all verifyItem(.prevVerify)+.jdkInfo however point always to the same
       
  1572         // jdk, only their certInfo is different. the same signatures are
       
  1573         // verified with different jdks in different top-level VerifyItems
       
  1574         // attached directly to signItem.verifyItems and not to
       
  1575         // verifyItem.prevVerify.
       
  1576 
       
  1577         // ManifestDigester fails to parse manifests ending in '\r' with
       
  1578         // IndexOutOfBoundsException at ManifestDigester.java:87 before 8217375
       
  1579         if (signItem.signedJar.startsWith("eofr")
       
  1580                 && !signItem.jdkInfo.isAtLeastMajorVersion(13)
       
  1581                 && !verifyItem.jdkInfo.isAtLeastMajorVersion(13)) return false;
       
  1582 
       
  1583         // if there is no blank line after main attributes, JarSigner adds
       
  1584         // individual sections nevertheless without being properly delimited
       
  1585         // in JarSigner.java:777..790 without checking for blank line
       
  1586         // before 8217375
       
  1587 //        if (signItem.signedJar.startsWith("eofn-")
       
  1588 //                && signItem.signedJar.contains("-addfile-")
       
  1589 //                && !signItem.jdkInfo.isAtLeastMajorVersion(13)
       
  1590 //                && !verifyItem.jdkInfo.isAtLeastMajorVersion(13)) return false; // FIXME
       
  1591 
       
  1592 //        System.out.println("isFailed: signItem.isErrorInclPrev() " + signItem.isErrorInclPrev());
       
  1593 //        System.out.println("isFailed: verifyItem.isErrorInclPrev() " + verifyItem.isErrorInclPrev());
       
  1594         boolean isFailed = signItem.isErrorInclPrev() || verifyItem.isErrorInclPrev();
       
  1595         System.out.println("isFailed: returning " + isFailed);
       
  1596         return isFailed;
  1173     }
  1597     }
  1174 
  1598 
  1175     // If a value is null, then displays the default value or N/A.
  1599     // If a value is null, then displays the default value or N/A.
  1176     private static String null2Default(String value, String defaultValue) {
  1600     private static String null2Default(String value, String defaultValue) {
  1177         return value == null
  1601         return value != null ? value :
  1178                ? DEFAULT + "(" + (defaultValue == null
  1602                DEFAULT + "(" + (defaultValue == null
  1179                                   ? "N/A"
  1603                                   ? "N/A"
  1180                                   : defaultValue) + ")"
  1604                                   : defaultValue) + ")";
  1181                : value;
  1605     }
  1182     }
  1606 
  1183 }
  1607 }