test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java
changeset 47216 71c04702a3d5
parent 46156 79e8a865c5b8
child 51058 44c355346475
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     1 /*
       
     2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 /*
       
    25  * @test
       
    26  * @summary This test is used to verify the compatibility on jarsigner cross
       
    27  *     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  *     algorithms.
       
    30  *     Note that, this is a manual test. For more details about the test and
       
    31  *     its usages, please look through README.
       
    32  *
       
    33  * @modules java.base/sun.security.pkcs
       
    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.6 -target 1.6 JdkUtils.java
       
    40  * @run main/manual/othervm Compatibility
       
    41  */
       
    42 
       
    43 import java.io.BufferedReader;
       
    44 import java.io.File;
       
    45 import java.io.FileReader;
       
    46 import java.io.FileWriter;
       
    47 import java.io.IOException;
       
    48 import java.io.PrintStream;
       
    49 import java.text.DateFormat;
       
    50 import java.text.SimpleDateFormat;
       
    51 import java.util.ArrayList;
       
    52 import java.util.Calendar;
       
    53 import java.util.Date;
       
    54 import java.util.HashMap;
       
    55 import java.util.HashSet;
       
    56 import java.util.List;
       
    57 import java.util.Map;
       
    58 import java.util.Set;
       
    59 import java.util.concurrent.TimeUnit;
       
    60 import java.util.regex.Matcher;
       
    61 import java.util.regex.Pattern;
       
    62 
       
    63 import jdk.test.lib.process.OutputAnalyzer;
       
    64 import jdk.test.lib.process.ProcessTools;
       
    65 import jdk.test.lib.util.JarUtils;
       
    66 
       
    67 public class Compatibility {
       
    68 
       
    69     private static final String TEST_JAR_NAME = "test.jar";
       
    70 
       
    71     private static final String TEST_SRC = System.getProperty("test.src");
       
    72     private static final String TEST_CLASSES = System.getProperty("test.classes");
       
    73     private static final String TEST_JDK = System.getProperty("test.jdk");
       
    74     private static final String TEST_JARSIGNER = jarsignerPath(TEST_JDK);
       
    75 
       
    76     private static final String PROXY_HOST = System.getProperty("proxyHost");
       
    77     private static final String PROXY_PORT = System.getProperty("proxyPort", "80");
       
    78 
       
    79     // An alternative security properties file.
       
    80     // The test provides a default one, which only contains two lines:
       
    81     // jdk.certpath.disabledAlgorithms=MD2, MD5
       
    82     // jdk.jar.disabledAlgorithms=MD2, MD5
       
    83     private static final String JAVA_SECURITY = System.getProperty(
       
    84             "javaSecurityFile", TEST_SRC + "/java.security");
       
    85 
       
    86     private static final String PASSWORD = "testpass";
       
    87     private static final String KEYSTORE = "testKeystore";
       
    88 
       
    89     private static final String RSA = "RSA";
       
    90     private static final String DSA = "DSA";
       
    91     private static final String EC = "EC";
       
    92     private static final String[] KEY_ALGORITHMS = new String[] {
       
    93             RSA,
       
    94             DSA,
       
    95             EC};
       
    96 
       
    97     private static final String SHA1 = "SHA-1";
       
    98     private static final String SHA256 = "SHA-256";
       
    99     private static final String SHA512 = "SHA-512";
       
   100     private static final String DEFAULT = "DEFAULT";
       
   101     private static final String[] DIGEST_ALGORITHMS = new String[] {
       
   102             SHA1,
       
   103             SHA256,
       
   104             SHA512,
       
   105             DEFAULT};
       
   106 
       
   107     private static final boolean[] EXPIRED = new boolean[] {
       
   108             false,
       
   109             true};
       
   110 
       
   111     private static final Calendar CALENDAR = Calendar.getInstance();
       
   112     private static final DateFormat DATE_FORMAT
       
   113             = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
       
   114 
       
   115     // The certificate validity period in minutes. The default value is 1440
       
   116     // minutes, namely 1 day.
       
   117     private static final int CERT_VALIDITY
       
   118             = Integer.valueOf(System.getProperty("certValidity", "1440"));
       
   119     static {
       
   120         if (CERT_VALIDITY < 1 || CERT_VALIDITY > 1440) {
       
   121             throw new RuntimeException(
       
   122                     "certValidity if out of range [1, 1440]: " + CERT_VALIDITY);
       
   123         }
       
   124     }
       
   125 
       
   126     // If true, an additional verifying will be triggered after all of
       
   127     // valid certificates expire. The default value is false.
       
   128     public static final boolean DELAY_VERIFY
       
   129             = Boolean.valueOf(System.getProperty("delayVerify", "false"));
       
   130 
       
   131     private static long lastCertStartTime;
       
   132 
       
   133     private static DetailsOutputStream detailsOutput;
       
   134 
       
   135     public static void main(String[] args) throws Throwable {
       
   136         // Backups stdout and stderr.
       
   137         PrintStream origStdOut = System.out;
       
   138         PrintStream origStdErr = System.err;
       
   139 
       
   140         detailsOutput = new DetailsOutputStream();
       
   141 
       
   142         // Redirects the system output to a custom one.
       
   143         PrintStream printStream = new PrintStream(detailsOutput);
       
   144         System.setOut(printStream);
       
   145         System.setErr(printStream);
       
   146 
       
   147         List<TsaInfo> tsaList = tsaInfoList();
       
   148         if (tsaList.size() == 0) {
       
   149             throw new RuntimeException("TSA service is mandatory.");
       
   150         }
       
   151 
       
   152         List<JdkInfo> jdkInfoList = jdkInfoList();
       
   153         List<CertInfo> certList = createCertificates(jdkInfoList);
       
   154         createJar();
       
   155         List<SignItem> signItems = test(jdkInfoList, tsaList, certList);
       
   156 
       
   157         boolean failed = generateReport(tsaList, signItems);
       
   158 
       
   159         // Restores the original stdout and stderr.
       
   160         System.setOut(origStdOut);
       
   161         System.setErr(origStdErr);
       
   162 
       
   163         if (failed) {
       
   164             throw new RuntimeException("At least one test case failed. "
       
   165                     + "Please check the failed row(s) in report.html "
       
   166                     + "or failedReport.html.");
       
   167         }
       
   168     }
       
   169 
       
   170     // Creates a jar file that contains an empty file.
       
   171     private static void createJar() throws IOException {
       
   172         String testFile = "test";
       
   173         new File(testFile).createNewFile();
       
   174         JarUtils.createJar(TEST_JAR_NAME, testFile);
       
   175     }
       
   176 
       
   177     // Creates a key store that includes a set of valid/expired certificates
       
   178     // with various algorithms.
       
   179     private static List<CertInfo> createCertificates(List<JdkInfo> jdkInfoList)
       
   180             throws Throwable {
       
   181         List<CertInfo> certList = new ArrayList<CertInfo>();
       
   182         Set<String> expiredCertFilter = new HashSet<String>();
       
   183 
       
   184         for(JdkInfo jdkInfo : jdkInfoList) {
       
   185             for(String keyAlgorithm : KEY_ALGORITHMS) {
       
   186                 for(String digestAlgorithm : DIGEST_ALGORITHMS) {
       
   187                     for(int keySize : keySizes(keyAlgorithm)) {
       
   188                         for(boolean expired : EXPIRED) {
       
   189                             // It creates only one expired certificate for one
       
   190                             // key algorithm.
       
   191                             if (expired
       
   192                                     && !expiredCertFilter.add(keyAlgorithm)) {
       
   193                                 continue;
       
   194                             }
       
   195 
       
   196                             CertInfo certInfo = new CertInfo(
       
   197                                     jdkInfo.version,
       
   198                                     keyAlgorithm,
       
   199                                     digestAlgorithm,
       
   200                                     keySize,
       
   201                                     expired);
       
   202                             if (!certList.contains(certInfo)) {
       
   203                                 String alias = createCertificate(
       
   204                                         jdkInfo.jdkPath, certInfo);
       
   205                                 if (alias != null) {
       
   206                                     certList.add(certInfo);
       
   207                                 }
       
   208                             }
       
   209                         }
       
   210                     }
       
   211                 }
       
   212             }
       
   213         }
       
   214 
       
   215         return certList;
       
   216     }
       
   217 
       
   218     // Creates/Updates a key store that adds a certificate with specific algorithm.
       
   219     private static String createCertificate(String jdkPath, CertInfo certInfo)
       
   220             throws Throwable {
       
   221         String alias = certInfo.alias();
       
   222 
       
   223         List<String> arguments = new ArrayList<String>();
       
   224         arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
       
   225         arguments.add("-v");
       
   226         arguments.add("-storetype");
       
   227         arguments.add("jks");
       
   228         arguments.add("-genkey");
       
   229         arguments.add("-keyalg");
       
   230         arguments.add(certInfo.keyAlgorithm);
       
   231         String sigalg = sigalg(certInfo.digestAlgorithm, certInfo.keyAlgorithm);
       
   232         if (sigalg != null) {
       
   233             arguments.add("-sigalg");
       
   234             arguments.add(sigalg);
       
   235         }
       
   236         if (certInfo.keySize != 0) {
       
   237             arguments.add("-keysize");
       
   238             arguments.add(certInfo.keySize + "");
       
   239         }
       
   240         arguments.add("-dname");
       
   241         arguments.add("CN=Test");
       
   242         arguments.add("-alias");
       
   243         arguments.add(alias);
       
   244         arguments.add("-keypass");
       
   245         arguments.add(PASSWORD);
       
   246         arguments.add("-storepass");
       
   247         arguments.add(PASSWORD);
       
   248 
       
   249         arguments.add("-startdate");
       
   250         arguments.add(startDate(certInfo.expired));
       
   251         arguments.add("-validity");
       
   252         arguments.add("1");
       
   253         arguments.add("-keystore");
       
   254         arguments.add(KEYSTORE);
       
   255 
       
   256         OutputAnalyzer outputAnalyzer = execTool(
       
   257                 jdkPath + "/bin/keytool",
       
   258                 arguments.toArray(new String[arguments.size()]));
       
   259         if (outputAnalyzer.getExitValue() == 0
       
   260                 && !outputAnalyzer.getOutput().matches("[Ee]xception")) {
       
   261             return alias;
       
   262         } else {
       
   263             return null;
       
   264         }
       
   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     }
       
   275 
       
   276     // 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
       
   278     // certificate expires immediately. And for creating a valid certificate,
       
   279     // the start date is the time before (1 day - CERT_VALIDITY minutes), then
       
   280     // the certificate will expires in CERT_VALIDITY minutes.
       
   281     private static String startDate(boolean expiredCert) {
       
   282         CALENDAR.setTime(new Date());
       
   283         CALENDAR.add(Calendar.DAY_OF_MONTH, -1);
       
   284         if (!expiredCert) {
       
   285             CALENDAR.add(Calendar.MINUTE, CERT_VALIDITY);
       
   286         }
       
   287         Date startDate = CALENDAR.getTime();
       
   288         lastCertStartTime = startDate.getTime();
       
   289         return DATE_FORMAT.format(startDate);
       
   290     }
       
   291 
       
   292     // Retrieves JDK info from the file which is specified by property jdkListFile,
       
   293     // or from property jdkList if jdkListFile is not available.
       
   294     private static List<JdkInfo> jdkInfoList() throws Throwable {
       
   295         String[] jdkList = list("jdkList");
       
   296         if (jdkList.length == 0) {
       
   297             jdkList = new String[] { TEST_JDK };
       
   298         }
       
   299 
       
   300         List<JdkInfo> jdkInfoList = new ArrayList<JdkInfo>();
       
   301         for (String jdkPath : jdkList) {
       
   302             JdkInfo jdkInfo = new JdkInfo(jdkPath);
       
   303             // The JDK version must be unique.
       
   304             if (!jdkInfoList.contains(jdkInfo)) {
       
   305                 jdkInfoList.add(jdkInfo);
       
   306             } else {
       
   307                 System.out.println("The JDK version is duplicate: " + jdkPath);
       
   308             }
       
   309         }
       
   310         return jdkInfoList;
       
   311     }
       
   312 
       
   313     // Retrieves TSA info from the file which is specified by property tsaListFile,
       
   314     // or from property tsaList if tsaListFile is not available.
       
   315     private static List<TsaInfo> tsaInfoList() throws IOException {
       
   316         String[] tsaList = list("tsaList");
       
   317 
       
   318         List<TsaInfo> tsaInfoList = new ArrayList<TsaInfo>();
       
   319         for (int i = 0; i < tsaList.length; i++) {
       
   320             String[] values = tsaList[i].split(";digests=");
       
   321 
       
   322             String[] digests = new String[0];
       
   323             if (values.length == 2) {
       
   324                 digests = values[1].split(",");
       
   325             }
       
   326 
       
   327             TsaInfo bufTsa = new TsaInfo(i, values[0]);
       
   328 
       
   329             for (String digest : digests) {
       
   330                 bufTsa.addDigest(digest);
       
   331             }
       
   332 
       
   333             tsaInfoList.add(bufTsa);
       
   334         }
       
   335 
       
   336         return tsaInfoList;
       
   337     }
       
   338 
       
   339     private static String[] list(String listProp)
       
   340             throws IOException {
       
   341         String listFileProp = listProp + "File";
       
   342         String listFile = System.getProperty(listFileProp);
       
   343         if (!isEmpty(listFile)) {
       
   344             System.out.println(listFileProp + "=" + listFile);
       
   345             List<String> list = new ArrayList<String>();
       
   346             BufferedReader reader = new BufferedReader(
       
   347                     new FileReader(listFile));
       
   348             String line;
       
   349             while ((line = reader.readLine()) != null) {
       
   350                 String item = line.trim();
       
   351                 if (!item.isEmpty()) {
       
   352                     list.add(item);
       
   353                 }
       
   354             }
       
   355             reader.close();
       
   356             return list.toArray(new String[list.size()]);
       
   357         }
       
   358 
       
   359         String list = System.getProperty(listProp);
       
   360         System.out.println(listProp + "=" + list);
       
   361         return !isEmpty(list) ? list.split("#") : new String[0];
       
   362     }
       
   363 
       
   364     private static boolean isEmpty(String str) {
       
   365         return str == null || str.isEmpty();
       
   366     }
       
   367 
       
   368     // 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
       
   370     // jars respectively.
       
   371     private static List<SignItem> test(List<JdkInfo> jdkInfoList,
       
   372             List<TsaInfo> tsaInfoList, List<CertInfo> certList)
       
   373             throws Throwable {
       
   374         detailsOutput.transferPhase();
       
   375         List<SignItem> signItems = signing(jdkInfoList, tsaInfoList, certList);
       
   376 
       
   377         detailsOutput.transferPhase();
       
   378         for (SignItem signItem : signItems) {
       
   379             for (JdkInfo verifierInfo : jdkInfoList) {
       
   380                 // JDK 6 doesn't support EC
       
   381                 if (!verifierInfo.isJdk6()
       
   382                         || signItem.certInfo.keyAlgorithm != EC) {
       
   383                     verifying(signItem, VerifyItem.build(verifierInfo));
       
   384                 }
       
   385             }
       
   386         }
       
   387 
       
   388         if (DELAY_VERIFY) {
       
   389             detailsOutput.transferPhase();
       
   390             System.out.print("Waiting for delay verifying");
       
   391             long lastCertExpirationTime = lastCertStartTime + 24 * 60 * 60 * 1000;
       
   392             while (System.currentTimeMillis() < lastCertExpirationTime) {
       
   393                 TimeUnit.SECONDS.sleep(30);
       
   394                 System.out.print(".");
       
   395             }
       
   396             System.out.println();
       
   397 
       
   398             System.out.println("Delay verifying starts");
       
   399             for (SignItem signItem : signItems) {
       
   400                 for (VerifyItem verifyItem : signItem.verifyItems) {
       
   401                     verifying(signItem, verifyItem);
       
   402                 }
       
   403             }
       
   404         }
       
   405 
       
   406         detailsOutput.transferPhase();
       
   407 
       
   408         return signItems;
       
   409     }
       
   410 
       
   411     private static List<SignItem> signing(List<JdkInfo> jdkInfos,
       
   412             List<TsaInfo> tsaList, List<CertInfo> certList) throws Throwable {
       
   413         List<SignItem> signItems = new ArrayList<SignItem>();
       
   414 
       
   415         Set<String> signFilter = new HashSet<String>();
       
   416 
       
   417         for (JdkInfo signerInfo : jdkInfos) {
       
   418             for (String keyAlgorithm : KEY_ALGORITHMS) {
       
   419                 // JDK 6 doesn't support EC
       
   420                 if (signerInfo.isJdk6() && keyAlgorithm == EC) {
       
   421                     continue;
       
   422                 }
       
   423 
       
   424                 for (String digestAlgorithm : DIGEST_ALGORITHMS) {
       
   425                     String sigalg = sigalg(digestAlgorithm, keyAlgorithm);
       
   426                     // If the signature algorithm is not supported by the JDK,
       
   427                     // it cannot try to sign jar with this algorithm.
       
   428                     if (sigalg != null && !signerInfo.isSupportedSigalg(sigalg)) {
       
   429                         continue;
       
   430                     }
       
   431 
       
   432                     // If the JDK doesn't support option -tsadigestalg, the
       
   433                     // associated cases just be ignored.
       
   434                     if (digestAlgorithm != DEFAULT
       
   435                             && !signerInfo.supportsTsadigestalg) {
       
   436                         continue;
       
   437                     }
       
   438 
       
   439                     for (int keySize : keySizes(keyAlgorithm)) {
       
   440                         for (boolean expired : EXPIRED) {
       
   441                             CertInfo certInfo = new CertInfo(
       
   442                                     signerInfo.version,
       
   443                                     keyAlgorithm,
       
   444                                     digestAlgorithm,
       
   445                                     keySize,
       
   446                                     expired);
       
   447                             if (!certList.contains(certInfo)) {
       
   448                                 continue;
       
   449                             }
       
   450 
       
   451                             String tsadigestalg = digestAlgorithm != DEFAULT
       
   452                                                 ? digestAlgorithm
       
   453                                                 : null;
       
   454 
       
   455                             for (TsaInfo tsaInfo : tsaList) {
       
   456                                 // It has to ignore the digest algorithm, which
       
   457                                 // is not supported by the TSA server.
       
   458                                 if(!tsaInfo.isDigestSupported(tsadigestalg)) {
       
   459                                     continue;
       
   460                                 }
       
   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                             }
       
   529                         }
       
   530                     }
       
   531                 }
       
   532             }
       
   533         }
       
   534 
       
   535         return signItems;
       
   536     }
       
   537 
       
   538     private static void verifying(SignItem signItem, VerifyItem verifyItem)
       
   539             throws Throwable {
       
   540         boolean delayVerify = verifyItem.status == Status.NONE;
       
   541         String verifyingId = verifyingId(signItem, verifyItem, !delayVerify);
       
   542         detailsOutput.writeAnchorName(verifyingId, "Verifying: " + verifyingId);
       
   543 
       
   544         OutputAnalyzer verifyOA = verifyJar(verifyItem.jdkInfo.jarsignerPath,
       
   545                 signItem.signedJar);
       
   546         Status verifyingStatus = verifyingStatus(verifyOA);
       
   547 
       
   548         // It checks if the default timestamp digest algorithm is SHA-256.
       
   549         if (verifyingStatus != Status.ERROR
       
   550                 && signItem.tsaDigestAlgorithm == null) {
       
   551             verifyingStatus = signItem.extractedTsaDigestAlgorithm != null
       
   552                                     && !signItem.extractedTsaDigestAlgorithm.matches("SHA-?256")
       
   553                             ? Status.ERROR
       
   554                             : verifyingStatus;
       
   555             if (verifyingStatus == Status.ERROR) {
       
   556                 System.out.println("The default tsa digest is not SHA-256: "
       
   557                     + signItem.extractedTsaDigestAlgorithm);
       
   558             }
       
   559         }
       
   560 
       
   561         if (delayVerify) {
       
   562             signItem.addVerifyItem(verifyItem.status(verifyingStatus));
       
   563         } else {
       
   564             verifyItem.delayStatus(verifyingStatus);
       
   565         }
       
   566     }
       
   567 
       
   568     // Return key sizes according to the specified key algorithm.
       
   569     private static int[] keySizes(String keyAlgorithm) {
       
   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     }
       
   578 
       
   579     // Determines the status of signing.
       
   580     private static Status signingStatus(OutputAnalyzer outputAnalyzer) {
       
   581         if (outputAnalyzer.getExitValue() == 0) {
       
   582             if (outputAnalyzer.getOutput().contains(Test.WARNING)) {
       
   583                 return Status.WARNING;
       
   584             } else {
       
   585                 return Status.NORMAL;
       
   586             }
       
   587         } else {
       
   588             return Status.ERROR;
       
   589         }
       
   590     }
       
   591 
       
   592     // Determines the status of verifying.
       
   593     private static Status verifyingStatus(OutputAnalyzer outputAnalyzer) {
       
   594         if (outputAnalyzer.getExitValue() == 0) {
       
   595             String output = outputAnalyzer.getOutput();
       
   596             if (!output.contains(Test.JAR_VERIFIED)) {
       
   597                 return Status.ERROR;
       
   598             } else if (output.contains(Test.WARNING)) {
       
   599                 return Status.WARNING;
       
   600             } else {
       
   601                 return Status.NORMAL;
       
   602             }
       
   603         } else {
       
   604             return Status.ERROR;
       
   605         }
       
   606     }
       
   607 
       
   608     // Extracts string from text by specified patterns.
       
   609     private static String extract(String text, String linePattern,
       
   610             String replacePattern) {
       
   611         Matcher lineMatcher = Pattern.compile(linePattern).matcher(text);
       
   612         if (lineMatcher.find()) {
       
   613             String line = lineMatcher.group(0);
       
   614             return line.replaceAll(replacePattern, "");
       
   615         } else {
       
   616             return null;
       
   617         }
       
   618     }
       
   619 
       
   620     // Using specified jarsigner to sign the pre-created jar with specified
       
   621     // algorithms.
       
   622     private static OutputAnalyzer signJar(String jarsignerPath, String sigalg,
       
   623             String tsadigestalg, String tsa, String alias, String signedJar)
       
   624             throws Throwable {
       
   625         List<String> arguments = new ArrayList<String>();
       
   626 
       
   627         if (PROXY_HOST != null && PROXY_PORT != null) {
       
   628             arguments.add("-J-Dhttp.proxyHost=" + PROXY_HOST);
       
   629             arguments.add("-J-Dhttp.proxyPort=" + PROXY_PORT);
       
   630             arguments.add("-J-Dhttps.proxyHost=" + PROXY_HOST);
       
   631             arguments.add("-J-Dhttps.proxyPort=" + PROXY_PORT);
       
   632         }
       
   633         arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
       
   634         arguments.add("-debug");
       
   635         arguments.add("-verbose");
       
   636         if (sigalg != null) {
       
   637             arguments.add("-sigalg");
       
   638             arguments.add(sigalg);
       
   639         }
       
   640         if (tsa != null) {
       
   641             arguments.add("-tsa");
       
   642             arguments.add(tsa);
       
   643         }
       
   644         if (tsadigestalg != null) {
       
   645             arguments.add("-tsadigestalg");
       
   646             arguments.add(tsadigestalg);
       
   647         }
       
   648         arguments.add("-keystore");
       
   649         arguments.add(KEYSTORE);
       
   650         arguments.add("-storepass");
       
   651         arguments.add(PASSWORD);
       
   652         arguments.add("-signedjar");
       
   653         arguments.add(signedJar + ".jar");
       
   654         arguments.add(TEST_JAR_NAME);
       
   655         arguments.add(alias);
       
   656 
       
   657         OutputAnalyzer outputAnalyzer = execTool(
       
   658                 jarsignerPath,
       
   659                 arguments.toArray(new String[arguments.size()]));
       
   660         return outputAnalyzer;
       
   661     }
       
   662 
       
   663     // Using specified jarsigner to verify the signed jar.
       
   664     private static OutputAnalyzer verifyJar(String jarsignerPath,
       
   665             String signedJar) throws Throwable {
       
   666         OutputAnalyzer outputAnalyzer = execTool(
       
   667                 jarsignerPath,
       
   668                 "-J-Djava.security.properties=" + JAVA_SECURITY,
       
   669                 "-debug",
       
   670                 "-verbose",
       
   671                 "-certs",
       
   672                 "-keystore", KEYSTORE,
       
   673                 "-verify", signedJar + ".jar");
       
   674         return outputAnalyzer;
       
   675     }
       
   676 
       
   677     // Generates the test result report.
       
   678     private static boolean generateReport(List<TsaInfo> tsaList,
       
   679             List<SignItem> signItems) throws IOException {
       
   680         System.out.println("Report is being generated...");
       
   681 
       
   682         StringBuilder report = new StringBuilder();
       
   683         report.append(HtmlHelper.startHtml());
       
   684         report.append(HtmlHelper.startPre());
       
   685         // Generates TSA URLs
       
   686         report.append("TSA list:\n");
       
   687         for(TsaInfo tsaInfo : tsaList) {
       
   688             report.append(
       
   689                     String.format("%d=%s%n", tsaInfo.index, tsaInfo.tsaUrl));
       
   690         }
       
   691         report.append(HtmlHelper.endPre());
       
   692 
       
   693         report.append(HtmlHelper.startTable());
       
   694         // Generates report headers.
       
   695         List<String> headers = new ArrayList<String>();
       
   696         headers.add("[Certificate]");
       
   697         headers.add("[Signer JDK]");
       
   698         headers.add("[Signature Algorithm]");
       
   699         headers.add("[TSA Digest]");
       
   700         headers.add("[TSA]");
       
   701         headers.add("[Signing Status]");
       
   702         headers.add("[Verifier JDK]");
       
   703         headers.add("[Verifying Status]");
       
   704         if (DELAY_VERIFY) {
       
   705             headers.add("[Delay Verifying Status]");
       
   706         }
       
   707         headers.add("[Failed]");
       
   708         report.append(HtmlHelper.htmlRow(
       
   709                 headers.toArray(new String[headers.size()])));
       
   710 
       
   711         StringBuilder failedReport = new StringBuilder(report.toString());
       
   712 
       
   713         boolean failed = false;
       
   714 
       
   715         // Generates report rows.
       
   716         for (SignItem signItem : signItems) {
       
   717             for (VerifyItem verifyItem : signItem.verifyItems) {
       
   718                 String reportRow = reportRow(signItem, verifyItem);
       
   719                 report.append(reportRow);
       
   720                 boolean isFailedCase = isFailed(signItem, verifyItem);
       
   721                 if (isFailedCase) {
       
   722                     failedReport.append(reportRow);
       
   723                 }
       
   724                 failed = failed || isFailedCase;
       
   725             }
       
   726         }
       
   727 
       
   728         report.append(HtmlHelper.endTable());
       
   729         report.append(HtmlHelper.endHtml());
       
   730         generateFile("report.html", report.toString());
       
   731         if (failed) {
       
   732             failedReport.append(HtmlHelper.endTable());
       
   733             failedReport.append(HtmlHelper.endPre());
       
   734             failedReport.append(HtmlHelper.endHtml());
       
   735             generateFile("failedReport.html", failedReport.toString());
       
   736         }
       
   737 
       
   738         System.out.println("Report is generated.");
       
   739         return failed;
       
   740     }
       
   741 
       
   742     private static void generateFile(String path, String content)
       
   743             throws IOException {
       
   744         FileWriter writer = new FileWriter(new File(path));
       
   745         writer.write(content);
       
   746         writer.close();
       
   747     }
       
   748 
       
   749     private static String jarsignerPath(String jdkPath) {
       
   750         return jdkPath + "/bin/jarsigner";
       
   751     }
       
   752 
       
   753     // Executes the specified function on JdkUtils by the specified JDK.
       
   754     private static String execJdkUtils(String jdkPath, String method,
       
   755             String... args) throws Throwable {
       
   756         String[] cmd = new String[args.length + 5];
       
   757         cmd[0] = jdkPath + "/bin/java";
       
   758         cmd[1] = "-cp";
       
   759         cmd[2] = TEST_CLASSES;
       
   760         cmd[3] = JdkUtils.class.getName();
       
   761         cmd[4] = method;
       
   762         System.arraycopy(args, 0, cmd, 5, args.length);
       
   763         return ProcessTools.executeCommand(cmd).getOutput();
       
   764     }
       
   765 
       
   766     // Executes the specified JDK tools, such as keytool and jarsigner, and
       
   767     // ensures the output is in US English.
       
   768     private static OutputAnalyzer execTool(String toolPath, String... args)
       
   769             throws Throwable {
       
   770         String[] cmd = new String[args.length + 4];
       
   771         cmd[0] = toolPath;
       
   772         cmd[1] = "-J-Duser.language=en";
       
   773         cmd[2] = "-J-Duser.country=US";
       
   774         cmd[3] = "-J-Djava.security.egd=file:/dev/./urandom";
       
   775         System.arraycopy(args, 0, cmd, 4, args.length);
       
   776         return ProcessTools.executeCommand(cmd);
       
   777     }
       
   778 
       
   779     private static class JdkInfo {
       
   780 
       
   781         private final String jdkPath;
       
   782         private final String jarsignerPath;
       
   783         private final String version;
       
   784         private final boolean supportsTsadigestalg;
       
   785 
       
   786         private Map<String, Boolean> sigalgMap = new HashMap<String, Boolean>();
       
   787 
       
   788         private JdkInfo(String jdkPath) throws Throwable {
       
   789             this.jdkPath = jdkPath;
       
   790             version = execJdkUtils(jdkPath, JdkUtils.M_JAVA_RUNTIME_VERSION);
       
   791             if (version == null || version.trim().isEmpty()) {
       
   792                 throw new RuntimeException(
       
   793                         "Cannot determine the JDK version: " + jdkPath);
       
   794             }
       
   795             jarsignerPath = jarsignerPath(jdkPath);
       
   796             supportsTsadigestalg = execTool(jarsignerPath, "-help")
       
   797                     .getOutput().contains("-tsadigestalg");
       
   798         }
       
   799 
       
   800         private boolean isSupportedSigalg(String sigalg) throws Throwable {
       
   801             if (!sigalgMap.containsKey(sigalg)) {
       
   802                 boolean isSupported = "true".equalsIgnoreCase(
       
   803                         execJdkUtils(
       
   804                                 jdkPath,
       
   805                                 JdkUtils.M_IS_SUPPORTED_SIGALG,
       
   806                                 sigalg));
       
   807                 sigalgMap.put(sigalg, isSupported);
       
   808             }
       
   809 
       
   810             return sigalgMap.get(sigalg);
       
   811         }
       
   812 
       
   813         private boolean isJdk6() {
       
   814             return version.startsWith("1.6");
       
   815         }
       
   816 
       
   817         @Override
       
   818         public int hashCode() {
       
   819             final int prime = 31;
       
   820             int result = 1;
       
   821             result = prime * result
       
   822                     + ((version == null) ? 0 : version.hashCode());
       
   823             return result;
       
   824         }
       
   825 
       
   826         @Override
       
   827         public boolean equals(Object obj) {
       
   828             if (this == obj)
       
   829                 return true;
       
   830             if (obj == null)
       
   831                 return false;
       
   832             if (getClass() != obj.getClass())
       
   833                 return false;
       
   834             JdkInfo other = (JdkInfo) obj;
       
   835             if (version == null) {
       
   836                 if (other.version != null)
       
   837                     return false;
       
   838             } else if (!version.equals(other.version))
       
   839                 return false;
       
   840             return true;
       
   841         }
       
   842     }
       
   843 
       
   844     private static class TsaInfo {
       
   845 
       
   846         private final int index;
       
   847         private final String tsaUrl;
       
   848         private Set<String> digestList = new HashSet<String>();
       
   849 
       
   850         private TsaInfo(int index, String tsa) {
       
   851             this.index = index;
       
   852             this.tsaUrl = tsa;
       
   853         }
       
   854 
       
   855         private void addDigest(String digest) {
       
   856             if (!ignore(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         }
       
   866 
       
   867         private boolean isDigestSupported(String digest) {
       
   868             return digest == null || digestList.isEmpty()
       
   869                     || digestList.contains(digest);
       
   870         }
       
   871     }
       
   872 
       
   873     private static class CertInfo {
       
   874 
       
   875         private final String jdkVersion;
       
   876         private final String keyAlgorithm;
       
   877         private final String digestAlgorithm;
       
   878         private final int keySize;
       
   879         private final boolean expired;
       
   880 
       
   881         private CertInfo(String jdkVersion, String keyAlgorithm,
       
   882                 String digestAlgorithm, int keySize, boolean expired) {
       
   883             this.jdkVersion = jdkVersion;
       
   884             this.keyAlgorithm = keyAlgorithm;
       
   885             this.digestAlgorithm = digestAlgorithm;
       
   886             this.keySize = keySize;
       
   887             this.expired = expired;
       
   888         }
       
   889 
       
   890         @Override
       
   891         public int hashCode() {
       
   892             final int prime = 31;
       
   893             int result = 1;
       
   894             result = prime * result
       
   895                     + ((digestAlgorithm == null) ? 0 : digestAlgorithm.hashCode());
       
   896             result = prime * result + (expired ? 1231 : 1237);
       
   897             result = prime * result
       
   898                     + ((jdkVersion == null) ? 0 : jdkVersion.hashCode());
       
   899             result = prime * result
       
   900                     + ((keyAlgorithm == null) ? 0 : keyAlgorithm.hashCode());
       
   901             result = prime * result + keySize;
       
   902             return result;
       
   903         }
       
   904 
       
   905         @Override
       
   906         public boolean equals(Object obj) {
       
   907             if (this == obj)
       
   908                 return true;
       
   909             if (obj == null)
       
   910                 return false;
       
   911             if (getClass() != obj.getClass())
       
   912                 return false;
       
   913             CertInfo other = (CertInfo) obj;
       
   914             if (digestAlgorithm == null) {
       
   915                 if (other.digestAlgorithm != null)
       
   916                     return false;
       
   917             } else if (!digestAlgorithm.equals(other.digestAlgorithm))
       
   918                 return false;
       
   919             if (expired != other.expired)
       
   920                 return false;
       
   921             if (jdkVersion == null) {
       
   922                 if (other.jdkVersion != null)
       
   923                     return false;
       
   924             } else if (!jdkVersion.equals(other.jdkVersion))
       
   925                 return false;
       
   926             if (keyAlgorithm == null) {
       
   927                 if (other.keyAlgorithm != null)
       
   928                     return false;
       
   929             } else if (!keyAlgorithm.equals(other.keyAlgorithm))
       
   930                 return false;
       
   931             if (keySize != other.keySize)
       
   932                 return false;
       
   933             return true;
       
   934         }
       
   935 
       
   936         private String alias() {
       
   937             return jdkVersion + "_" + toString();
       
   938         }
       
   939 
       
   940         @Override
       
   941         public String toString() {
       
   942             return keyAlgorithm + "_" + digestAlgorithm
       
   943                     + (keySize == 0 ? "" : "_" + keySize)
       
   944                     + (expired ? "_Expired" : "");
       
   945         }
       
   946     }
       
   947 
       
   948     // It does only one timestamping for the same JDK, digest algorithm and
       
   949     // TSA service with an arbitrary valid/expired certificate.
       
   950     private static class TsaFilter {
       
   951 
       
   952         private static final Set<Condition> SET = new HashSet<Condition>();
       
   953 
       
   954         private static boolean filter(String signerVersion,
       
   955                 String digestAlgorithm, boolean expiredCert, int tsaIndex) {
       
   956             return !SET.add(new Condition(signerVersion, digestAlgorithm,
       
   957                     expiredCert, tsaIndex));
       
   958         }
       
   959 
       
   960         private static class Condition {
       
   961 
       
   962             private final String signerVersion;
       
   963             private final String digestAlgorithm;
       
   964             private final boolean expiredCert;
       
   965             private final int tsaIndex;
       
   966 
       
   967             private Condition(String signerVersion, String digestAlgorithm,
       
   968                     boolean expiredCert, int tsaIndex) {
       
   969                 this.signerVersion = signerVersion;
       
   970                 this.digestAlgorithm = digestAlgorithm;
       
   971                 this.expiredCert = expiredCert;
       
   972                 this.tsaIndex = tsaIndex;
       
   973             }
       
   974 
       
   975             @Override
       
   976             public int hashCode() {
       
   977                 final int prime = 31;
       
   978                 int result = 1;
       
   979                 result = prime * result
       
   980                         + ((digestAlgorithm == null) ? 0 : digestAlgorithm.hashCode());
       
   981                 result = prime * result + (expiredCert ? 1231 : 1237);
       
   982                 result = prime * result
       
   983                         + ((signerVersion == null) ? 0 : signerVersion.hashCode());
       
   984                 result = prime * result + tsaIndex;
       
   985                 return result;
       
   986             }
       
   987 
       
   988             @Override
       
   989             public boolean equals(Object obj) {
       
   990                 if (this == obj)
       
   991                     return true;
       
   992                 if (obj == null)
       
   993                     return false;
       
   994                 if (getClass() != obj.getClass())
       
   995                     return false;
       
   996                 Condition other = (Condition) obj;
       
   997                 if (digestAlgorithm == null) {
       
   998                     if (other.digestAlgorithm != null)
       
   999                         return false;
       
  1000                 } else if (!digestAlgorithm.equals(other.digestAlgorithm))
       
  1001                     return false;
       
  1002                 if (expiredCert != other.expiredCert)
       
  1003                     return false;
       
  1004                 if (signerVersion == null) {
       
  1005                     if (other.signerVersion != null)
       
  1006                         return false;
       
  1007                 } else if (!signerVersion.equals(other.signerVersion))
       
  1008                     return false;
       
  1009                 if (tsaIndex != other.tsaIndex)
       
  1010                     return false;
       
  1011                 return true;
       
  1012             }
       
  1013         }}
       
  1014 
       
  1015     private static enum Status {
       
  1016 
       
  1017         // No action due to pre-action fails.
       
  1018         NONE,
       
  1019 
       
  1020         // jar is signed/verified with error
       
  1021         ERROR,
       
  1022 
       
  1023         // jar is signed/verified with warning
       
  1024         WARNING,
       
  1025 
       
  1026         // jar is signed/verified without any warning and error
       
  1027         NORMAL
       
  1028     }
       
  1029 
       
  1030     private static class SignItem {
       
  1031 
       
  1032         private CertInfo certInfo;
       
  1033         private String version;
       
  1034         private String signatureAlgorithm;
       
  1035         // Signature algorithm that is extracted from verification output.
       
  1036         private String extractedSignatureAlgorithm;
       
  1037         private String tsaDigestAlgorithm;
       
  1038         // TSA digest algorithm that is extracted from verification output.
       
  1039         private String extractedTsaDigestAlgorithm;
       
  1040         private int tsaIndex;
       
  1041         private Status status;
       
  1042         private String signedJar;
       
  1043 
       
  1044         private List<VerifyItem> verifyItems = new ArrayList<VerifyItem>();
       
  1045 
       
  1046         private static SignItem build() {
       
  1047             return new SignItem();
       
  1048         }
       
  1049 
       
  1050         private SignItem certInfo(CertInfo certInfo) {
       
  1051             this.certInfo = certInfo;
       
  1052             return this;
       
  1053         }
       
  1054 
       
  1055         private SignItem version(String version) {
       
  1056             this.version = version;
       
  1057             return this;
       
  1058         }
       
  1059 
       
  1060         private SignItem signatureAlgorithm(String signatureAlgorithm) {
       
  1061             this.signatureAlgorithm = signatureAlgorithm;
       
  1062             return this;
       
  1063         }
       
  1064 
       
  1065         private SignItem extractedSignatureAlgorithm(
       
  1066                 String extractedSignatureAlgorithm) {
       
  1067             this.extractedSignatureAlgorithm = extractedSignatureAlgorithm;
       
  1068             return this;
       
  1069         }
       
  1070 
       
  1071         private SignItem tsaDigestAlgorithm(String tsaDigestAlgorithm) {
       
  1072             this.tsaDigestAlgorithm = tsaDigestAlgorithm;
       
  1073             return this;
       
  1074         }
       
  1075 
       
  1076         private SignItem extractedTsaDigestAlgorithm(
       
  1077                 String extractedTsaDigestAlgorithm) {
       
  1078             this.extractedTsaDigestAlgorithm = extractedTsaDigestAlgorithm;
       
  1079             return this;
       
  1080         }
       
  1081 
       
  1082         private SignItem tsaIndex(int tsaIndex) {
       
  1083             this.tsaIndex = tsaIndex;
       
  1084             return this;
       
  1085         }
       
  1086 
       
  1087         private SignItem status(Status status) {
       
  1088             this.status = status;
       
  1089             return this;
       
  1090         }
       
  1091 
       
  1092         private SignItem signedJar(String signedJar) {
       
  1093             this.signedJar = signedJar;
       
  1094             return this;
       
  1095         }
       
  1096 
       
  1097         private void addVerifyItem(VerifyItem verifyItem) {
       
  1098             verifyItems.add(verifyItem);
       
  1099         }
       
  1100     }
       
  1101 
       
  1102     private static class VerifyItem {
       
  1103 
       
  1104         private JdkInfo jdkInfo;
       
  1105         private Status status = Status.NONE;
       
  1106         private Status delayStatus = Status.NONE;
       
  1107 
       
  1108         private static VerifyItem build(JdkInfo jdkInfo) {
       
  1109             VerifyItem verifyItem = new VerifyItem();
       
  1110             verifyItem.jdkInfo = jdkInfo;
       
  1111             return verifyItem;
       
  1112         }
       
  1113 
       
  1114         private VerifyItem status(Status status) {
       
  1115             this.status = status;
       
  1116             return this;
       
  1117         }
       
  1118 
       
  1119         private VerifyItem delayStatus(Status status) {
       
  1120             this.delayStatus = status;
       
  1121             return this;
       
  1122         }
       
  1123     }
       
  1124 
       
  1125     // The identifier for a specific signing.
       
  1126     private static String signingId(SignItem signItem) {
       
  1127         return signItem.signedJar;
       
  1128     }
       
  1129 
       
  1130     // The identifier for a specific verifying.
       
  1131     private static String verifyingId(SignItem signItem, VerifyItem verifyItem,
       
  1132             boolean delayVerify) {
       
  1133         return "S_" + signingId(signItem) + "-" + (delayVerify ? "DV" : "V")
       
  1134                 + "_" + verifyItem.jdkInfo.version;
       
  1135     }
       
  1136 
       
  1137     private static String reportRow(SignItem signItem, VerifyItem verifyItem) {
       
  1138         List<String> values = new ArrayList<String>();
       
  1139         values.add(signItem.certInfo.toString());
       
  1140         values.add(signItem.version);
       
  1141         values.add(null2Default(signItem.signatureAlgorithm,
       
  1142                 signItem.extractedSignatureAlgorithm));
       
  1143         values.add(signItem.tsaIndex == -1
       
  1144                    ? ""
       
  1145                    : null2Default(signItem.tsaDigestAlgorithm,
       
  1146                         signItem.extractedTsaDigestAlgorithm));
       
  1147         values.add(signItem.tsaIndex == -1 ? "" : signItem.tsaIndex + "");
       
  1148         values.add(HtmlHelper.anchorLink(
       
  1149                 PhaseOutputStream.fileName(PhaseOutputStream.Phase.SIGNING),
       
  1150                 signingId(signItem),
       
  1151                 signItem.status.toString()));
       
  1152         values.add(verifyItem.jdkInfo.version);
       
  1153         values.add(HtmlHelper.anchorLink(
       
  1154                 PhaseOutputStream.fileName(PhaseOutputStream.Phase.VERIFYING),
       
  1155                 verifyingId(signItem, verifyItem, false),
       
  1156                 verifyItem.status.toString()));
       
  1157         if (DELAY_VERIFY) {
       
  1158             values.add(HtmlHelper.anchorLink(
       
  1159                     PhaseOutputStream.fileName(
       
  1160                             PhaseOutputStream.Phase.DELAY_VERIFYING),
       
  1161                     verifyingId(signItem, verifyItem, true),
       
  1162                     verifyItem.delayStatus.toString()));
       
  1163         }
       
  1164         values.add(isFailed(signItem, verifyItem) ? "X" : "");
       
  1165         return HtmlHelper.htmlRow(values.toArray(new String[values.size()]));
       
  1166     }
       
  1167 
       
  1168     private static boolean isFailed(SignItem signItem,
       
  1169             VerifyItem verifyItem) {
       
  1170         return signItem.status == Status.ERROR
       
  1171                 || verifyItem.status == Status.ERROR
       
  1172                 || verifyItem.delayStatus == Status.ERROR;
       
  1173     }
       
  1174 
       
  1175     // If a value is null, then displays the default value or N/A.
       
  1176     private static String null2Default(String value, String defaultValue) {
       
  1177         return value == null
       
  1178                ? DEFAULT + "(" + (defaultValue == null
       
  1179                                   ? "N/A"
       
  1180                                   : defaultValue) + ")"
       
  1181                : value;
       
  1182     }
       
  1183 }