24
24
import java .util .HexFormat ;
25
25
import java .util .Locale ;
26
26
import java .util .function .Function ;
27
+ import java .util .jar .Attributes ;
27
28
import java .util .jar .JarEntry ;
28
29
import java .util .jar .JarFile ;
29
30
import java .util .jar .JarOutputStream ;
31
+ import java .util .jar .Manifest ;
30
32
import java .util .stream .Collectors ;
31
33
32
34
import static org .objectweb .asm .ClassWriter .COMPUTE_FRAMES ;
@@ -60,6 +62,10 @@ public String toString() {
60
62
}
61
63
}
62
64
65
+ public static void patchJar (File inputJar , File outputJar , Collection <PatcherInfo > patchers ) {
66
+ patchJar (inputJar , outputJar , patchers , false );
67
+ }
68
+
63
69
/**
64
70
* Patches the classes in the input JAR file, using the collection of patchers. Each patcher specifies a target class (its jar entry
65
71
* name) and the SHA256 digest on the class bytes.
@@ -69,8 +75,11 @@ public String toString() {
69
75
* @param inputFile the JAR file to patch
70
76
* @param outputFile the output (patched) JAR file
71
77
* @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>
72
81
*/
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 ) {
74
83
var classPatchers = patchers .stream ().collect (Collectors .toMap (PatcherInfo ::jarEntryName , Function .identity ()));
75
84
var mismatchedClasses = new ArrayList <MismatchInfo >();
76
85
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
101
110
);
102
111
}
103
112
} else {
104
- // Read the entry's data and write it to the new JAR
105
113
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
+ }
107
130
}
108
131
}
109
132
jos .closeEntry ();
0 commit comments