diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index 63831b6f062c..8903e1a6aa0b 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -24,9 +24,11 @@ import java.util.Enumeration; import java.util.HexFormat; import java.util.Locale; import java.util.function.Function; +import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; import java.util.stream.Collectors; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; @@ -60,6 +62,10 @@ public class Utils { } } + public static void patchJar(File inputJar, File outputJar, Collection patchers) { + patchJar(inputJar, outputJar, patchers, false); + } + /** * Patches the classes in the input JAR file, using the collection of patchers. Each patcher specifies a target class (its jar entry * name) and the SHA256 digest on the class bytes. @@ -69,8 +75,11 @@ public class Utils { * @param inputFile the JAR file to patch * @param outputFile the output (patched) JAR file * @param patchers list of patcher info (classes to patch (jar entry name + optional SHA256 digest) and ASM visitor to transform them) + * @param unsignJar whether to remove class signatures from the JAR Manifest; set this to true when patching a signed JAR, + * otherwise the patched classes will fail to load at runtime due to mismatched signatures. + * @see Understanding Signing and Verification */ - public static void patchJar(File inputFile, File outputFile, Collection patchers) { + public static void patchJar(File inputFile, File outputFile, Collection patchers, boolean unsignJar) { var classPatchers = patchers.stream().collect(Collectors.toMap(PatcherInfo::jarEntryName, Function.identity())); var mismatchedClasses = new ArrayList(); try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) { @@ -101,9 +110,23 @@ public class Utils { ); } } else { - // Read the entry's data and write it to the new JAR try (InputStream is = jarFile.getInputStream(entry)) { - is.transferTo(jos); + if (unsignJar && entryName.equals("META-INF/MANIFEST.MF")) { + var manifest = new Manifest(is); + for (var manifestEntry : manifest.getEntries().entrySet()) { + var nonSignatureAttributes = new Attributes(); + for (var attribute : manifestEntry.getValue().entrySet()) { + if (attribute.getKey().toString().endsWith("Digest") == false) { + nonSignatureAttributes.put(attribute.getKey(), attribute.getValue()); + } + } + manifestEntry.setValue(nonSignatureAttributes); + } + manifest.write(jos); + } else if (unsignJar == false || entryName.matches("META-INF/.*\\.SF") == false) { + // Read the entry's data and write it to the new JAR + is.transferTo(jos); + } } } jos.closeEntry(); diff --git a/docs/changelog/128613.yaml b/docs/changelog/128613.yaml new file mode 100644 index 000000000000..4d5d7bba0354 --- /dev/null +++ b/docs/changelog/128613.yaml @@ -0,0 +1,5 @@ +pr: 128613 +summary: Improve support for bytecode patching signed jars +area: Infra/Core +type: enhancement +issues: []