8024302: Clarify jar verifications
authorweijun
Thu, 19 Sep 2013 10:40:16 +0800
changeset 22315 529f1cc322fc
parent 22314 06c989518cb5
child 22316 5668a8abf212
8024302: Clarify jar verifications 8023338: Update jarsigner to encourage timestamping Reviewed-by: mullan, ahgross
jdk/src/share/classes/sun/security/tools/jarsigner/Main.java
jdk/src/share/classes/sun/security/tools/jarsigner/Resources.java
jdk/test/sun/security/tools/jarsigner/TimestampCheck.java
jdk/test/sun/security/tools/jarsigner/concise_jarsigner.sh
jdk/test/sun/security/tools/jarsigner/ts.sh
jdk/test/sun/security/tools/jarsigner/warnings.sh
--- a/jdk/src/share/classes/sun/security/tools/jarsigner/Main.java	Thu Sep 19 08:34:37 2013 -0700
+++ b/jdk/src/share/classes/sun/security/tools/jarsigner/Main.java	Thu Sep 19 10:40:16 2013 +0800
@@ -158,8 +158,13 @@
     private String altSignerClasspath = null;
     private ZipFile zipFile = null;
 
+    // Informational warnings
+    private boolean hasExpiringCert = false;
+    private boolean noTimestamp = false;
+    private Date expireDate = new Date(0L);     // used in noTimestamp warning
+
+    // Severe warnings
     private boolean hasExpiredCert = false;
-    private boolean hasExpiringCert = false;
     private boolean notYetValidCert = false;
     private boolean chainNotValidated = false;
     private boolean notSignedByAlias = false;
