From 171fbd8cb4d06e55407bc5c34bf078a371a0e8d6 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sat, 7 Dec 2024 12:21:47 +0100 Subject: [PATCH 1/3] Add support for stripping Jar signatures when recompiling/remapping Forge --- .../actions/InjectFromZipFileSource.java | 28 ++++++++++- .../actions/RemapSrgSourcesAction.java | 1 - .../StripManifestDigestContentFilter.java | 46 +++++++++++++++++++ .../runtime/cli/RunNeoFormCommand.java | 16 ++++++- 4 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/InjectFromZipFileSource.java b/src/main/java/net/neoforged/neoform/runtime/actions/InjectFromZipFileSource.java index ea0ffdc..3c53a50 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/InjectFromZipFileSource.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/InjectFromZipFileSource.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.security.DigestOutputStream; import java.security.MessageDigest; @@ -34,15 +35,25 @@ public class InjectFromZipFileSource implements InjectSource { */ @Nullable private final Pattern includeFilterPattern; + /** + * This function can modify the content that is being copied. + */ + @Nullable + private final ContentFilter contentFilter; public InjectFromZipFileSource(ZipFile zf, String sourcePath) { this(zf, sourcePath, null); } public InjectFromZipFileSource(ZipFile zf, String sourcePath, @Nullable Pattern includeFilterPattern) { + this(zf, sourcePath, includeFilterPattern, null); + } + + public InjectFromZipFileSource(ZipFile zf, String sourcePath, @Nullable Pattern includeFilterPattern, @Nullable ContentFilter contentFilter) { this.zf = zf; this.sourcePath = sanitizeSourcePath(sourcePath); this.includeFilterPattern = includeFilterPattern; + this.contentFilter = contentFilter; } private static String sanitizeSourcePath(String sourcePath) { @@ -71,7 +82,11 @@ public CacheKey.AnnotatedValue getCacheKey(FileHashService fileHashService) thro if ((sourcePath.isEmpty() || entry.getName().startsWith(sourcePath)) && matchesIncludeFilter(entry)) { digestStream.write(entry.getName().getBytes()); try (var in = zf.getInputStream(entry)) { - in.transferTo(digestStream); + if (contentFilter != null) { + contentFilter.copy(entry, in, digestStream); + } else { + in.transferTo(digestStream); + } } } } @@ -108,7 +123,11 @@ public void copyTo(ZipOutputStream out) throws IOException { copiedEntry.setMethod(entry.getMethod()); out.putNextEntry(copiedEntry); - in.transferTo(out); + if (contentFilter != null) { + contentFilter.copy(entry, in, out); + } else { + in.transferTo(out); + } out.closeEntry(); } catch (ZipException e) { if (!e.getMessage().startsWith("duplicate entry:")) { @@ -125,4 +144,9 @@ public void copyTo(ZipOutputStream out) throws IOException { private boolean matchesIncludeFilter(ZipEntry entry) { return includeFilterPattern == null || includeFilterPattern.matcher(entry.getName()).matches(); } + + @FunctionalInterface + public interface ContentFilter { + void copy(ZipEntry entry, InputStream in, OutputStream out) throws IOException; + } } diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/RemapSrgSourcesAction.java b/src/main/java/net/neoforged/neoform/runtime/actions/RemapSrgSourcesAction.java index 6900302..95fcb91 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/RemapSrgSourcesAction.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/RemapSrgSourcesAction.java @@ -10,7 +10,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.HashMap; -import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipInputStream; diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java b/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java new file mode 100644 index 0000000..588b5c4 --- /dev/null +++ b/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java @@ -0,0 +1,46 @@ +package net.neoforged.neoform.runtime.actions; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +/** + * This content filter will strip the SHA-256 digests from MANIFEST.MF + */ +public class StripManifestDigestContentFilter implements InjectFromZipFileSource.ContentFilter { + // Theoretically, we'd need to check all digests that the VM supports, but we just go for the most common. + // https://docs.oracle.com/en/java/javase/21/docs/specs/jar/jar.html + private static final Set SIGNATURE_ATTRIBUTES = Set.of( + new Attributes.Name("Magic"), + new Attributes.Name("SHA-256-Digest"), + new Attributes.Name("SHA1-Digest") + ); + + public static final StripManifestDigestContentFilter INSTANCE = new StripManifestDigestContentFilter(); + + @Override + public void copy(ZipEntry entry, InputStream in, OutputStream out) throws IOException { + if (!entry.getName().equals("META-INF/MANIFEST.MF")) { + in.transferTo(out); + } else { + var manifest = new Manifest(in); + + var it = manifest.getEntries().values().iterator(); + while (it.hasNext()) { + // Remove all signing related attributes + var entryAttrs = it.next(); + entryAttrs.keySet().removeIf(SIGNATURE_ATTRIBUTES::contains); + // Remove entries that no longer have attributes + if (entryAttrs.isEmpty()) { + it.remove(); + } + } + + manifest.write(out); + } + } +} diff --git a/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java b/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java index aa58903..043bd60 100644 --- a/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java +++ b/src/main/java/net/neoforged/neoform/runtime/cli/RunNeoFormCommand.java @@ -6,6 +6,7 @@ import net.neoforged.neoform.runtime.actions.MergeWithSourcesAction; import net.neoforged.neoform.runtime.actions.PatchActionFactory; import net.neoforged.neoform.runtime.actions.RecompileSourcesAction; +import net.neoforged.neoform.runtime.actions.StripManifestDigestContentFilter; import net.neoforged.neoform.runtime.artifacts.ClasspathItem; import net.neoforged.neoform.runtime.config.neoforge.NeoForgeConfig; import net.neoforged.neoform.runtime.engine.NeoFormEngine; @@ -112,6 +113,7 @@ protected void runWithNeoFormEngine(NeoFormEngine engine, List cl // When source remapping is in effect, we would normally have to remap the NeoForge sources as well // To circumvent this, we inject the sources before recompile and disable the optimization of // injecting the already compiled NeoForge classes later. + // Since remapping and recompiling will invariably change the digests, we also need to strip any signatures. if (engine.getProcessGeneration().sourcesUseIntermediaryNames()) { engine.applyTransforms(List.of( new ModifyAction<>( @@ -120,8 +122,18 @@ protected void runWithNeoFormEngine(NeoFormEngine engine, List cl action -> { // Annoyingly, Forge only had the Java sources in the sources artifact. // We have to pull resources from the universal jar. - action.getInjectedSources().add(new InjectFromZipFileSource(neoforgeClassesZip, "/", Pattern.compile("^(?!.*\\.class$).*"))); - action.getInjectedSources().add(new InjectFromZipFileSource(neoforgeSourcesZip, "/")); + action.getInjectedSources().add(new InjectFromZipFileSource( + neoforgeClassesZip, + "/", + Pattern.compile("^(?!META-INF/[^/]+\\.(SF|RSA|DSA|EC)$|.*\\.class$).*"), + StripManifestDigestContentFilter.INSTANCE + )); + action.getInjectedSources().add(new InjectFromZipFileSource( + neoforgeSourcesZip, + "/", + // The MCF sources have a bogus MANIFEST that should be ignored + Pattern.compile("^(?!META-INF/MANIFEST.MF$).*") + )); } ) )); From ab556a2986168476f4ef9bc2c8c30e3da5f57701 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sat, 7 Dec 2024 12:27:24 +0100 Subject: [PATCH 2/3] Add support for stripping Jar signatures when recompiling/remapping Forge --- .../runtime/actions/StripManifestDigestContentFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java b/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java index 588b5c4..7a57d9d 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java @@ -9,7 +9,7 @@ import java.util.zip.ZipEntry; /** - * This content filter will strip the SHA-256 digests from MANIFEST.MF + * This content filter will strip signature related attributes from MANIFEST.MF entries. */ public class StripManifestDigestContentFilter implements InjectFromZipFileSource.ContentFilter { // Theoretically, we'd need to check all digests that the VM supports, but we just go for the most common. From c1bc942ce3e7ccab13b96bdd1cf622bdb454be4f Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sat, 7 Dec 2024 12:45:01 +0100 Subject: [PATCH 3/3] Review comments --- .../actions/InjectFromZipFileSource.java | 18 ++++++------------ .../StripManifestDigestContentFilter.java | 3 +++ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/InjectFromZipFileSource.java b/src/main/java/net/neoforged/neoform/runtime/actions/InjectFromZipFileSource.java index 3c53a50..1bec1c1 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/InjectFromZipFileSource.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/InjectFromZipFileSource.java @@ -11,6 +11,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HexFormat; +import java.util.Objects; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipException; @@ -38,7 +39,6 @@ public class InjectFromZipFileSource implements InjectSource { /** * This function can modify the content that is being copied. */ - @Nullable private final ContentFilter contentFilter; public InjectFromZipFileSource(ZipFile zf, String sourcePath) { @@ -53,7 +53,7 @@ public InjectFromZipFileSource(ZipFile zf, String sourcePath, @Nullable Pattern this.zf = zf; this.sourcePath = sanitizeSourcePath(sourcePath); this.includeFilterPattern = includeFilterPattern; - this.contentFilter = contentFilter; + this.contentFilter = Objects.requireNonNullElse(contentFilter, ContentFilter.NONE); } private static String sanitizeSourcePath(String sourcePath) { @@ -82,11 +82,7 @@ public CacheKey.AnnotatedValue getCacheKey(FileHashService fileHashService) thro if ((sourcePath.isEmpty() || entry.getName().startsWith(sourcePath)) && matchesIncludeFilter(entry)) { digestStream.write(entry.getName().getBytes()); try (var in = zf.getInputStream(entry)) { - if (contentFilter != null) { - contentFilter.copy(entry, in, digestStream); - } else { - in.transferTo(digestStream); - } + contentFilter.copy(entry, in, digestStream); } } } @@ -123,11 +119,7 @@ public void copyTo(ZipOutputStream out) throws IOException { copiedEntry.setMethod(entry.getMethod()); out.putNextEntry(copiedEntry); - if (contentFilter != null) { - contentFilter.copy(entry, in, out); - } else { - in.transferTo(out); - } + contentFilter.copy(entry, in, out); out.closeEntry(); } catch (ZipException e) { if (!e.getMessage().startsWith("duplicate entry:")) { @@ -147,6 +139,8 @@ private boolean matchesIncludeFilter(ZipEntry entry) { @FunctionalInterface public interface ContentFilter { + ContentFilter NONE = (entry, in, out) -> in.transferTo(out); + void copy(ZipEntry entry, InputStream in, OutputStream out) throws IOException; } } diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java b/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java index 7a57d9d..e088688 100644 --- a/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java +++ b/src/main/java/net/neoforged/neoform/runtime/actions/StripManifestDigestContentFilter.java @@ -22,6 +22,9 @@ public class StripManifestDigestContentFilter implements InjectFromZipFileSource public static final StripManifestDigestContentFilter INSTANCE = new StripManifestDigestContentFilter(); + private StripManifestDigestContentFilter() { + } + @Override public void copy(ZipEntry entry, InputStream in, OutputStream out) throws IOException { if (!entry.getName().equals("META-INF/MANIFEST.MF")) {