2424import java .util .HexFormat ;
2525import java .util .Locale ;
2626import java .util .function .Function ;
27+ import java .util .jar .Attributes ;
2728import java .util .jar .JarEntry ;
2829import java .util .jar .JarFile ;
2930import java .util .jar .JarOutputStream ;
31+ import java .util .jar .Manifest ;
3032import java .util .stream .Collectors ;
3133
3234import static org .objectweb .asm .ClassWriter .COMPUTE_FRAMES ;
@@ -60,6 +62,10 @@ public String toString() {
6062 }
6163 }
6264
65+ public static void patchJar (File inputJar , File outputJar , Collection <PatcherInfo > patchers ) {
66+ patchJar (inputJar , outputJar , patchers , false );
67+ }
68+
6369 /**
6470 * Patches the classes in the input JAR file, using the collection of patchers. Each patcher specifies a target class (its jar entry
6571 * name) and the SHA256 digest on the class bytes.
@@ -69,8 +75,11 @@ public String toString() {
6975 * @param inputFile the JAR file to patch
7076 * @param outputFile the output (patched) JAR file
7177 * @param patchers list of patcher info (classes to patch (jar entry name + optional SHA256 digest) and ASM visitor to transform them)
78+ * @param unsignJar whether to remove class signatures from the JAR Manifest; set this to true when patching a signed JAR,
79+ * otherwise the patched classes will fail to load at runtime due to mismatched signatures.
80+ * @see <a href="https://docs.oracle.com/javase/tutorial/deployment/jar/intro.html">Understanding Signing and Verification</a>
7281 */
73- public static void patchJar (File inputFile , File outputFile , Collection <PatcherInfo > patchers ) {
82+ public static void patchJar (File inputFile , File outputFile , Collection <PatcherInfo > patchers , boolean unsignJar ) {
7483 var classPatchers = patchers .stream ().collect (Collectors .toMap (PatcherInfo ::jarEntryName , Function .identity ()));
7584 var mismatchedClasses = new ArrayList <MismatchInfo >();
7685 try (JarFile jarFile = new JarFile (inputFile ); JarOutputStream jos = new JarOutputStream (new FileOutputStream (outputFile ))) {
@@ -101,9 +110,23 @@ public static void patchJar(File inputFile, File outputFile, Collection<PatcherI
101110 );
102111 }
103112 } else {
104- // Read the entry's data and write it to the new JAR
105113 try (InputStream is = jarFile .getInputStream (entry )) {
106- is .transferTo (jos );
114+ if (unsignJar && entryName .equals ("META-INF/MANIFEST.MF" )) {
115+ var manifest = new Manifest (is );
116+ for (var manifestEntry : manifest .getEntries ().entrySet ()) {
117+ var nonSignatureAttributes = new Attributes ();
118+ for (var attribute : manifestEntry .getValue ().entrySet ()) {
119+ if (attribute .getKey ().toString ().endsWith ("Digest" ) == false ) {
120+ nonSignatureAttributes .put (attribute .getKey (), attribute .getValue ());
121+ }
122+ }
123+ manifestEntry .setValue (nonSignatureAttributes );
124+ }
125+ manifest .write (jos );
126+ } else if (unsignJar == false || entryName .matches ("META-INF/.*\\ .SF" ) == false ) {
127+ // Read the entry's data and write it to the new JAR
128+ is .transferTo (jos );
129+ }
107130 }
108131 }
109132 jos .closeEntry ();
0 commit comments