diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java index ab5d8fc6842..763bafa8759 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java @@ -237,6 +237,7 @@ public class Main { private boolean badNetscapeCertType = false; private boolean signerSelfSigned = false; private boolean allAliasesFound = true; + private boolean hasMultipleManifests = false; private Throwable chainNotValidatedReason = null; private Throwable tsaChainNotValidatedReason = null; @@ -1252,6 +1253,11 @@ public class Main { rb.getString("The.full.keyAlgName.signing.key.is.considered.a.security.risk.and.is.disabled."), fullDisplayKeyName(privateKey))); } + + if (hasMultipleManifests) { + warnings.add(String.format(rb.getString("multiple.manifest.warning."))); + } + } else { if ((legacyAlg & 1) != 0) { warnings.add(String.format( @@ -1965,6 +1971,15 @@ public class Main { Throwable failedCause = null; String failedMessage = null; + try (JarFile asJar = new JarFile(jarFile)) { + if (JUZFA.getManifestNum(asJar) > 1) { + hasMultipleManifests = true; + } + } catch (IOException ioe) { + // intentionally "eat" this, since we don't want to fail, if we + // cannot perform the multiple manifest check to output the warning + } + try { Event.setReportListener(Event.ReporterCategory.ZIPFILEATTRS, (t, o) -> externalFileAttributesDetected = true); diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties index 6721dbc837b..0b28eec671d 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties @@ -96,6 +96,7 @@ jar.is.unsigned=jar is unsigned. jar.treated.unsigned=WARNING: Signature is either not parsable or not verifiable, and the jar will be treated as unsigned. For more information, re-run jarsigner with debug enabled (-J-Djava.security.debug=jar). jar.treated.unsigned.see.weak=The jar will be treated as unsigned, because it is signed with a weak algorithm that is now disabled.\n\nRe-run jarsigner with the -verbose option for more details. jar.treated.unsigned.see.weak.verbose=WARNING: The jar will be treated as unsigned, because it is signed with a weak algorithm that is now disabled by the security property: +multiple.manifest.warning.=Duplicate manifest entries were detected in the jar file. JarSigner operated on only one, and the others have been discarded. jar.signed.=jar signed. jar.signed.with.signer.errors.=jar signed, with signer errors. jar.verified.=jar verified. diff --git a/src/jdk.jartool/share/man/jarsigner.md b/src/jdk.jartool/share/man/jarsigner.md index 9542c0fda60..fccdf0ecdc3 100644 --- a/src/jdk.jartool/share/man/jarsigner.md +++ b/src/jdk.jartool/share/man/jarsigner.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1998, 2025, 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 @@ -922,6 +922,10 @@ hasExpiringCert hasExpiringTsaCert : The timestamp will expire within one year on `YYYY-MM-DD`. +hasMultipleManifests +: This JAR contained multiple manifest files. During signing, one of the files +was selected, and the others were discarded. + hasNonexistentEntries : This JAR contains signed entries for files that do not exist. diff --git a/test/jdk/sun/security/tools/jarsigner/MultiManifest.java b/test/jdk/sun/security/tools/jarsigner/MultiManifest.java new file mode 100644 index 00000000000..8ea88697625 --- /dev/null +++ b/test/jdk/sun/security/tools/jarsigner/MultiManifest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025, 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 8341775 + * @summary Print warning that duplicate manifest files are removed by jarsigner + * after signing whether or not -verbose is passed + * @library /test/lib + */ + +import jdk.test.lib.SecurityTools; + +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class MultiManifest { + + private static final String META_INF = "META-INF/"; + + public static void main(String[] args) throws Exception { + + writeMultiManifestJar(); + + SecurityTools.keytool("-keystore ks -storepass changeit " + + "-keypass changeit -alias a -dname CN=a -keyalg rsa " + + "-genkey -validity 300"); + + SecurityTools.jarsigner("-verbose -keystore ks -storepass changeit " + + "MultiManifest.jar -signedjar MultiManifest.signed.jar a") + .shouldHaveExitValue(0) + .shouldContain("Duplicate manifest entries were detected") + .shouldContain("discarded"); + + SecurityTools.jarsigner("-keystore ks -storepass changeit " + + "MultiManifest.jar -signedjar MultiManifest.signed.jar a") + .shouldHaveExitValue(0) + .shouldContain("Duplicate manifest entries were detected") + .shouldContain("discarded"); + + } + + public static void writeMultiManifestJar() throws IOException { + int locPosA, locPosB, cenPos; + var out = new ByteArrayOutputStream(1024); + try (var zos = new ZipOutputStream(out)) { + zos.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME)); + zos.closeEntry(); + locPosA = out.size(); + zos.putNextEntry(new ZipEntry(META_INF + "AANIFEST.MF")); + zos.closeEntry(); + locPosB = out.size(); + zos.putNextEntry(new ZipEntry(META_INF + "BANIFEST.MF")); + zos.flush(); + cenPos = out.size(); + } + var template = out.toByteArray(); + // ISO_8859_1 to keep the 8-bit value + var s = new String(template, StandardCharsets.ISO_8859_1); + // change META-INF/AANIFEST.MF to META-INF/MANIFEST.MF + var loc = s.indexOf("AANIFEST.MF", locPosA); + var cen = s.indexOf("AANIFEST.MF", cenPos); + template[loc] = template[cen] = (byte) 'M'; + // change META-INF/BANIFEST.MF to META-INF/MANIFEST.MF + loc = s.indexOf("BANIFEST.MF", locPosB); + cen = s.indexOf("BANIFEST.MF", cenPos); + template[loc] = template[cen] = (byte) 'M'; + Files.write(Path.of("MultiManifest.jar"), template); + } +}