Skip to content

improve support for bytecode patching signed jars #128613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 11, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
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;
Expand Down Expand Up @@ -60,6 +62,10 @@ public String toString() {
}
}

public static void patchJar(File inputJar, File outputJar, Collection<PatcherInfo> 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.
Expand All @@ -69,8 +75,11 @@ public String toString() {
* @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 <a href="https://docs.oracle.com/javase/tutorial/deployment/jar/intro.html">Understanding Signing and Verification</a>
*/
public static void patchJar(File inputFile, File outputFile, Collection<PatcherInfo> patchers) {
public static void patchJar(File inputFile, File outputFile, Collection<PatcherInfo> patchers, boolean unsignJar) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the unsignJar should be the default behaviour since if there is a signature it will always be invalid after patching the jar. It also shouldn't impact existing usages of this method since they appear to not use signed jars (they work after patching).

var classPatchers = patchers.stream().collect(Collectors.toMap(PatcherInfo::jarEntryName, Function.identity()));
var mismatchedClasses = new ArrayList<MismatchInfo>();
try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) {
Expand Down Expand Up @@ -101,9 +110,23 @@ public static void patchJar(File inputFile, File outputFile, Collection<PatcherI
);
}
} 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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in practice, the digest keys appear to either be SHA-256-Digest or SHA1-Digest, but the algorithms aren't actually specified

https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signature_Validation

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();
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog/128613.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 128613
summary: Improve support for bytecode patching signed jars
area: Infra/Core
type: enhancement
issues: []
Loading