@@ -258,9 +263,6 @@
 
         if (strict) {
             int exitCode = 0;
-            if (hasExpiringCert) {
-                exitCode |= 2;
-            }
             if (chainNotValidated || hasExpiredCert || notYetValidCert) {
                 exitCode |= 4;
             }
@@ -754,14 +756,25 @@
                 System.out.println(rb.getString(
                       "jar.is.unsigned.signatures.missing.or.not.parsable."));
             } else {
-                System.out.println(rb.getString("jar.verified."));
-                if (hasUnsignedEntry || hasExpiredCert || hasExpiringCert ||
-                    badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
-                    notYetValidCert || chainNotValidated ||
-                    aliasNotInStore || notSignedByAlias) {
+                boolean warningAppeared = false;
+                boolean errorAppeared = false;
+                if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
+                        notYetValidCert || chainNotValidated || hasExpiredCert ||
+                        hasUnsignedEntry ||
+                        aliasNotInStore || notSignedByAlias) {
 
-                    System.out.println();
-                    System.out.println(rb.getString("Warning."));
+                    if (strict) {
+                        System.out.println(rb.getString("jar.verified.with.signer.errors."));
+                        System.out.println();
+                        System.out.println(rb.getString("Error."));
+                        errorAppeared = true;
+                    } else {
+                        System.out.println(rb.getString("jar.verified."));
+                        System.out.println();
+                        System.out.println(rb.getString("Warning."));
+                        warningAppeared = true;
+                    }
+
                     if (badKeyUsage) {
                         System.out.println(
                             rb.getString("This.jar.contains.entries.whose.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."));
@@ -785,10 +798,6 @@
                         System.out.println(rb.getString(
                             "This.jar.contains.entries.whose.signer.certificate.has.expired."));
                     }
-                    if (hasExpiringCert) {
-                        System.out.println(rb.getString(
-                            "This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
-                    }
                     if (notYetValidCert) {
                         System.out.println(rb.getString(
                             "This.jar.contains.entries.whose.signer.certificate.is.not.yet.valid."));
@@ -807,10 +816,29 @@
                     if (aliasNotInStore) {
                         System.out.println(rb.getString("This.jar.contains.signed.entries.that.s.not.signed.by.alias.in.this.keystore."));
                     }
+                } else {
+                    System.out.println(rb.getString("jar.verified."));
+                }
+                if (hasExpiringCert || noTimestamp) {
+                    if (!warningAppeared) {
+                        System.out.println();
+                        System.out.println(rb.getString("Warning."));
+                        warningAppeared = true;
+                    }
+                    if (hasExpiringCert) {
+                        System.out.println(rb.getString(
+                                "This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
+                    }
+                    if (noTimestamp) {
+                        System.out.println(
+                                String.format(rb.getString("no.timestamp.verifying"), expireDate));
+                    }
+                }
+                if (warningAppeared || errorAppeared) {
                     if (! (verbose != null && showcerts)) {
                         System.out.println();
                         System.out.println(rb.getString(
-                            "Re.run.with.the.verbose.and.certs.options.for.more.details."));
+                                "Re.run.with.the.verbose.and.certs.options.for.more.details."));
                     }
                 }
             }
@@ -870,6 +898,9 @@
             try {
                 boolean printValidity = true;
                 if (timestamp == null) {
+                    if (expireDate.getTime() == 0 || expireDate.after(notAfter)) {
+                        expireDate = notAfter;
+                    }
                     x509Cert.checkValidity();
                     // test if cert will expire within six months
                     if (notAfter.getTime() < System.currentTimeMillis() + SIX_MONTHS) {
@@ -1233,6 +1264,10 @@
                 tsaCert = getTsaCert(tsaAlias);
             }
 
+            if (tsaUrl == null && tsaCert == null) {
+                noTimestamp = true;
+            }
+
             SignatureFile.Block block = null;
 
             try {
@@ -1380,12 +1415,20 @@
                 }
             }
 
-            if (hasExpiredCert || hasExpiringCert || notYetValidCert
-                    || badKeyUsage || badExtendedKeyUsage
-                    || badNetscapeCertType || chainNotValidated) {
-                System.out.println();
+            boolean warningAppeared = false;
+            if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
+                    notYetValidCert || chainNotValidated || hasExpiredCert) {
+                if (strict) {
+                    System.out.println(rb.getString("jar.signed.with.signer.errors."));
+                    System.out.println();
+                    System.out.println(rb.getString("Error."));
+                } else {
+                    System.out.println(rb.getString("jar.signed."));
+                    System.out.println();
+                    System.out.println(rb.getString("Warning."));
+                    warningAppeared = true;
+                }
 
-                System.out.println(rb.getString("Warning."));
                 if (badKeyUsage) {
                     System.out.println(
                         rb.getString("The.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."));
@@ -1404,9 +1447,6 @@
                 if (hasExpiredCert) {
                     System.out.println(
                         rb.getString("The.signer.certificate.has.expired."));
-                } else if (hasExpiringCert) {
-                    System.out.println(
-                        rb.getString("The.signer.certificate.will.expire.within.six.months."));
                 } else if (notYetValidCert) {
                     System.out.println(
                         rb.getString("The.signer.certificate.is.not.yet.valid."));
@@ -1416,6 +1456,24 @@
                     System.out.println(
                             rb.getString("The.signer.s.certificate.chain.is.not.validated."));
                 }
+            } else {
+                System.out.println(rb.getString("jar.signed."));
+            }
+            if (hasExpiringCert || noTimestamp) {
+                if (!warningAppeared) {
+                    System.out.println();
+                    System.out.println(rb.getString("Warning."));
+                }
+
+                if (hasExpiringCert) {
+                    System.out.println(
+                            rb.getString("The.signer.certificate.will.expire.within.six.months."));
+                }
+
+                if (noTimestamp) {
+                    System.out.println(
+                            String.format(rb.getString("no.timestamp.signing"), expireDate));
+                }
             }
 
         // no IOException thrown in the above try clause, so disable
@@ -1502,6 +1560,7 @@
             timestamp = ts.getTimestamp();
         } else {
             timestamp = null;
+            noTimestamp = true;
         }
         // display the certificate(s). The first one is end-entity cert and
         // its KeyUsage should be checked.
--- a/jdk/src/share/classes/sun/security/tools/jarsigner/Resources.java	Thu Sep 19 08:34:37 2013 -0700
+++ b/jdk/src/share/classes/sun/security/tools/jarsigner/Resources.java	Thu Sep 19 10:40:16 2013 +0800
@@ -135,7 +135,10 @@
         {".Unsigned.entries.", "(Unsigned entries)"},
         {"jar.is.unsigned.signatures.missing.or.not.parsable.",
                 "jar is unsigned. (signatures missing or not parsable)"},
+        {"jar.signed.", "jar signed."},
+        {"jar.signed.with.signer.errors.", "jar signed, with signer errors."},
         {"jar.verified.", "jar verified."},
+        {"jar.verified.with.signer.errors.", "jar verified, with signer errors."},
         {"jarsigner.", "jarsigner: "},
         {"signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or.",
                 "signature filename must consist of the following characters: A-Z, 0-9, _ or -"},
@@ -193,6 +196,7 @@
                 "using an alternative signing mechanism"},
         {"entry.was.signed.on", "entry was signed on {0}"},
         {"Warning.", "Warning: "},
+        {"Error.", "Error: "},
         {"This.jar.contains.unsigned.entries.which.have.not.been.integrity.checked.",
                 "This jar contains unsigned entries which have not been integrity-checked. "},
         {"This.jar.contains.entries.whose.signer.certificate.has.expired.",
@@ -229,6 +233,10 @@
                 "The signer's certificate chain is not validated."},
         {"This.jar.contains.entries.whose.certificate.chain.is.not.validated.",
                  "This jar contains entries whose certificate chain is not validated."},
+        {"no.timestamp.signing",
+                "No -tsa or -tsacert is provided and this jar is not timestamped. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td) or after any future revocation date."},
+        {"no.timestamp.verifying",
+                "This jar contains signatures that does not include a timestamp. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td) or after any future revocation date."},
         {"Unknown.password.type.", "Unknown password type: "},
         {"Cannot.find.environment.variable.",
                 "Cannot find environment variable: "},
--- a/jdk/test/sun/security/tools/jarsigner/TimestampCheck.java	Thu Sep 19 08:34:37 2013 -0700
+++ b/jdk/test/sun/security/tools/jarsigner/TimestampCheck.java	Thu Sep 19 10:40:16 2013 +0800
@@ -239,13 +239,13 @@
                 " -J-Djava.security.egd=file:/dev/./urandom" +
                 " -debug -keystore " + TSKS + " -storepass changeit" +
                 " -tsa http://localhost:" + port + "/%d" +
-                " -signedjar new.jar " + JAR + " old";
+                " -signedjar new_%d.jar " + JAR + " old";
         } else {
             cmd = System.getProperty("java.home") + "/bin/jarsigner" +
                 " -J-Djava.security.egd=file:/dev/./urandom" +
                 " -debug -keystore " + TSKS + " -storepass changeit" +
                 " -tsa http://localhost:" + port + "/%d" +
-                " -signedjar new.jar " + JAR + " old";
+                " -signedjar new_%d.jar " + JAR + " old";
         }
 
         try {
@@ -280,7 +280,7 @@
     static void jarsigner(String cmd, int path, boolean expected)
             throws Exception {
         System.err.println("Test " + path);
-        Process p = Runtime.getRuntime().exec(String.format(cmd, path));
+        Process p = Runtime.getRuntime().exec(String.format(cmd, path, path));
         BufferedReader reader = new BufferedReader(
                 new InputStreamReader(p.getErrorStream()));
         while (true) {
@@ -288,9 +288,25 @@
             if (s == null) break;
             System.err.println(s);
         }
+
+        // Will not see noTimestamp warning
+        boolean seeWarning = false;
+        reader = new BufferedReader(
+                new InputStreamReader(p.getInputStream()));
+        while (true) {
+            String s = reader.readLine();
+            if (s == null) break;
+            System.err.println(s);
+            if (s.indexOf("Warning:") >= 0) {
+                seeWarning = true;
+            }
+        }
         int result = p.waitFor();
         if (expected && result != 0 || !expected && result == 0) {
             throw new Exception("Failed");
         }
+        if (seeWarning) {
+            throw new Exception("See warning");
+        }
     }
 }
--- a/jdk/test/sun/security/tools/jarsigner/concise_jarsigner.sh	Thu Sep 19 08:34:37 2013 -0700
+++ b/jdk/test/sun/security/tools/jarsigner/concise_jarsigner.sh	Thu Sep 19 10:40:16 2013 +0800
@@ -139,7 +139,6 @@
 # 16 and 32 already covered in the first part
 # ==========================================================
 
-$KT -genkeypair -alias expiring -dname CN=expiring -startdate -1m
 $KT -genkeypair -alias expired -dname CN=expired -startdate -10m
 $KT -genkeypair -alias notyetvalid -dname CN=notyetvalid -startdate +1m
 $KT -genkeypair -alias badku -dname CN=badku -ext KU=cRLSign -validity 365
@@ -154,9 +153,6 @@
         $KT -importcert -alias badchain
 $KT -delete -alias ca
 
-$JARSIGNER -strict -keystore js.jks -storepass changeit a.jar expiring
-[ $? = 2 ] || exit $LINENO
-
 $JARSIGNER -strict -keystore js.jks -storepass changeit a.jar expired
 [ $? = 4 ] || exit $LINENO
 
--- a/jdk/test/sun/security/tools/jarsigner/ts.sh	Thu Sep 19 08:34:37 2013 -0700
+++ b/jdk/test/sun/security/tools/jarsigner/ts.sh	Thu Sep 19 10:40:16 2013 +0800
@@ -22,7 +22,7 @@
 #
 
 # @test
-# @bug 6543842 6543440 6939248 8009636
+# @bug 6543842 6543440 6939248 8009636 8024302
 # @summary checking response of timestamp
 #
 # @run shell/timeout=600 ts.sh
@@ -53,7 +53,7 @@
 JAR="${TESTJAVA}${FS}bin${FS}jar"
 JAVA="${TESTJAVA}${FS}bin${FS}java"
 JAVAC="${TESTJAVA}${FS}bin${FS}javac"
-KT="${TESTJAVA}${FS}bin${FS}keytool -keystore tsks -storepass changeit -keypass changeit"
+KT="${TESTJAVA}${FS}bin${FS}keytool -keystore tsks -storepass changeit -keypass changeit -validity 200"
 
 rm tsks
 echo Nothing > A
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/tools/jarsigner/warnings.sh	Thu Sep 19 10:40:16 2013 +0800
@@ -0,0 +1,117 @@
+#
+# Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# @test
+# @bug 8024302
+# @summary Clarify jar verifications
+#
+
+if [ "${TESTJAVA}" = "" ] ; then
+  JAVAC_CMD=`which javac`
+  TESTJAVA=`dirname $JAVAC_CMD`/..
+fi
+
+# set platform-dependent variables
+OS=`uname -s`
+case "$OS" in
+  Windows_* )
+    FS="\\"
+    ;;
+  * )
+    FS="/"
+    ;;
+esac
+
+KS=warnings.jks
+JFILE=warnings.jar
+
+KT="$TESTJAVA${FS}bin${FS}keytool -storepass changeit -keypass changeit \
+        -keystore $KS"
+JAR=$TESTJAVA${FS}bin${FS}jar
+JARSIGNER="$TESTJAVA${FS}bin${FS}jarsigner -keystore $KS -storepass changeit"
+
+rm $KS 2> /dev/null
+
+export LANG=C
+
+echo 12345 > file
+
+ERR=""
+
+# Normal signer expiring on 2100-01-01
+$KT -alias s1 -dname CN=s1 -genkey -startdate 2000/01/01 -validity 36525 || ERR="$ERR keytool s1,"
+# Cert expiring soon, informational warning
+$KT -alias s2 -dname CN=s2 -genkey -validity 100 || ERR="$ERR keytool s2,"
+# Cert expired, severe warning
+$KT -alias s3 -dname CN=s3 -genkey -startdate -200d -validity 100 || ERR="$ERR keytool s3,"
+
+# noTimestamp is informatiional warning and includes a date
+$JAR cvf $JFILE file
+$JARSIGNER $JFILE s1 > output1 || ERR="$ERR jarsigner s1,"
+$JARSIGNER -strict $JFILE s1 >> output1 || ERR="$ERR jarsigner s1 strict,"
+$JARSIGNER -verify $JFILE s1 >> output1 || ERR="$ERR jarsigner s1,"
+$JARSIGNER -verify -strict $JFILE s1 >> output1 || ERR="$ERR jarsigner s1 strict,"
+
+cat output1 | grep Warning || ERR="$ERR s1 warning,"
+cat output1 | grep Error && ERR="$ERR s1 error,"
+cat output1 | grep timestamp | grep 2100-01-01 || ERR="$ERR s1 timestamp,"
+cat output1 | grep "with signer errors" && ERR="$ERR s1 err,"
+
+# hasExpiringCert is informatiional warning
+$JAR cvf $JFILE file
+$JARSIGNER $JFILE s2 > output2 || ERR="$ERR jarsigner s2,"
+$JARSIGNER -strict $JFILE s2 >> output2 || ERR="$ERR jarsigner s2 strict,"
+$JARSIGNER -verify $JFILE s2 >> output2 || ERR="$ERR jarsigner s2,"
+$JARSIGNER -verify -strict $JFILE s2 >> output2 || ERR="$ERR jarsigner s2 strict,"
+
+cat output2 | grep Warning || ERR="$ERR s2 warning,"
+cat output2 | grep Error && ERR="$ERR s2 error,"
+cat output2 | grep timestamp || ERR="$ERR s2 timestamp,"
+cat output2 | grep "will expire" || ERR="$ERR s2 expiring,"
+cat output2 | grep "with signer errors" && ERR="$ERR s2 err,"
+
+# hasExpiredCert is severe warning
+$JAR cvf $JFILE file
+$JARSIGNER $JFILE s3 > output3 || ERR="$ERR jarsigner s3,"
+$JARSIGNER -strict $JFILE s3 > output3s && ERR="$ERR jarsigner s3 strict,"
+$JARSIGNER -verify $JFILE s3 >> output3 || ERR="$ERR jarsigner s3,"
+$JARSIGNER -verify -strict $JFILE s3 >> output3s && ERR="$ERR jarsigner s3 strict,"
+
+# warning without -strict
+cat output3 | grep Warning || ERR="$ERR s3 warning,"
+cat output3 | grep Error && ERR="$ERR s3 error,"
+cat output3 | grep "with signer errors" && ERR="$ERR s3 err,"
+
+# error with -strict
+cat output3s | grep Warning || ERR="$ERR s3s warning,"
+cat output3s | grep Error || ERR="$ERR s3s error,"
+cat output3s | grep "with signer errors" || ERR="$ERR s3 err,"
+
+if [ "$ERR" = "" ]; then
+    exit 0
+else
+    echo "ERR is $ERR"
+    exit 1
+fi
+
+