6948909: Jarsigner removes MANIFEST.MF info for badly packages jar's
Reviewed-by: mullan, xuelei
--- 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
+