6948909: Jarsigner removes MANIFEST.MF info for badly packages jar's
authorweijun
Thu, 06 May 2010 11:26:16 +0800
changeset 5461 a0e3063bc133
parent 5460 0682ab146d05
child 5462 cb614e59f7f9
6948909: Jarsigner removes MANIFEST.MF info for badly packages jar's Reviewed-by: mullan, xuelei
jdk/src/share/classes/sun/security/tools/JarSigner.java
jdk/test/sun/security/tools/jarsigner/diffend.sh
--- a/jdk/src/share/classes/sun/security/tools/JarSigner.java	Wed May 05 13:18:31 2010 +0100
+++ b/jdk/src/share/classes/sun/security/tools/JarSigner.java	Thu May 06 11:26:16 2010 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 1997-2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 1997-2010 Sun Microsystems, Inc.  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
@@ -1123,6 +1123,8 @@
             BASE64Encoder encoder = new JarBASE64Encoder();
             Vector<ZipEntry> mfFiles = new Vector<ZipEntry>();
 
+            boolean wasSigned = false;
+
             for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
                         enum_.hasMoreElements();) {
                 ZipEntry ze = enum_.nextElement();
@@ -1132,6 +1134,11 @@
                     // out first
                     mfFiles.addElement(ze);
 
+                    if (SignatureFileVerifier.isBlockOrSF(
+                            ze.getName().toUpperCase(Locale.ENGLISH))) {
+                        wasSigned = true;
+                    }
+
                     if (signatureRelated(ze.getName())) {
                         // ignore signature-related and manifest files
                         continue;
@@ -1159,37 +1166,41 @@
             if (mfModified) {
                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
                 manifest.write(baos);
-                byte[] newBytes = baos.toByteArray();
-                if (mfRawBytes != null
-                        && oldAttr.equals(manifest.getMainAttributes())) {
+                if (wasSigned) {
+                    byte[] newBytes = baos.toByteArray();
+                    if (mfRawBytes != null
+                            && oldAttr.equals(manifest.getMainAttributes())) {
 
-                    /*
-                     * Note:
-                     *
-                     * The Attributes object is based on HashMap and can handle
-                     * continuation columns. Therefore, even if the contents are
-                     * not changed (in a Map view), the bytes that it write()
-                     * may be different from the original bytes that it read()
-                     * from. Since the signature on the main attributes is based
-                     * on raw bytes, we must retain the exact bytes.
-                     */
+                        /*
+                         * Note:
+                         *
+                         * The Attributes object is based on HashMap and can handle
+                         * continuation columns. Therefore, even if the contents are
+                         * not changed (in a Map view), the bytes that it write()
+                         * may be different from the original bytes that it read()
+                         * from. Since the signature on the main attributes is based
+                         * on raw bytes, we must retain the exact bytes.
+                         */
 
-                    int newPos = findHeaderEnd(newBytes);
-                    int oldPos = findHeaderEnd(mfRawBytes);
+                        int newPos = findHeaderEnd(newBytes);
+                        int oldPos = findHeaderEnd(mfRawBytes);
 
-                    if (newPos == oldPos) {
-                        System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
-                    } else {
-                        // cat oldHead newTail > newBytes
-                        byte[] lastBytes = new byte[oldPos +
-                                newBytes.length - newPos];
-                        System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
-                        System.arraycopy(newBytes, newPos, lastBytes, oldPos,
-                                newBytes.length - newPos);
-                        newBytes = lastBytes;
+                        if (newPos == oldPos) {
+                            System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
+                        } else {
+                            // cat oldHead newTail > newBytes
+                            byte[] lastBytes = new byte[oldPos +
+                                    newBytes.length - newPos];
+                            System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
+                            System.arraycopy(newBytes, newPos, lastBytes, oldPos,
+                                    newBytes.length - newPos);
+                            newBytes = lastBytes;
+                        }
                     }
+                    mfRawBytes = newBytes;
+                } else {
+                    mfRawBytes = baos.toByteArray();
                 }
-                mfRawBytes = newBytes;
             }
 
             // Write out the manifest
@@ -1411,23 +1422,31 @@
     }
 
     /**
-     * Find the position of an empty line inside bs
+     * Find the length of header inside bs. The header is a multiple (>=0)
+     * lines of attributes plus an empty line. The empty line is included
+     * in the header.
      */
     private int findHeaderEnd(byte[] bs) {
-        // An empty line can be at the beginning...
-        if (bs.length > 1 && bs[0] == '\r' && bs[1] == '\n') {
-            return 0;
-        }
-        // ... or after another line
-        for (int i=0; i<bs.length-3; i++) {
-            if (bs[i] == '\r' && bs[i+1] == '\n' &&
-                    bs[i+2] == '\r' && bs[i+3] == '\n') {
-               return i;
+        // Initial state true to deal with empty header
+        boolean newline = true;     // just met a newline
+        int len = bs.length;
+        for (int i=0; i<len; i++) {
+            switch (bs[i]) {
+                case '\r':
+                    if (i < len && bs[i+1] == '\n') i++;
+                    // fallthrough
+                case '\n':
+                    if (newline) return i+1;    //+1 to get length
+                    newline = true;
+                    break;
+                default:
+                    newline = false;
             }
         }
-        // If header end is not found, return 0,
-        // which means no behavior change.
-        return 0;
+        // If header end is not found, it means the MANIFEST.MF has only
+        // the main attributes section and it does not end with 2 newlines.
+        // Returns the whole length so that it can be completely replaced.
+        return len;
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/tools/jarsigner/diffend.sh	Thu May 06 11:26:16 2010 +0800
@@ -0,0 +1,113 @@
+#
+# Copyright 2010 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# @test
+# @bug 6948909
+# @summary Jarsigner removes MANIFEST.MF info for badly packages jar's
+#
+
+if [ "${TESTSRC}" = "" ] ; then
+  TESTSRC="."
+fi
+if [ "${TESTCLASSES}" = "" ] ; then
+  TESTCLASSES="."
+fi
+if [ "${TESTJAVA}" = "" ] ; then
+  echo "TESTJAVA not set.  Test cannot execute."
+  echo "FAILED!!!"
+  exit 1
+fi
+
+# set platform-dependent variables
+OS=`uname -s`
+case "$OS" in
+  SunOS | Linux )
+    NULL=/dev/null
+    PS=":"
+    FS="/"
+    CP="${FS}bin${FS}cp -f"
+    TMP=/tmp
+    ;;
+  CYGWIN* )
+    NULL=/dev/null
+    PS=";"
+    FS="/"
+    CP="cp -f"
+    TMP=/tmp
+    ;;
+  Windows_* )
+    NULL=NUL
+    PS=";"
+    FS="\\"
+    CP="cp -f"
+    TMP="c:/temp"
+    ;;
+  * )
+    echo "Unrecognized operating system!"
+    exit 1;
+    ;;
+esac
+
+echo 1 > 1
+mkdir META-INF
+
+# Create a fake .RSA file so that jarsigner believes it's signed
+
+touch META-INF/x.RSA
+
+# A MANIFEST.MF using \n as newlines and no double newlines at the end
+
+cat > META-INF/MANIFEST.MF <<EOF
+Manifest-Version: 1.0
+Created-By: 1.7.0-internal (Sun Microsystems Inc.)
+Today: Monday
+EOF
+
+# With the fake .RSA file, to trigger the if (wasSigned) block
+
+rm diffend.jar
+zip diffend.jar META-INF/MANIFEST.MF META-INF/x.RSA 1
+
+${TESTJAVA}${FS}bin${FS}jarsigner \
+    -keystore ${TESTSRC}${FS}JarSigning.keystore \
+    -storepass bbbbbb \
+    -digestalg SHA1 \
+    -signedjar diffend.new.jar \
+    diffend.jar c
+
+unzip -p diffend.new.jar META-INF/MANIFEST.MF | grep Today || exit 1
+
+# Without the fake .RSA file, to trigger the else block
+
+rm diffend.jar
+zip diffend.jar META-INF/MANIFEST.MF 1
+
+${TESTJAVA}${FS}bin${FS}jarsigner \
+    -keystore ${TESTSRC}${FS}JarSigning.keystore \
+    -storepass bbbbbb \
+    -digestalg SHA1 \
+    -signedjar diffend.new.jar \
+    diffend.jar c
+
+unzip -p diffend.new.jar META-INF/MANIFEST.MF | grep Today || exit 2
+