From a4fa02f3e59bba78267e058f69def8fd9dd7b6d9 Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Fri, 5 Jul 2024 14:32:48 +0200 Subject: [PATCH 1/9] [ISSUE 606] Discard signatures from jar file that was mutated --- .../signed-hello-servlet-1.0-SNAPSHOT.jar | Bin 0 -> 4514 bytes .../signed-hello-world-1.0-SNAPSHOT.jar | Bin 0 -> 4221 bytes .../transformer/test/TestCommandLine.java | 55 ++++++++++++++++++ org.eclipse.transformer/pom.xml | 4 ++ .../transformer/action/ElementAction.java | 5 ++ .../action/impl/ZipActionImpl.java | 52 ++++++++++++++++- 6 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.transformer.cli/src/test/data/command-line/signed-hello-servlet-1.0-SNAPSHOT.jar create mode 100644 org.eclipse.transformer.cli/src/test/data/command-line/signed-hello-world-1.0-SNAPSHOT.jar diff --git a/org.eclipse.transformer.cli/src/test/data/command-line/signed-hello-servlet-1.0-SNAPSHOT.jar b/org.eclipse.transformer.cli/src/test/data/command-line/signed-hello-servlet-1.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..d2f2b9a99c10c4f32e32d5e57831939b7446151c GIT binary patch literal 4514 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 V8NfhHbUO~(?;@>5Pjb@#{Rh1Re4qdT literal 0 HcmV?d00001 diff --git a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java index 5d2c0a75..45d9b989 100644 --- a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java +++ b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java @@ -15,14 +15,21 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.PrintStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import aQute.lib.io.IO; import org.assertj.core.api.SoftAssertions; import org.eclipse.transformer.Transformer; import org.eclipse.transformer.action.Changes; import org.eclipse.transformer.action.ContainerChanges; +import org.eclipse.transformer.action.ElementAction; import org.eclipse.transformer.action.impl.DirectoryActionImpl; import org.eclipse.transformer.action.impl.JavaActionImpl; import org.eclipse.transformer.action.impl.ManifestActionImpl; @@ -129,6 +136,41 @@ void zip_entry_creation() throws Exception { verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); } + // Tests that signature files have been discarded from transformed jar file + @Test + void testSignatureFilesStrippedFromTransformedJarFile() throws Exception { + String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-hello-servlet-1.0-SNAPSHOT.jar"; + String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-hello-servlet-1.0-SNAPSHOT.jar"; + // Assert that signed input jar file contains 2 signature files: META-INF/MYKEY.SF and META-INF/MYKEY.DSA + Map sigFilesMap = extractSignatureFileEntries(inputFileName); + assertThat(sigFilesMap.size()).isEqualTo(2); + assertThat(sigFilesMap.containsKey("META-INF/MYKEY.SF")).isTrue(); + assertThat(sigFilesMap.containsKey("META-INF/MYKEY.DSA")).isTrue(); + verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); + // Assert that signature files have been removed from output jar file + assertThat(extractSignatureFileEntries(outputFileName).size()).isEqualTo(0); + } + + // Tests that signature files have been preserved in unmodified jar file + @Test + void testSignatureFilesPreservedInUnmodifiedJarFile() throws Exception { + String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-hello-world-1.0-SNAPSHOT.jar"; + String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-hello-world-1.0-SNAPSHOT.jar"; + // Assert that signed input jar file contains 2 signature files: META-INF/MYKEY.SF and META-INF/MYKEY.DSA + Map inputJarSigFilesMap = extractSignatureFileEntries(inputFileName); + assertThat(inputJarSigFilesMap.size()).isEqualTo(2); + assertThat(inputJarSigFilesMap.containsKey("META-INF/MYKEY.SF")).isTrue(); + assertThat(inputJarSigFilesMap.containsKey("META-INF/MYKEY.DSA")).isTrue(); + verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); + // Assert that signature files have been preserved in output jar file + Map outputJarSigFilesMap = extractSignatureFileEntries(outputFileName); + assertThat(outputJarSigFilesMap.size()).isEqualTo(2); + assertThat(outputJarSigFilesMap.containsKey("META-INF/MYKEY.SF")).isTrue(); + assertThat(outputJarSigFilesMap.get("META-INF/MYKEY.SF").getSize()).isEqualTo(inputJarSigFilesMap.get("META-INF/MYKEY.SF").getSize()); + assertThat(outputJarSigFilesMap.containsKey("META-INF/MYKEY.DSA")).isTrue(); + assertThat(outputJarSigFilesMap.get("META-INF/MYKEY.DSA").getSize()).isEqualTo(inputJarSigFilesMap.get("META-INF/MYKEY.DSA").getSize()); + } + // Test zip with STORED archive to make sure ZipEntries are properly created. @Test void zip_nested_stored_archive() throws Exception { @@ -368,4 +410,17 @@ private void verifyActionInvalidDirectoryRejected(String actionClassName, String assertThat(transformer.setOutput()).as("transformer.setOutput() unexpectedly succeeded") .isFalse(); } + + private static Map extractSignatureFileEntries(String zipFilePath) throws IOException { + final ZipFile zipFile = new ZipFile(zipFilePath); + final Enumeration entries = zipFile.entries(); + final Map signatureFilesMap = new HashMap(); + while (entries.hasMoreElements()) { + final ZipEntry zipEntry = entries.nextElement(); + if (ElementAction.SIGNATURE_FILE_PATTERN.matcher(zipEntry.getName()).matches()) { + signatureFilesMap.put(zipEntry.getName(), zipEntry); + } + } + return signatureFilesMap; + } } diff --git a/org.eclipse.transformer/pom.xml b/org.eclipse.transformer/pom.xml index 675469f2..dbb41189 100644 --- a/org.eclipse.transformer/pom.xml +++ b/org.eclipse.transformer/pom.xml @@ -41,6 +41,10 @@ biz.aQute.bnd biz.aQute.bnd.transform + + biz.aQute.bnd + biz.aQute.bndlib + org.slf4j slf4j-simple diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/ElementAction.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/ElementAction.java index 5e890cf0..08846d45 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/ElementAction.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/ElementAction.java @@ -13,7 +13,12 @@ import org.eclipse.transformer.TransformException; +import java.util.regex.Pattern; + public interface ElementAction extends Action { + + Pattern SIGNATURE_FILE_PATTERN = Pattern.compile("META-INF/[A-Za-z0-9_-]+\\.(SF|RSA|DSA|EC)"); + @Override default boolean isElementAction() { return true; diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java index 103e7dcf..959105a6 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java @@ -18,7 +18,9 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.file.attribute.FileTime; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.zip.CRC32; import java.util.zip.ZipEntry; @@ -27,6 +29,7 @@ import aQute.lib.io.ByteBufferOutputStream; import aQute.lib.io.IO; +import aQute.libg.tuple.Pair; import org.eclipse.transformer.TransformException; import org.eclipse.transformer.action.Action; import org.eclipse.transformer.action.ActionContext; @@ -257,6 +260,8 @@ private void applyZipStream( String prevName = null; String inputName = null; + final Map> signatureFilesMap = new HashMap<>(); + try { for ( ZipEntry inputEntry; (inputEntry = zipInputStream.getNextEntry()) != null; @@ -264,6 +269,16 @@ private void applyZipStream( try { inputName = FileUtils.sanitize(inputEntry.getName()); // Avoid ZipSlip + if (ElementAction.SIGNATURE_FILE_PATTERN.matcher(inputName).matches()) { + /* + * Signature file resource: Store its contents in memory until we have finished processing + * the input zip file. Only at that point will we know if any of its resources were modified, + * in which case the signature files will have been invalidated and must be discarded. + * Otherwise, they must be added back to the output zip file. + */ + signatureFilesMap.put(inputName, Pair.newInstance(inputEntry, collect(inputName, zipInputStream))); + continue; + } int inputLength = Math.toIntExact(inputEntry.getSize()); useLogger.debug("[ {}.{} ] Entry [ {} ] Size [ {} ]", className, methodName, inputName, inputLength); @@ -416,7 +431,7 @@ private void applyZipStream( useLogger.error("Transform failure [ {} ] of [ {} ]", inputName, inputPath, t); } } - + handleSignatureFiles(signatureFilesMap, zipOutputStream, inputPath); } catch (IOException e) { String message; if (inputName != null) { @@ -430,6 +445,10 @@ private void applyZipStream( message = "Failed to process first entry of [ " + inputPath + " ]"; } throw new TransformException(message, e); + } finally { + if (!signatureFilesMap.isEmpty()) { + signatureFilesMap.clear(); + } } } @@ -596,4 +615,35 @@ private void putEntry(ZipOutputStream zipOutputStream, ZipEntry outputEntry, Tra zipOutputStream.closeEntry(); // throws IOException } } + + /* + * Adds any signature (*.SF) and signature block (*.[RSA|DSA|EC]) files extracted from the input zip file + * to the output zip file. + * + * If any of the resources of the input zip file have been transformed, then the signature files are no longer valid + * and will be discarded. + * + * @param signatureFilesMap Map of signature files extracted from the input zip file, will be empty if the + * input zip file is unsigned + * @param zipOutputStream Output zip file + * @param inputPath Path to the input zip file (used for logging purposes only) + */ + private void handleSignatureFiles(Map> signatureFilesMap, + ZipOutputStream zipOutputStream, + String inputPath) throws IOException { + if (!signatureFilesMap.isEmpty()) { + if (!getActiveChanges().isContentChanged()) { + for (Map.Entry> entry : signatureFilesMap.entrySet()) { + if (getLogger().isInfoEnabled()) { + getLogger().info("Restoring signature file [ {} ] from unmodified [ {} ]", entry.getKey(), inputPath); + } + writeUnmodified(entry.getValue().getFirst(), entry.getValue().getSecond(), entry.getKey(), zipOutputStream); + } + } else { + if (getLogger().isInfoEnabled()) { + getLogger().info("Discarding all signature files from transformed [ {} ]", inputPath); + } + } + } + } } From ca1b1c23116c083c8c71761453b70695c0b7b4e4 Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Fri, 5 Jul 2024 15:39:51 +0200 Subject: [PATCH 2/9] [ISSUE 606] Discard signatures from jar file that was mutated --- org.eclipse.transformer/pom.xml | 4 ---- .../transformer/action/impl/ZipActionImpl.java | 12 ++++++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/org.eclipse.transformer/pom.xml b/org.eclipse.transformer/pom.xml index dbb41189..675469f2 100644 --- a/org.eclipse.transformer/pom.xml +++ b/org.eclipse.transformer/pom.xml @@ -41,10 +41,6 @@ biz.aQute.bnd biz.aQute.bnd.transform - - biz.aQute.bnd - biz.aQute.bndlib - org.slf4j slf4j-simple diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java index 959105a6..2134c6fd 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java @@ -18,6 +18,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.file.attribute.FileTime; +import java.util.AbstractMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -29,7 +30,6 @@ import aQute.lib.io.ByteBufferOutputStream; import aQute.lib.io.IO; -import aQute.libg.tuple.Pair; import org.eclipse.transformer.TransformException; import org.eclipse.transformer.action.Action; import org.eclipse.transformer.action.ActionContext; @@ -260,7 +260,7 @@ private void applyZipStream( String prevName = null; String inputName = null; - final Map> signatureFilesMap = new HashMap<>(); + final Map> signatureFilesMap = new HashMap(); try { for ( ZipEntry inputEntry; @@ -276,7 +276,7 @@ private void applyZipStream( * in which case the signature files will have been invalidated and must be discarded. * Otherwise, they must be added back to the output zip file. */ - signatureFilesMap.put(inputName, Pair.newInstance(inputEntry, collect(inputName, zipInputStream))); + signatureFilesMap.put(inputName, new AbstractMap.SimpleEntry(inputEntry, collect(inputName, zipInputStream))); continue; } int inputLength = Math.toIntExact(inputEntry.getSize()); @@ -628,16 +628,16 @@ private void putEntry(ZipOutputStream zipOutputStream, ZipEntry outputEntry, Tra * @param zipOutputStream Output zip file * @param inputPath Path to the input zip file (used for logging purposes only) */ - private void handleSignatureFiles(Map> signatureFilesMap, + private void handleSignatureFiles(Map> signatureFilesMap, ZipOutputStream zipOutputStream, String inputPath) throws IOException { if (!signatureFilesMap.isEmpty()) { if (!getActiveChanges().isContentChanged()) { - for (Map.Entry> entry : signatureFilesMap.entrySet()) { + for (Map.Entry> entry : signatureFilesMap.entrySet()) { if (getLogger().isInfoEnabled()) { getLogger().info("Restoring signature file [ {} ] from unmodified [ {} ]", entry.getKey(), inputPath); } - writeUnmodified(entry.getValue().getFirst(), entry.getValue().getSecond(), entry.getKey(), zipOutputStream); + writeUnmodified(entry.getValue().getKey(), entry.getValue().getValue(), entry.getKey(), zipOutputStream); } } else { if (getLogger().isInfoEnabled()) { From 8592e37436c688f0b9bd335f39a9e3023f9e89d4 Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Fri, 5 Jul 2024 16:04:42 +0200 Subject: [PATCH 3/9] [ISSUE 606] Borrow signature file pattern from aQute.bnd.osgi.Jar.METAINF_SIGNING_P --- .../main/java/org/eclipse/transformer/action/ElementAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/ElementAction.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/ElementAction.java index 08846d45..2f64c64e 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/ElementAction.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/ElementAction.java @@ -17,7 +17,7 @@ public interface ElementAction extends Action { - Pattern SIGNATURE_FILE_PATTERN = Pattern.compile("META-INF/[A-Za-z0-9_-]+\\.(SF|RSA|DSA|EC)"); + Pattern SIGNATURE_FILE_PATTERN = Pattern.compile("META-INF/([^/]+\\.(?:DSA|RSA|EC|SF)|SIG-[^/]+)"); @Override default boolean isElementAction() { From 09b3b986bfd0d1e42a5cc60dc6fa1215e09145d0 Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Sun, 4 Aug 2024 20:19:11 +0200 Subject: [PATCH 4/9] [ISSUE 606] Discard signatures from jar file that was mutated --- .../signed-hello-servlet-1.0-SNAPSHOT.jar | Bin 4514 -> 0 bytes .../signed-hello-world-1.0-SNAPSHOT.jar | Bin 4221 -> 0 bytes .../command-line/signed-jar-with-javax.jar | Bin 0 -> 109502 bytes .../command-line/signed-jar-without-javax.jar | Bin 0 -> 4903 bytes .../transformer/test/TestCommandLine.java | 22 ++- org.eclipse.transformer/pom.xml | 3 + .../action/impl/ZipActionImpl.java | 178 ++++++++++++------ 7 files changed, 141 insertions(+), 62 deletions(-) delete mode 100644 org.eclipse.transformer.cli/src/test/data/command-line/signed-hello-servlet-1.0-SNAPSHOT.jar delete mode 100644 org.eclipse.transformer.cli/src/test/data/command-line/signed-hello-world-1.0-SNAPSHOT.jar create mode 100644 org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-with-javax.jar create mode 100644 org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-without-javax.jar diff --git a/org.eclipse.transformer.cli/src/test/data/command-line/signed-hello-servlet-1.0-SNAPSHOT.jar b/org.eclipse.transformer.cli/src/test/data/command-line/signed-hello-servlet-1.0-SNAPSHOT.jar deleted file mode 100644 index d2f2b9a99c10c4f32e32d5e57831939b7446151c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4514 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 V8NfhHbUO~(?;@>5Pjb@#{Rh1Re4qdT diff --git a/org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-with-javax.jar b/org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-with-javax.jar new file mode 100644 index 0000000000000000000000000000000000000000..5f7bea0f2afdd4a04ad226227e42e930cc3819b4 GIT binary patch literal 109502 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 literal 0 HcmV?d00001 diff --git a/org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-without-javax.jar b/org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-without-javax.jar new file mode 100644 index 0000000000000000000000000000000000000000..3d4452f062356780d1a2da077141553e8ab06dab GIT binary patch literal 4903 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 literal 0 HcmV?d00001 diff --git a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java index 45d9b989..66ab2b44 100644 --- a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java +++ b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java @@ -136,11 +136,16 @@ void zip_entry_creation() throws Exception { verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); } - // Tests that signature files have been discarded from transformed jar file + /* + * Tests that signature files have been discarded from signed jar file that was mutated by transformation. + * + * signed-jar-with-javax.jar contains a single class file which references javax.servlet.Servlet. + * The Java source code is included with the jar file. + */ @Test void testSignatureFilesStrippedFromTransformedJarFile() throws Exception { - String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-hello-servlet-1.0-SNAPSHOT.jar"; - String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-hello-servlet-1.0-SNAPSHOT.jar"; + String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-jar-with-javax.jar"; + String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-jar-with-javax.jar"; // Assert that signed input jar file contains 2 signature files: META-INF/MYKEY.SF and META-INF/MYKEY.DSA Map sigFilesMap = extractSignatureFileEntries(inputFileName); assertThat(sigFilesMap.size()).isEqualTo(2); @@ -151,11 +156,16 @@ void testSignatureFilesStrippedFromTransformedJarFile() throws Exception { assertThat(extractSignatureFileEntries(outputFileName).size()).isEqualTo(0); } - // Tests that signature files have been preserved in unmodified jar file + /* + * Tests that signature files have been preserved in signed jar file that was left unmodified by transformation. + * + * signed-jar-without-javax.jar contains a single class file without any javax references. + * The Java source code is included with the jar file. + */ @Test void testSignatureFilesPreservedInUnmodifiedJarFile() throws Exception { - String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-hello-world-1.0-SNAPSHOT.jar"; - String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-hello-world-1.0-SNAPSHOT.jar"; + String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-jar-without-javax.jar"; + String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-jar-without-javax.jar"; // Assert that signed input jar file contains 2 signature files: META-INF/MYKEY.SF and META-INF/MYKEY.DSA Map inputJarSigFilesMap = extractSignatureFileEntries(inputFileName); assertThat(inputJarSigFilesMap.size()).isEqualTo(2); diff --git a/org.eclipse.transformer/pom.xml b/org.eclipse.transformer/pom.xml index 675469f2..13bcb6bf 100644 --- a/org.eclipse.transformer/pom.xml +++ b/org.eclipse.transformer/pom.xml @@ -25,6 +25,9 @@ Eclipse Transformer Library ${project.groupId}:${project.artifactId} https://projects.eclipse.org/projects/technology.transformer + + 9 + https://github.com/eclipse/transformer scm:git:https://github.com/eclipse/transformer.git diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java index 2134c6fd..59fee509 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java @@ -11,6 +11,8 @@ package org.eclipse.transformer.action.impl; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -18,10 +20,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.file.attribute.FileTime; -import java.util.AbstractMap; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Set; import java.util.zip.CRC32; import java.util.zip.ZipEntry; @@ -40,6 +39,8 @@ import org.eclipse.transformer.util.FileUtils; import org.slf4j.Logger; +import static org.eclipse.transformer.action.ActionType.*; + /** * Top level ZIP action. A ZIP action is a container action which iterates * across the ZIP file elements, selecting and applying actions on each of these @@ -188,20 +189,48 @@ private void applyStream(String inputPath, InputStream inputStream, String outpu Charset charset = resourceCharset(inputPath); getLogger().debug("Zip Charset [ {} ]: {}", inputPath, charset); - try { - ZipInputStream zipInputStream = new ZipInputStream(inputStream, charset); - - ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, charset); + if (actionType == JAR) { try { - applyZipStream(inputPath, zipInputStream, outputPath, zipOutputStream); - } finally { - zipOutputStream.finish(); + // "Clone" inputStream because we are going to consume it twice: in Round One and Two + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + inputStream.transferTo(baos); + byte[] inputBytes = baos.toByteArray(); + + // Round One: Determine if the jar file is signed and has transformation changes. + // Does not output any transformation changes: this will be done in the Round Two + boolean discardSignatureFiles; + try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(inputBytes), charset)) { + discardSignatureFiles = applyZipStream(inputPath, zipInputStream, outputPath, new NoopZipOutputStreamWrapper(), false); + } finally { + stopRecording(inputPath); + } + // Round Two: Use the information obtained in Round One: If the jar file is signed and has + // transformation changes, its signature files must be discarded because they are no longer invalid + startRecording(inputPath); + setResourceNames(inputPath, outputPath); + ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, charset); + try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(inputBytes), charset)) { + applyZipStream(inputPath, zipInputStream, outputPath, zipOutputStream, discardSignatureFiles); + } finally { + zipOutputStream.finish(); + // stopRecording (for this second recording session) will be called inside the finally block of the caller + } + } catch (IOException e) { + throw new TransformException("Failed to complete output [ " + inputPath + " ]", e); + } + } else { + try { + ZipInputStream zipInputStream = new ZipInputStream(inputStream, charset); + ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, charset); + try { + applyZipStream(inputPath, zipInputStream, outputPath, zipOutputStream, false); + } finally { + zipOutputStream.finish(); + } + } catch (IOException e) { + throw new TransformException("Failed to complete output [ " + inputPath + " ]", e); } - - } catch (IOException e) { - throw new TransformException("Failed to complete output [ " + inputPath + " ]", e); } - } protected boolean isDuplicate( @@ -233,12 +262,15 @@ protected boolean isDuplicate( * @param zipInputStream An input stream for the input archive. * @param outputPath A name associated with the output stream. * @param zipOutputStream An output stream for the output archive. + * @param discardSignatureFiles True if any signature files found need to be discarded, false otherwise + * @return true if the zip-type archive is signed and has transformation changes, false otherwise * @throws TransformException Thrown if reading or writing the archives * fails, or if transformation of an entry fails. */ - private void applyZipStream( + private boolean applyZipStream( String inputPath, ZipInputStream zipInputStream, - String outputPath, ZipOutputStream zipOutputStream) throws TransformException { + String outputPath, ZipOutputStream zipOutputStream, + boolean discardSignatureFiles) throws TransformException { String className = getClass().getSimpleName(); String methodName = "apply"; @@ -260,7 +292,7 @@ private void applyZipStream( String prevName = null; String inputName = null; - final Map> signatureFilesMap = new HashMap(); + boolean isSigned = false; try { for ( ZipEntry inputEntry; @@ -270,14 +302,11 @@ private void applyZipStream( try { inputName = FileUtils.sanitize(inputEntry.getName()); // Avoid ZipSlip if (ElementAction.SIGNATURE_FILE_PATTERN.matcher(inputName).matches()) { - /* - * Signature file resource: Store its contents in memory until we have finished processing - * the input zip file. Only at that point will we know if any of its resources were modified, - * in which case the signature files will have been invalidated and must be discarded. - * Otherwise, they must be added back to the output zip file. - */ - signatureFilesMap.put(inputName, new AbstractMap.SimpleEntry(inputEntry, collect(inputName, zipInputStream))); - continue; + if (discardSignatureFiles) { + continue; + } else { + isSigned = true; + } } int inputLength = Math.toIntExact(inputEntry.getSize()); @@ -431,7 +460,9 @@ private void applyZipStream( useLogger.error("Transform failure [ {} ] of [ {} ]", inputName, inputPath, t); } } - handleSignatureFiles(signatureFilesMap, zipOutputStream, inputPath); + + return isSigned && getActiveChanges().isContentChanged(); + } catch (IOException e) { String message; if (inputName != null) { @@ -445,10 +476,6 @@ private void applyZipStream( message = "Failed to process first entry of [ " + inputPath + " ]"; } throw new TransformException(message, e); - } finally { - if (!signatureFilesMap.isEmpty()) { - signatureFilesMap.clear(); - } } } @@ -617,33 +644,72 @@ private void putEntry(ZipOutputStream zipOutputStream, ZipEntry outputEntry, Tra } /* - * Adds any signature (*.SF) and signature block (*.[RSA|DSA|EC]) files extracted from the input zip file - * to the output zip file. - * - * If any of the resources of the input zip file have been transformed, then the signature files are no longer valid - * and will be discarded. - * - * @param signatureFilesMap Map of signature files extracted from the input zip file, will be empty if the - * input zip file is unsigned - * @param zipOutputStream Output zip file - * @param inputPath Path to the input zip file (used for logging purposes only) + * ZipOutputStream which does nothing */ - private void handleSignatureFiles(Map> signatureFilesMap, - ZipOutputStream zipOutputStream, - String inputPath) throws IOException { - if (!signatureFilesMap.isEmpty()) { - if (!getActiveChanges().isContentChanged()) { - for (Map.Entry> entry : signatureFilesMap.entrySet()) { - if (getLogger().isInfoEnabled()) { - getLogger().info("Restoring signature file [ {} ] from unmodified [ {} ]", entry.getKey(), inputPath); - } - writeUnmodified(entry.getValue().getKey(), entry.getValue().getValue(), entry.getKey(), zipOutputStream); - } - } else { - if (getLogger().isInfoEnabled()) { - getLogger().info("Discarding all signature files from transformed [ {} ]", inputPath); - } - } + private static class NoopZipOutputStreamWrapper extends ZipOutputStream { + + public NoopZipOutputStreamWrapper() { + super(new ByteArrayOutputStream()); + } + + @Override + public void setComment(String comment) { + // Do nothing + } + + @Override + public void setMethod(int method) { + // Do nothing + } + + @Override + public void setLevel(int level) { + // Do nothing + } + + @Override + public void putNextEntry(ZipEntry e) { + // Do nothing + } + + @Override + public void closeEntry() { + // Do nothing + } + + @Override + public synchronized void write(byte[] b, int off, int len) { + // Do nothing + } + + @Override + public void finish() { + // Do nothing + } + + @Override + public void close() { + // Do nothing + } + + @Override + public void write(int b) { + // Do nothing + } + + @Override + protected void deflate() { + // Do nothing + } + + @Override + public void flush() { + // Do nothing + } + + @Override + public void write(byte[] b) { + // Do nothing } } } From eedd322114d02d4f054f00bcc08d7efa3c6ae7ff Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Mon, 5 Aug 2024 18:26:16 +0200 Subject: [PATCH 5/9] [ISSUE 606] Discard signatures from jar file that was mutated: Do not introduce dependency on Java 9 --- org.eclipse.transformer/pom.xml | 3 --- .../transformer/action/impl/ZipActionImpl.java | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/org.eclipse.transformer/pom.xml b/org.eclipse.transformer/pom.xml index 13bcb6bf..675469f2 100644 --- a/org.eclipse.transformer/pom.xml +++ b/org.eclipse.transformer/pom.xml @@ -25,9 +25,6 @@ Eclipse Transformer Library ${project.groupId}:${project.artifactId} https://projects.eclipse.org/projects/technology.transformer - - 9 - https://github.com/eclipse/transformer scm:git:https://github.com/eclipse/transformer.git diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java index 59fee509..2cb3ca24 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java @@ -192,9 +192,7 @@ private void applyStream(String inputPath, InputStream inputStream, String outpu if (actionType == JAR) { try { // "Clone" inputStream because we are going to consume it twice: in Round One and Two - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - inputStream.transferTo(baos); - byte[] inputBytes = baos.toByteArray(); + byte[] inputBytes = toByteArray(inputStream); // Round One: Determine if the jar file is signed and has transformation changes. // Does not output any transformation changes: this will be done in the Round Two @@ -643,6 +641,17 @@ private void putEntry(ZipOutputStream zipOutputStream, ZipEntry outputEntry, Tra } } + private static byte[] toByteArray(final InputStream is) throws IOException { + final byte[] tmp = new byte[8192]; + int n; + try (final ByteArrayOutputStream output = new ByteArrayOutputStream()) { + while ((n = is.read(tmp)) != -1) { + output.write(tmp, 0, n); + } + return output.toByteArray(); + } + } + /* * ZipOutputStream which does nothing */ From 7f7d105a2ac97a47ce0c21a6358a36287ea05020 Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Thu, 15 Aug 2024 16:22:28 +0200 Subject: [PATCH 6/9] [ISSUE 606] Introduce strip-signatures command-line option --- .../command-line/signed-jar-without-javax.jar | Bin 4903 -> 0 bytes .../transformer/test/TestCommandLine.java | 79 +++++---- .../org/eclipse/transformer/AppOption.java | 3 + .../org/eclipse/transformer/Transformer.java | 15 +- .../action/impl/ZipActionImpl.java | 158 +++--------------- .../test/TestTransformServiceConfig.java | 3 - 6 files changed, 80 insertions(+), 178 deletions(-) delete mode 100644 org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-without-javax.jar diff --git a/org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-without-javax.jar b/org.eclipse.transformer.cli/src/test/data/command-line/signed-jar-without-javax.jar deleted file mode 100644 index 3d4452f062356780d1a2da077141553e8ab06dab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4903 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 diff --git a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java index 66ab2b44..244975d8 100644 --- a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java +++ b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java @@ -137,48 +137,43 @@ void zip_entry_creation() throws Exception { } /* - * Tests that signature files have been discarded from signed jar file that was mutated by transformation. + * Tests that signature files have been discarded from signed jar file (regardless of whether it was mutated) + * when -s command-line option has been specified. * * signed-jar-with-javax.jar contains a single class file which references javax.servlet.Servlet. * The Java source code is included with the jar file. */ @Test - void testSignatureFilesStrippedFromTransformedJarFile() throws Exception { + void testSignatureFilesStripped() throws Exception { String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-jar-with-javax.jar"; String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-jar-with-javax.jar"; // Assert that signed input jar file contains 2 signature files: META-INF/MYKEY.SF and META-INF/MYKEY.DSA - Map sigFilesMap = extractSignatureFileEntries(inputFileName); - assertThat(sigFilesMap.size()).isEqualTo(2); - assertThat(sigFilesMap.containsKey("META-INF/MYKEY.SF")).isTrue(); - assertThat(sigFilesMap.containsKey("META-INF/MYKEY.DSA")).isTrue(); - verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); + Map inputJarSigFilesMap = extractSignatureFileEntries(inputFileName); + assertThat(inputJarSigFilesMap).containsOnlyKeys("META-INF/MYKEY.SF", "META-INF/MYKEY.DSA"); + verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName, true); // Assert that signature files have been removed from output jar file - assertThat(extractSignatureFileEntries(outputFileName).size()).isEqualTo(0); + assertThat(extractSignatureFileEntries(outputFileName)).isEmpty(); } /* - * Tests that signature files have been preserved in signed jar file that was left unmodified by transformation. + * Tests that signature files are preserved in signed jar file by default. * - * signed-jar-without-javax.jar contains a single class file without any javax references. + * signed-jar-with-javax.jar contains a single class file which references javax.servlet.Servlet. * The Java source code is included with the jar file. */ @Test - void testSignatureFilesPreservedInUnmodifiedJarFile() throws Exception { - String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-jar-without-javax.jar"; - String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-jar-without-javax.jar"; + void testSignatureFilesPreservedByDefault() throws Exception { + String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-jar-with-javax.jar"; + String outputFileName = DYNAMIC_CONTENT_DIR + "/unsigned-jar-with-javax.jar"; // Assert that signed input jar file contains 2 signature files: META-INF/MYKEY.SF and META-INF/MYKEY.DSA - Map inputJarSigFilesMap = extractSignatureFileEntries(inputFileName); - assertThat(inputJarSigFilesMap.size()).isEqualTo(2); - assertThat(inputJarSigFilesMap.containsKey("META-INF/MYKEY.SF")).isTrue(); - assertThat(inputJarSigFilesMap.containsKey("META-INF/MYKEY.DSA")).isTrue(); + Map inputJarSigFilesMap = extractSignatureFileEntries(inputFileName); + assertThat(inputJarSigFilesMap).containsOnlyKeys("META-INF/MYKEY.SF", "META-INF/MYKEY.DSA"); verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); // Assert that signature files have been preserved in output jar file - Map outputJarSigFilesMap = extractSignatureFileEntries(outputFileName); - assertThat(outputJarSigFilesMap.size()).isEqualTo(2); - assertThat(outputJarSigFilesMap.containsKey("META-INF/MYKEY.SF")).isTrue(); - assertThat(outputJarSigFilesMap.get("META-INF/MYKEY.SF").getSize()).isEqualTo(inputJarSigFilesMap.get("META-INF/MYKEY.SF").getSize()); - assertThat(outputJarSigFilesMap.containsKey("META-INF/MYKEY.DSA")).isTrue(); - assertThat(outputJarSigFilesMap.get("META-INF/MYKEY.DSA").getSize()).isEqualTo(inputJarSigFilesMap.get("META-INF/MYKEY.DSA").getSize()); + Map outputJarSigFilesMap = extractSignatureFileEntries(outputFileName); + assertThat(outputJarSigFilesMap).containsOnlyKeys("META-INF/MYKEY.SF", "META-INF/MYKEY.DSA"); + assertThat(outputJarSigFilesMap.get("META-INF/MYKEY.SF")).isEqualTo(inputJarSigFilesMap.get("META-INF/MYKEY.SF")); + assertThat(outputJarSigFilesMap.get("META-INF/MYKEY.DSA")).isEqualTo(inputJarSigFilesMap.get("META-INF/MYKEY.DSA")); } // Test zip with STORED archive to make sure ZipEntries are properly created. @@ -347,10 +342,20 @@ void testSetLogFile() throws Exception { } private void verifyAction(String actionClassName, String inputFileName, String outputFileName, String expectedOutputFileName) throws Exception { - verifyAction(actionClassName, inputFileName, outputFileName, expectedOutputFileName, 0); + verifyAction(actionClassName, inputFileName, outputFileName, expectedOutputFileName, false); + } + + private void verifyAction(String actionClassName, String inputFileName, String outputFileName, String expectedOutputFileName, + boolean stripSignatures) throws Exception { + verifyAction(actionClassName, inputFileName, outputFileName, expectedOutputFileName, 0, stripSignatures); } private void verifyAction(String actionClassName, String inputFileName, String outputFileName, String expectedOutputFileName, int duplicates) throws Exception { + verifyAction(actionClassName, inputFileName, outputFileName, expectedOutputFileName, duplicates, false); + } + + private void verifyAction(String actionClassName, String inputFileName, String outputFileName, String expectedOutputFileName, + int duplicates, boolean stripSignatures) throws Exception { System.out.printf("verifyAction: Input is: [%s] Output is: [%s]\n", inputFileName, outputFileName); String[] args = outputFileName != null ? new String[] { inputFileName, outputFileName, "-o" @@ -358,6 +363,13 @@ private void verifyAction(String actionClassName, String inputFileName, String o inputFileName, "-o" }; + if (stripSignatures) { + String[] moreArgs = new String[args.length + 1]; + System.arraycopy(args, 0, moreArgs, 0, args.length); + moreArgs[args.length] = "-s"; + args = moreArgs; + } + TransformerCLI cli = new JakartaTransformerCLI(System.out, System.err, args); Transformer transformer = new Transformer(cli.getLogger(), cli); @@ -421,16 +433,17 @@ private void verifyActionInvalidDirectoryRejected(String actionClassName, String .isFalse(); } - private static Map extractSignatureFileEntries(String zipFilePath) throws IOException { - final ZipFile zipFile = new ZipFile(zipFilePath); - final Enumeration entries = zipFile.entries(); - final Map signatureFilesMap = new HashMap(); - while (entries.hasMoreElements()) { - final ZipEntry zipEntry = entries.nextElement(); - if (ElementAction.SIGNATURE_FILE_PATTERN.matcher(zipEntry.getName()).matches()) { - signatureFilesMap.put(zipEntry.getName(), zipEntry); + private static Map extractSignatureFileEntries(String zipFilePath) throws IOException { + try (ZipFile zipFile = new ZipFile(zipFilePath)) { + final Enumeration entries = zipFile.entries(); + final Map signatureFilesMap = new HashMap(); + while (entries.hasMoreElements()) { + final ZipEntry zipEntry = entries.nextElement(); + if (ElementAction.SIGNATURE_FILE_PATTERN.matcher(zipEntry.getName()).matches()) { + signatureFilesMap.put(zipEntry.getName(), IO.read(zipFile.getInputStream(zipEntry))); + } } + return signatureFilesMap; } - return signatureFilesMap; } } diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/AppOption.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/AppOption.java index a44c27f3..ff52609b 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/AppOption.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/AppOption.java @@ -77,6 +77,9 @@ public enum AppOption { INVERT(new Settings("i", "invert", "Invert transformation rules", !Settings.HAS_ARG, !Settings.HAS_ARGS, !Settings.IS_REQUIRED, Settings.NO_GROUP)), + STRIP_SIGNATURES(new Settings("s", "strip-signatures", "Strips signatures from signed jar files", !Settings.HAS_ARG, !Settings.HAS_ARGS, + !Settings.IS_REQUIRED, Settings.NO_GROUP)), + FILE_TYPE(new Settings("t", "type", "Input file type", Settings.HAS_ARG, !Settings.HAS_ARGS, !Settings.IS_REQUIRED, Settings.NO_GROUP)), OVERWRITE(new Settings("o", "overwrite", "Overwrite", !Settings.HAS_ARG, !Settings.HAS_ARGS, !Settings.IS_REQUIRED, diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/Transformer.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/Transformer.java index 67459c8e..f953079b 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/Transformer.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/Transformer.java @@ -1135,11 +1135,16 @@ public ActionSelector getActionSelector() { standardActions.add(propertiesAction); // after text so text can supersede standardActions.add(xmlAction); // after text so text can supersede - ContainerAction jarAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.JAR), context); - ContainerAction warAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.WAR), context); - ContainerAction rarAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.RAR), context); - ContainerAction earAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.EAR), context); - ContainerAction zipAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.ZIP), context); + ContainerAction jarAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.JAR, + options.hasOption(AppOption.STRIP_SIGNATURES)), context); + ContainerAction warAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.WAR, + options.hasOption(AppOption.STRIP_SIGNATURES)), context); + ContainerAction rarAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.RAR, + options.hasOption(AppOption.STRIP_SIGNATURES)), context); + ContainerAction earAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.EAR, + options.hasOption(AppOption.STRIP_SIGNATURES)), context); + ContainerAction zipAction = useSelector.addUsing(c -> new ZipActionImpl(c, ActionType.ZIP, + options.hasOption(AppOption.STRIP_SIGNATURES)), context); Action renameAction = useSelector.addUsing(RenameActionImpl::new, context); diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java index 2cb3ca24..6f9b7364 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java @@ -11,8 +11,6 @@ package org.eclipse.transformer.action.impl; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -39,8 +37,6 @@ import org.eclipse.transformer.util.FileUtils; import org.slf4j.Logger; -import static org.eclipse.transformer.action.ActionType.*; - /** * Top level ZIP action. A ZIP action is a container action which iterates * across the ZIP file elements, selecting and applying actions on each of these @@ -57,11 +53,17 @@ public class ZipActionImpl extends ContainerActionImpl implements ElementAction { public ZipActionImpl(ActionContext context, ActionType actionType) { + this(context, actionType, false); + } + + public ZipActionImpl(ActionContext context, ActionType actionType, boolean stripSignatures) { super(context); this.actionType = actionType; + this.stripSignatures = stripSignatures; } private final ActionType actionType; + private final boolean stripSignatures; @Override public ActionType getActionType() { @@ -189,46 +191,20 @@ private void applyStream(String inputPath, InputStream inputStream, String outpu Charset charset = resourceCharset(inputPath); getLogger().debug("Zip Charset [ {} ]: {}", inputPath, charset); - if (actionType == JAR) { - try { - // "Clone" inputStream because we are going to consume it twice: in Round One and Two - byte[] inputBytes = toByteArray(inputStream); - - // Round One: Determine if the jar file is signed and has transformation changes. - // Does not output any transformation changes: this will be done in the Round Two - boolean discardSignatureFiles; - try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(inputBytes), charset)) { - discardSignatureFiles = applyZipStream(inputPath, zipInputStream, outputPath, new NoopZipOutputStreamWrapper(), false); - } finally { - stopRecording(inputPath); - } - // Round Two: Use the information obtained in Round One: If the jar file is signed and has - // transformation changes, its signature files must be discarded because they are no longer invalid - startRecording(inputPath); - setResourceNames(inputPath, outputPath); - ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, charset); - try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(inputBytes), charset)) { - applyZipStream(inputPath, zipInputStream, outputPath, zipOutputStream, discardSignatureFiles); - } finally { - zipOutputStream.finish(); - // stopRecording (for this second recording session) will be called inside the finally block of the caller - } - } catch (IOException e) { - throw new TransformException("Failed to complete output [ " + inputPath + " ]", e); - } - } else { + try { + ZipInputStream zipInputStream = new ZipInputStream(inputStream, charset); + + ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, charset); try { - ZipInputStream zipInputStream = new ZipInputStream(inputStream, charset); - ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, charset); - try { - applyZipStream(inputPath, zipInputStream, outputPath, zipOutputStream, false); - } finally { - zipOutputStream.finish(); - } - } catch (IOException e) { - throw new TransformException("Failed to complete output [ " + inputPath + " ]", e); + applyZipStream(inputPath, zipInputStream, outputPath, zipOutputStream); + } finally { + zipOutputStream.finish(); } + + } catch (IOException e) { + throw new TransformException("Failed to complete output [ " + inputPath + " ]", e); } + } protected boolean isDuplicate( @@ -260,15 +236,12 @@ protected boolean isDuplicate( * @param zipInputStream An input stream for the input archive. * @param outputPath A name associated with the output stream. * @param zipOutputStream An output stream for the output archive. - * @param discardSignatureFiles True if any signature files found need to be discarded, false otherwise - * @return true if the zip-type archive is signed and has transformation changes, false otherwise * @throws TransformException Thrown if reading or writing the archives * fails, or if transformation of an entry fails. */ - private boolean applyZipStream( + private void applyZipStream( String inputPath, ZipInputStream zipInputStream, - String outputPath, ZipOutputStream zipOutputStream, - boolean discardSignatureFiles) throws TransformException { + String outputPath, ZipOutputStream zipOutputStream) throws TransformException { String className = getClass().getSimpleName(); String methodName = "apply"; @@ -290,8 +263,6 @@ private boolean applyZipStream( String prevName = null; String inputName = null; - boolean isSigned = false; - try { for ( ZipEntry inputEntry; (inputEntry = zipInputStream.getNextEntry()) != null; @@ -299,12 +270,8 @@ private boolean applyZipStream( try { inputName = FileUtils.sanitize(inputEntry.getName()); // Avoid ZipSlip - if (ElementAction.SIGNATURE_FILE_PATTERN.matcher(inputName).matches()) { - if (discardSignatureFiles) { - continue; - } else { - isSigned = true; - } + if (stripSignatures && ElementAction.SIGNATURE_FILE_PATTERN.matcher(inputName).matches()) { + continue; } int inputLength = Math.toIntExact(inputEntry.getSize()); @@ -459,8 +426,6 @@ private boolean applyZipStream( } } - return isSigned && getActiveChanges().isContentChanged(); - } catch (IOException e) { String message; if (inputName != null) { @@ -640,85 +605,4 @@ private void putEntry(ZipOutputStream zipOutputStream, ZipEntry outputEntry, Tra zipOutputStream.closeEntry(); // throws IOException } } - - private static byte[] toByteArray(final InputStream is) throws IOException { - final byte[] tmp = new byte[8192]; - int n; - try (final ByteArrayOutputStream output = new ByteArrayOutputStream()) { - while ((n = is.read(tmp)) != -1) { - output.write(tmp, 0, n); - } - return output.toByteArray(); - } - } - - /* - * ZipOutputStream which does nothing - */ - private static class NoopZipOutputStreamWrapper extends ZipOutputStream { - - public NoopZipOutputStreamWrapper() { - super(new ByteArrayOutputStream()); - } - - @Override - public void setComment(String comment) { - // Do nothing - } - - @Override - public void setMethod(int method) { - // Do nothing - } - - @Override - public void setLevel(int level) { - // Do nothing - } - - @Override - public void putNextEntry(ZipEntry e) { - // Do nothing - } - - @Override - public void closeEntry() { - // Do nothing - } - - @Override - public synchronized void write(byte[] b, int off, int len) { - // Do nothing - } - - @Override - public void finish() { - // Do nothing - } - - @Override - public void close() { - // Do nothing - } - - @Override - public void write(int b) { - // Do nothing - } - - @Override - protected void deflate() { - // Do nothing - } - - @Override - public void flush() { - // Do nothing - } - - @Override - public void write(byte[] b) { - // Do nothing - } - } } diff --git a/org.eclipse.transformer/src/test/java/transformer/test/TestTransformServiceConfig.java b/org.eclipse.transformer/src/test/java/transformer/test/TestTransformServiceConfig.java index b174819b..36047889 100644 --- a/org.eclipse.transformer/src/test/java/transformer/test/TestTransformServiceConfig.java +++ b/org.eclipse.transformer/src/test/java/transformer/test/TestTransformServiceConfig.java @@ -30,7 +30,6 @@ import org.eclipse.transformer.action.BundleData; import org.eclipse.transformer.action.ByteData; import org.eclipse.transformer.action.impl.ActionContextImpl; -import org.eclipse.transformer.action.impl.ActionSelectorImpl; import org.eclipse.transformer.action.impl.PropertiesActionImpl; import org.eclipse.transformer.action.impl.SelectionRuleImpl; import org.eclipse.transformer.action.impl.ServiceLoaderConfigActionImpl; @@ -190,8 +189,6 @@ public ZipActionImpl getJarJavaxServiceAction() { Map invertedRenames = TransformProperties.invert(getPackageRenames()); - ActionSelectorImpl actionSelector = new ActionSelectorImpl(); - ActionContext context = new ActionContextImpl(useLogger, createSelectionRule(useLogger, Collections.emptyMap(), getExcludes()), createSignatureRule(useLogger, invertedRenames, null, null, null)); From 2ccce67f3e874a0848a4f84b97b48aedcdaae70b Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Fri, 16 Aug 2024 22:37:19 +0200 Subject: [PATCH 7/9] [ISSUE 606] transformer-maven-plugin integration --- .../bnd/analyzer/BndAnalyzerPluginTest.java | 6 ++++++ .../transformer-maven-plugin/README.md | 3 ++- .../maven/AbstractTransformerMojo.java | 2 +- .../maven/TransformerMojoOptions.java | 3 +++ .../maven/configuration/TransformerRules.java | 19 +++++++++++++++++-- .../transformer/test/TestCommandLine.java | 16 ++++++---------- .../action/impl/ZipActionImpl.java | 4 ---- .../transformer/test/TestTransformClass.java | 10 +++++----- .../test/TestTransformServiceConfig.java | 2 +- 9 files changed, 41 insertions(+), 24 deletions(-) diff --git a/bnd-plugins/org.eclipse.transformer.bnd.analyzer/src/test/java/org/eclipse/transformer/bnd/analyzer/BndAnalyzerPluginTest.java b/bnd-plugins/org.eclipse.transformer.bnd.analyzer/src/test/java/org/eclipse/transformer/bnd/analyzer/BndAnalyzerPluginTest.java index 059af680..04e4ca54 100644 --- a/bnd-plugins/org.eclipse.transformer.bnd.analyzer/src/test/java/org/eclipse/transformer/bnd/analyzer/BndAnalyzerPluginTest.java +++ b/bnd-plugins/org.eclipse.transformer.bnd.analyzer/src/test/java/org/eclipse/transformer/bnd/analyzer/BndAnalyzerPluginTest.java @@ -230,6 +230,7 @@ void options_test() throws Exception { try (Processor processor = new Processor()) { processor.setProperty("-transformer", "tb;arg=value1"); processor.setProperty("-transformer.overwrite", "overwrite"); + processor.setProperty("-transformer.stripSignatures", "strip-signatures"); processor.setProperty("-transformer.morebundles", "bundles;arg=value2"); processor.setProperty("-transformer.immediate", "immediate;option=tv;package=org.eclipse.transformer.test.api;version=\"[2.0\\,3.0);Export-Package=2.0.0\""); @@ -253,6 +254,11 @@ void options_test() throws Exception { assertThat(options.getOptionValues(AppOption.WIDEN_ARCHIVE_NESTING)).isNull(); assertThat(options.getOptionValue(AppOption.WIDEN_ARCHIVE_NESTING)).isNull(); + assertThat(options.getDefaultValue(AppOption.STRIP_SIGNATURES)).isNull(); + assertThat(options.hasOption(AppOption.STRIP_SIGNATURES)).isTrue(); + assertThat(options.getOptionValues(AppOption.STRIP_SIGNATURES)).isEmpty(); + assertThat(options.getOptionValue(AppOption.STRIP_SIGNATURES)).isNull(); + assertThat(options.getOptionValues(AppOption.RULES_IMMEDIATE_DATA)).containsExactly("tv", "org.eclipse.transformer.test.api", "[2.0\\,3.0);Export-Package=2.0.0"); } diff --git a/maven-plugins/transformer-maven-plugin/README.md b/maven-plugins/transformer-maven-plugin/README.md index 2138fd57..9178d0cd 100644 --- a/maven-plugins/transformer-maven-plugin/README.md +++ b/maven-plugins/transformer-maven-plugin/README.md @@ -51,8 +51,9 @@ A list value of `-` (Hyphen-minus) is ignored. This can be used to configure an |`immediates` | A list of immediate options. | |`invert` | If `true`, invert the rename rules. _Defaults to `false`_. | |`overwrite` | If `true`, the items which transform to the same path as an existing item overwrite the existing item. _Defaults to `false`_. | -|`widen` | If `true`, By default, archive nesting is restricted to JavaEE active locations. This may be relaxed to enable JAR and ZIP within JAR, ZIP within ZIP, and ZIP within EAR, WAR, and RAR. _Defaults to `false`_. | +|`widen` | If `true`, by default, archive nesting is restricted to JavaEE active locations. This may be relaxed to enable JAR and ZIP within JAR, ZIP within ZIP, and ZIP within EAR, WAR, and RAR. _Defaults to `false`_. | |`jakartaDefaults` | If `true`, the Jakarta rule defaults are included. _Defaults to `false`_. | +|`strip-signatures` | If `true`, signature files will be removed from signed JAR files. _Defaults to `false`_. | ```xml diff --git a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/AbstractTransformerMojo.java b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/AbstractTransformerMojo.java index cf5c2029..63bb3fee 100644 --- a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/AbstractTransformerMojo.java +++ b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/AbstractTransformerMojo.java @@ -58,7 +58,7 @@ public abstract class AbstractTransformerMojo extends AbstractMojo { *

* The rules configuration includes: selections, renames, versions, bundles, * directs, texts, perClassConstants, immediates, invert, overwrite, widen, - * and jakartaDefaults. + * jakartaDefaults, and stripSignatures. */ @Parameter private TransformerRules rules = new TransformerRules(); diff --git a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerMojoOptions.java b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerMojoOptions.java index 2dc5fb5b..ee09d92b 100644 --- a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerMojoOptions.java +++ b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerMojoOptions.java @@ -109,6 +109,9 @@ public boolean hasOption(AppOption option) { case WIDEN_ARCHIVE_NESTING : has = rules.isWiden(); break; + case STRIP_SIGNATURES : + has = rules.isStripSignatures(); + break; default : has = TransformOptions.super.hasOption(option); break; diff --git a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/configuration/TransformerRules.java b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/configuration/TransformerRules.java index f4cc6bab..6a60df69 100644 --- a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/configuration/TransformerRules.java +++ b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/configuration/TransformerRules.java @@ -29,6 +29,7 @@ public class TransformerRules { private boolean overwrite; private boolean widen; private boolean jakartaDefaults; + private boolean stripSignatures; public TransformerRules() {} @@ -134,12 +135,26 @@ public void setWiden(boolean widen) { this.widen = widen; } + /** + * @return the stripSignatures + */ + public boolean isStripSignatures() { + return stripSignatures; + } + + /** + * @param stripSignatures the stripSignatures to set + */ + public void setStripSignatures(boolean stripSignatures) { + this.stripSignatures = stripSignatures; + } + @Override public String toString() { return String.format( - "selections=%s, renames=%s, versions=%s, bundles=%s, directs=%s, texts=%s, perClassConstants=%s, immediates=%s, invert=%s, overwrite=%s, widen=%s, jakartaDefaults=%s", + "selections=%s, renames=%s, versions=%s, bundles=%s, directs=%s, texts=%s, perClassConstants=%s, immediates=%s, invert=%s, overwrite=%s, widen=%s, jakartaDefaults=%s, stripSignatures=%s", getSelections(), getRenames(), getVersions(), getBundles(), getDirects(), getTexts(), - getPerClassConstants(), getImmediates(), isInvert(), isOverwrite(), isWiden(), isJakartaDefaults()); + getPerClassConstants(), getImmediates(), isInvert(), isOverwrite(), isWiden(), isJakartaDefaults(), isStripSignatures()); } } diff --git a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java index 244975d8..69537555 100644 --- a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java +++ b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java @@ -83,14 +83,14 @@ public void tearDown() { void testManifestActionAccepted() throws Exception { String inputFileName = STATIC_CONTENT_DIR + "/command-line/MANIFEST.MF"; String outputFileName = DYNAMIC_CONTENT_DIR + "/MANIFEST.MF"; - verifyAction(ManifestActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); + verifyAction(ManifestActionImpl.class.getName(), inputFileName, outputFileName, outputFileName, false); } @Test void testJavaActionAccepted() throws Exception { String inputFileName = STATIC_CONTENT_DIR + "/command-line/A.java"; String outputFileName = DYNAMIC_CONTENT_DIR + "/A.java"; - verifyAction(JavaActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); + verifyAction(JavaActionImpl.class.getName(), inputFileName, outputFileName, outputFileName, false); } @Test @@ -117,7 +117,7 @@ void testInputFileNameOnlyAccepted() throws Exception { String inputFileName = inputFile.getCanonicalPath().replace(File.separatorChar, '/'); String expectedOutputFileName = new File(inputFile.getParentFile(), Transformer.OUTPUT_PREFIX + inputFile.getName()).getCanonicalPath() .replace(File.separatorChar, '/'); - verifyAction(JavaActionImpl.class.getName(), inputFileName, null, expectedOutputFileName); + verifyAction(JavaActionImpl.class.getName(), inputFileName, null, expectedOutputFileName, false); } @Test @@ -133,7 +133,7 @@ void testInvalidOutputDirectoryRejected() throws Exception { void zip_entry_creation() throws Exception { String inputFileName = STATIC_CONTENT_DIR + "/command-line/sac-1.3.jar"; String outputFileName = DYNAMIC_CONTENT_DIR + "/sac-1.3.jar"; - verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); + verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName, false); } /* @@ -168,7 +168,7 @@ void testSignatureFilesPreservedByDefault() throws Exception { // Assert that signed input jar file contains 2 signature files: META-INF/MYKEY.SF and META-INF/MYKEY.DSA Map inputJarSigFilesMap = extractSignatureFileEntries(inputFileName); assertThat(inputJarSigFilesMap).containsOnlyKeys("META-INF/MYKEY.SF", "META-INF/MYKEY.DSA"); - verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); + verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName, false); // Assert that signature files have been preserved in output jar file Map outputJarSigFilesMap = extractSignatureFileEntries(outputFileName); assertThat(outputJarSigFilesMap).containsOnlyKeys("META-INF/MYKEY.SF", "META-INF/MYKEY.DSA"); @@ -181,7 +181,7 @@ void testSignatureFilesPreservedByDefault() throws Exception { void zip_nested_stored_archive() throws Exception { String inputFileName = STATIC_CONTENT_DIR + "/command-line/nested_stored_archive.war"; String outputFileName = DYNAMIC_CONTENT_DIR + "/nested_stored_archive.war"; - verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName); + verifyAction(ZipActionImpl.class.getName(), inputFileName, outputFileName, outputFileName, false); } // Test zip with entry names encoded with a charset other than UTF-8. @@ -341,10 +341,6 @@ void testSetLogFile() throws Exception { } - private void verifyAction(String actionClassName, String inputFileName, String outputFileName, String expectedOutputFileName) throws Exception { - verifyAction(actionClassName, inputFileName, outputFileName, expectedOutputFileName, false); - } - private void verifyAction(String actionClassName, String inputFileName, String outputFileName, String expectedOutputFileName, boolean stripSignatures) throws Exception { verifyAction(actionClassName, inputFileName, outputFileName, expectedOutputFileName, 0, stripSignatures); diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java index 6f9b7364..e90031e7 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/action/impl/ZipActionImpl.java @@ -52,10 +52,6 @@ */ public class ZipActionImpl extends ContainerActionImpl implements ElementAction { - public ZipActionImpl(ActionContext context, ActionType actionType) { - this(context, actionType, false); - } - public ZipActionImpl(ActionContext context, ActionType actionType, boolean stripSignatures) { super(context); this.actionType = actionType; diff --git a/org.eclipse.transformer/src/test/java/transformer/test/TestTransformClass.java b/org.eclipse.transformer/src/test/java/transformer/test/TestTransformClass.java index 4a4800b4..e7aaae33 100644 --- a/org.eclipse.transformer/src/test/java/transformer/test/TestTransformClass.java +++ b/org.eclipse.transformer/src/test/java/transformer/test/TestTransformClass.java @@ -345,7 +345,7 @@ public ZipActionImpl getJavaxToJakartaJarAction() { createSelectionRule(useLogger, getIncludes(), getExcludes()), createSignatureRule(useLogger, getToJakartaRenames(), null, null, null, Collections.emptyMap())); - toJakartaJarAction = new ZipActionImpl(context, ActionType.JAR); + toJakartaJarAction = new ZipActionImpl(context, ActionType.JAR, false); } return toJakartaJarAction; @@ -363,7 +363,7 @@ public ZipActionImpl getJakartaToJavaxJarAction() { createSelectionRule(useLogger, getIncludes(), getExcludes()), createSignatureRule(useLogger, toJavaxRenames, null, null, null, Collections.emptyMap())); - toJavaxJarAction = new ZipActionImpl(context, ActionType.JAR); + toJavaxJarAction = new ZipActionImpl(context, ActionType.JAR, false); } return toJavaxJarAction; @@ -379,7 +379,7 @@ public ZipActionImpl getJavaxToJakartaJarAction_DirectOverride() { createSelectionRule(useLogger, getOverrideIncludes(), getExcludes()), createSignatureRule(useLogger, getToJakartaRenames(), null, null, toJakartaDirectStrings(), null)); - toJakartaJarAction_DirectOverride = new ZipActionImpl(context, ActionType.JAR); + toJakartaJarAction_DirectOverride = new ZipActionImpl(context, ActionType.JAR, false); } return toJakartaJarAction_DirectOverride; @@ -396,7 +396,7 @@ public ZipActionImpl getJavaxToJakartaJarAction_PerClassDirectOverride() { createSignatureRule(useLogger, getToJakartaRenames(), null, null, toJakartaDirectStrings(), toJakartaPerClassDirectStrings())); - toJakartaJarAction_PerClassDirectOverride = new ZipActionImpl(context, ActionType.JAR); + toJakartaJarAction_PerClassDirectOverride = new ZipActionImpl(context, ActionType.JAR, false); } return toJakartaJarAction_PerClassDirectOverride; @@ -412,7 +412,7 @@ public ZipActionImpl getJavaxToJakartaJarAction_PackageRenamesOnly() { createSelectionRule(useLogger, getOverrideIncludes(), getExcludes()), createSignatureRule(useLogger, getToJakartaRenames(), null, null, null, null)); - toJakartaJarAction_PackageRenamesOnly = new ZipActionImpl(context, ActionType.JAR); + toJakartaJarAction_PackageRenamesOnly = new ZipActionImpl(context, ActionType.JAR, false); } return toJakartaJarAction_PackageRenamesOnly; diff --git a/org.eclipse.transformer/src/test/java/transformer/test/TestTransformServiceConfig.java b/org.eclipse.transformer/src/test/java/transformer/test/TestTransformServiceConfig.java index 36047889..565e6d1d 100644 --- a/org.eclipse.transformer/src/test/java/transformer/test/TestTransformServiceConfig.java +++ b/org.eclipse.transformer/src/test/java/transformer/test/TestTransformServiceConfig.java @@ -193,7 +193,7 @@ public ZipActionImpl getJarJavaxServiceAction() { createSelectionRule(useLogger, Collections.emptyMap(), getExcludes()), createSignatureRule(useLogger, invertedRenames, null, null, null)); - jarJavaxServiceAction = new ZipActionImpl(context, ActionType.JAR); + jarJavaxServiceAction = new ZipActionImpl(context, ActionType.JAR, false); jarJavaxServiceAction.addUsing(PropertiesActionImpl::new); jarJavaxServiceAction.addUsing(ServiceLoaderConfigActionImpl::new); } From 94e34c859ba7fadf5ec0d2da3e183c8a8dcb88ea Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Sun, 18 Aug 2024 13:13:25 +0200 Subject: [PATCH 8/9] [ISSUE 606] Added transformer-maven-plugin integration test --- .../transformer/maven/TransformerRunMojo.java | 8 + .../maven/TransformerRunMojoTest.java | 103 +++++- .../META-INF/MYKEY.DSA | Bin 0 -> 1500 bytes .../META-INF/MYKEY.SF | 297 ++++++++++++++++++ .../transform-build-jar-artifact/pom.xml | 66 ++++ .../transformer/test/TestCommandLine.java | 2 +- 6 files changed, 464 insertions(+), 12 deletions(-) create mode 100644 maven-plugins/transformer-maven-plugin/src/test/projects/transform-build-jar-artifact/META-INF/MYKEY.DSA create mode 100644 maven-plugins/transformer-maven-plugin/src/test/projects/transform-build-jar-artifact/META-INF/MYKEY.SF create mode 100644 maven-plugins/transformer-maven-plugin/src/test/projects/transform-build-jar-artifact/pom.xml diff --git a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerRunMojo.java b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerRunMojo.java index b5b0d8f8..98e6084d 100644 --- a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerRunMojo.java +++ b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerRunMojo.java @@ -59,6 +59,9 @@ public class TransformerRunMojo extends AbstractMojo { @Parameter(defaultValue = "true", property = "transformer-plugin.overwrite", required = true) private boolean overwrite; + @Parameter(defaultValue = "false", property = "transformer-plugin.strip-signatures", required = false) + private boolean stripSignatures; + @Parameter(defaultValue = "true", property = "transformer-plugin.attach", required = true) private boolean attach; @@ -131,6 +134,8 @@ public boolean hasOption(AppOption option) { return overwrite; case INVERT : return invert; + case STRIP_SIGNATURES: + return stripSignatures; default : return TransformOptions.super.hasOption(option); } @@ -257,4 +262,7 @@ void setAttach(boolean attach) { this.attach = attach; } + void setStripSignatures(boolean stripSignatures) { + this.stripSignatures = stripSignatures; + } } diff --git a/maven-plugins/transformer-maven-plugin/src/test/java/org/eclipse/transformer/maven/TransformerRunMojoTest.java b/maven-plugins/transformer-maven-plugin/src/test/java/org/eclipse/transformer/maven/TransformerRunMojoTest.java index 1d7e0910..c7c241ac 100644 --- a/maven-plugins/transformer-maven-plugin/src/test/java/org/eclipse/transformer/maven/TransformerRunMojoTest.java +++ b/maven-plugins/transformer-maven-plugin/src/test/java/org/eclipse/transformer/maven/TransformerRunMojoTest.java @@ -11,6 +11,7 @@ package org.eclipse.transformer.maven; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -18,8 +19,12 @@ import java.io.File; import java.io.IOException; +import java.util.Enumeration; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; @@ -28,8 +33,12 @@ import org.apache.maven.plugin.testing.stubs.DefaultArtifactHandlerStub; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; +import org.eclipse.transformer.action.ElementAction; +import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.FileAsset; import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Rule; import org.junit.Test; @@ -66,7 +75,7 @@ public void testProjectArtifactTransformerPlugin() throws Exception { final MavenProject mavenProject = createMavenProject(modelDirectory, pom, "war", "rest-sample"); mavenProject.getArtifact() - .setFile(createService()); + .setFile(createService("war", targetDirectory)); mojo.setProject(mavenProject); mojo.setClassifier("transformed"); @@ -113,11 +122,11 @@ public void testMultipleArtifactTransformerPlugin() throws Exception { mojo.setOutputDirectory(tmp.newFolder(name.getMethodName())); mojo.getProjectHelper() - .attachArtifact(mavenProject, "zip", "test1", createService()); + .attachArtifact(mavenProject, "zip", "test1", createService("war", targetDirectory)); mojo.getProjectHelper() - .attachArtifact(mavenProject, "zip", "test2", createService()); + .attachArtifact(mavenProject, "zip", "test2", createService("war", targetDirectory)); mojo.getProjectHelper() - .attachArtifact(mavenProject, "zip", "test3", createService()); + .attachArtifact(mavenProject, "zip", "test3", createService("war", targetDirectory)); final Artifact[] sourceArtifacts = mojo.getSourceArtifacts(); assertEquals(3, sourceArtifacts.length); @@ -171,7 +180,7 @@ public void testProjectArtifactTransformerPluginNoAttach() throws Exception { final MavenProject mavenProject = createMavenProject(modelDirectory, pom, "war", "rest-sample"); mavenProject.getArtifact() - .setFile(createService()); + .setFile(createService("war", targetDirectory)); mojo.setProject(mavenProject); mojo.setClassifier("transformed"); @@ -190,6 +199,53 @@ public void testProjectArtifactTransformerPluginNoAttach() throws Exception { .size()); } + @Test + public void testProjectArtifactTransformerPluginStripSignatureFiles() throws Exception { + final TransformerRunMojo mojo = new TransformerRunMojo(); + mojo.setProjectHelper(this.rule.lookup(MavenProjectHelper.class)); + mojo.setOverwrite(true); + mojo.setOutputDirectory(tmp.newFolder(name.getMethodName())); + mojo.setAttach(true); + mojo.setStripSignatures(true); + + assertNotNull(mojo); + + final File targetDirectory = this.resources.getBasedir("transform-build-jar-artifact"); + final File modelDirectory = new File(targetDirectory, "target/model"); + final File pom = new File(targetDirectory, "pom.xml"); + + final MavenProject mavenProject = createMavenProject(modelDirectory, pom, "jar", "rest-sample"); + mavenProject.getArtifact() + .setFile(createService("jar", targetDirectory)); + + mojo.setProject(mavenProject); + mojo.setClassifier("transformed"); + + final Artifact[] sourceArtifacts = mojo.getSourceArtifacts(); + assertEquals(1, sourceArtifacts.length); + final Artifact sourceArtifact = sourceArtifacts[0]; + assertEquals("org.superbiz.rest", sourceArtifact.getGroupId()); + assertEquals("rest-sample", sourceArtifact.getArtifactId()); + assertEquals("1.0-SNAPSHOT", sourceArtifact.getVersion()); + assertEquals("jar", sourceArtifact.getType()); + assertNull(sourceArtifact.getClassifier()); + assertThat(getSignatureFileEntries(sourceArtifact.getFile())).containsExactly("META-INF/MYKEY.SF", "META-INF/MYKEY.DSA"); + + mojo.transform(sourceArtifacts[0]); + + assertEquals(1, mavenProject.getAttachedArtifacts() + .size()); + final Artifact transformedArtifact = mavenProject.getAttachedArtifacts() + .get(0); + + assertEquals("org.superbiz.rest", transformedArtifact.getGroupId()); + assertEquals("rest-sample", transformedArtifact.getArtifactId()); + assertEquals("1.0-SNAPSHOT", transformedArtifact.getVersion()); + assertEquals("jar", transformedArtifact.getType()); + assertEquals("transformed", transformedArtifact.getClassifier()); + assertThat(getSignatureFileEntries(transformedArtifact.getFile())).isEmpty(); + } + public MavenProject createMavenProject(final File modelDirectory, final File pom, final String packaging, final String artfifactId) { final MavenProject mavenProject = new MavenProject(); @@ -205,19 +261,44 @@ public MavenProject createMavenProject(final File modelDirectory, final File pom .setOutputDirectory(modelDirectory.getAbsolutePath()); mavenProject.setArtifact( new DefaultArtifact(mavenProject.getGroupId(), mavenProject.getArtifactId(), mavenProject.getVersion(), - (String) null, "war", (String) null, new DefaultArtifactHandlerStub(packaging, null))); + null, packaging, null, new DefaultArtifactHandlerStub(packaging, null))); return mavenProject; } - public File createService() throws IOException { - final File tempFile = File.createTempFile("service", ".war"); + private static File createService(String packaging, File targetDirectory) throws IOException { + final File tempFile = File.createTempFile("service", "." + packaging); tempFile.delete(); - final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "service.war") - .addClass(EchoService.class); + final Archive archive; + if (packaging.equals("jar")) { + archive = ShrinkWrap.create(JavaArchive.class, "service." + packaging) + .addClass(EchoService.class); + } else { + archive = ShrinkWrap.create(WebArchive.class, "service." + packaging) + .addClass(EchoService.class); + } - webArchive.as(ZipExporter.class) + if (packaging.equals("jar")) { + archive.add(new FileAsset(new File(targetDirectory, "META-INF/MYKEY.SF")), "META-INF/MYKEY.SF"); + archive.add(new FileAsset(new File(targetDirectory, "META-INF/MYKEY.DSA")), "META-INF/MYKEY.DSA"); + } + + archive.as(ZipExporter.class) .exportTo(tempFile, true); return tempFile; } + + private static Set getSignatureFileEntries(File file) throws IOException { + try (ZipFile zipFile = new ZipFile(file)) { + final Enumeration entries = zipFile.entries(); + final Set signatureFiles = new HashSet<>(); + while (entries.hasMoreElements()) { + final ZipEntry zipEntry = entries.nextElement(); + if (ElementAction.SIGNATURE_FILE_PATTERN.matcher(zipEntry.getName()).matches()) { + signatureFiles.add(zipEntry.getName()); + } + } + return signatureFiles; + } + } } diff --git a/maven-plugins/transformer-maven-plugin/src/test/projects/transform-build-jar-artifact/META-INF/MYKEY.DSA b/maven-plugins/transformer-maven-plugin/src/test/projects/transform-build-jar-artifact/META-INF/MYKEY.DSA new file mode 100644 index 0000000000000000000000000000000000000000..f66e7f4b1e09483bf7204559a2d94d7041a2f87e GIT binary patch literal 1500 zcmXqLV!gq}snzDu_MMlJooPW6>q&zq)}u^}jE4LMylk8aZ61uN%q&cdtPBR+2!)Ib znpjR7G_f29;^GC&OpHuSED}AsYmu}tGXb@v8FCwNvN4CUun9AT2E#ZU!c5MNhJptC zAQ^UHj^M;R1+V<#R6_v+K9CTXFndXAaY<%gI!uHcAtG!b2vWl=%;lAzs^FWMlapFx zAScdiWMW`pU}9))YHVyC1>zbS8W=&jbaO`&vy(v+lPMcJFhtK;urLBS8ca=$j12vi zrZ;!4dbxk~+m5R9o`Ksw>m8kEz202n#j7LJHks}uJM*Jy$bhfnmfTwk#$>!6Tr+ zuas%=^l%nllUr6M6$eAEJXv}`S4HoE|Fmav-WMi#?OoGv{%+%g`G!rGY$iCLPSNjR za7=jn^AzW1g|g*yjFzc?a<`kb0xP0q*7ODw}11=P`hn+T;Dr%X0!K}w;S|p zCcIoS?efyt;ON(XT(5kS{AO?ac6Ym_>Y-0656TTSb62##k#qO>QTIFk-u93d!)zs^ zdryP!u4fX|=9OoV7=$=0oOXHbITt$2zt@dh0Cq@V)r$ zd(2J1Oa=-F_tOO$9#@;{I}CTvo5ki*vgP!p6>o$&c7?2dko01Dtns!!?dSSFc2_Qc z6ur8er`mVT%+!S0pOn|zp56R&ul3{X>f#Nv0_(nK9hhO{oE)Y7;qbZDv6B{Smz>ac zx3rK@5sdsQrJTQ1jv+U@bIqYFy?wl0$<^B5ROTK%sAww~RPsbOepiC7@anw__NhL8 zRIGh+`EUP^-4T{2nAF(1_=6nPzoi#M ze+r|OGtBl31~yDGE}Neg42F3=nxLFv}-eaaLesvXDpM9*Cm6*gcb(3=A7D88n^;mU;$x^eQVb3QTAj3oANU8CdK + + + 4.0.0 + + org.superbiz.rest + rest-sample + 1.0-SNAPSHOT + jar + + rest-sample Maven Webapp + http://www.example.com + + + UTF-8 + 1.8 + 1.8 + + + + + javax + javaee-api + 8.0.1 + provided + + + + + rest-sample + + + org.eclipse.transformer + org.eclipse.transformer.maven + ${revision} + + + + + + + run + + package + + + + + maven-jar-plugin + + false + + + + + diff --git a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java index 69537555..0df4629f 100644 --- a/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java +++ b/org.eclipse.transformer.cli/src/test/java/transformer/test/TestCommandLine.java @@ -144,7 +144,7 @@ void zip_entry_creation() throws Exception { * The Java source code is included with the jar file. */ @Test - void testSignatureFilesStripped() throws Exception { + void testStripSignatureFiles() throws Exception { String inputFileName = STATIC_CONTENT_DIR + "/command-line/signed-jar-with-javax.jar"; String outputFileName = DYNAMIC_CONTENT_DIR + "/signed-jar-with-javax.jar"; // Assert that signed input jar file contains 2 signature files: META-INF/MYKEY.SF and META-INF/MYKEY.DSA From b26210bb336c4f99c494b2c51551f8c968592d96 Mon Sep 17 00:00:00 2001 From: Jan Luehe Date: Tue, 20 Aug 2024 22:34:46 +0200 Subject: [PATCH 9/9] [ISSUE 606] Address code review comments: rename strip-signatures to stripSignatures --- .../transformer/bnd/analyzer/BndAnalyzerPluginTest.java | 2 +- maven-plugins/transformer-maven-plugin/README.md | 2 +- .../eclipse/transformer/maven/TransformerRunMojo.java | 2 +- .../transformer/maven/TransformerRunMojoTest.java | 9 --------- .../src/main/java/org/eclipse/transformer/AppOption.java | 2 +- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/bnd-plugins/org.eclipse.transformer.bnd.analyzer/src/test/java/org/eclipse/transformer/bnd/analyzer/BndAnalyzerPluginTest.java b/bnd-plugins/org.eclipse.transformer.bnd.analyzer/src/test/java/org/eclipse/transformer/bnd/analyzer/BndAnalyzerPluginTest.java index 04e4ca54..b4e37c4e 100644 --- a/bnd-plugins/org.eclipse.transformer.bnd.analyzer/src/test/java/org/eclipse/transformer/bnd/analyzer/BndAnalyzerPluginTest.java +++ b/bnd-plugins/org.eclipse.transformer.bnd.analyzer/src/test/java/org/eclipse/transformer/bnd/analyzer/BndAnalyzerPluginTest.java @@ -230,7 +230,7 @@ void options_test() throws Exception { try (Processor processor = new Processor()) { processor.setProperty("-transformer", "tb;arg=value1"); processor.setProperty("-transformer.overwrite", "overwrite"); - processor.setProperty("-transformer.stripSignatures", "strip-signatures"); + processor.setProperty("-transformer.stripSignatures", "stripSignatures"); processor.setProperty("-transformer.morebundles", "bundles;arg=value2"); processor.setProperty("-transformer.immediate", "immediate;option=tv;package=org.eclipse.transformer.test.api;version=\"[2.0\\,3.0);Export-Package=2.0.0\""); diff --git a/maven-plugins/transformer-maven-plugin/README.md b/maven-plugins/transformer-maven-plugin/README.md index 9178d0cd..35765ad6 100644 --- a/maven-plugins/transformer-maven-plugin/README.md +++ b/maven-plugins/transformer-maven-plugin/README.md @@ -53,7 +53,7 @@ A list value of `-` (Hyphen-minus) is ignored. This can be used to configure an |`overwrite` | If `true`, the items which transform to the same path as an existing item overwrite the existing item. _Defaults to `false`_. | |`widen` | If `true`, by default, archive nesting is restricted to JavaEE active locations. This may be relaxed to enable JAR and ZIP within JAR, ZIP within ZIP, and ZIP within EAR, WAR, and RAR. _Defaults to `false`_. | |`jakartaDefaults` | If `true`, the Jakarta rule defaults are included. _Defaults to `false`_. | -|`strip-signatures` | If `true`, signature files will be removed from signed JAR files. _Defaults to `false`_. | +|`stripSignatures` | If `true`, signature files will be removed from signed JAR files. _Defaults to `false`_. | ```xml diff --git a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerRunMojo.java b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerRunMojo.java index 98e6084d..6ae14943 100644 --- a/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerRunMojo.java +++ b/maven-plugins/transformer-maven-plugin/src/main/java/org/eclipse/transformer/maven/TransformerRunMojo.java @@ -59,7 +59,7 @@ public class TransformerRunMojo extends AbstractMojo { @Parameter(defaultValue = "true", property = "transformer-plugin.overwrite", required = true) private boolean overwrite; - @Parameter(defaultValue = "false", property = "transformer-plugin.strip-signatures", required = false) + @Parameter(defaultValue = "false", property = "transformer-plugin.stripSignatures", required = false) private boolean stripSignatures; @Parameter(defaultValue = "true", property = "transformer-plugin.attach", required = true) diff --git a/maven-plugins/transformer-maven-plugin/src/test/java/org/eclipse/transformer/maven/TransformerRunMojoTest.java b/maven-plugins/transformer-maven-plugin/src/test/java/org/eclipse/transformer/maven/TransformerRunMojoTest.java index c7c241ac..443c9eb3 100644 --- a/maven-plugins/transformer-maven-plugin/src/test/java/org/eclipse/transformer/maven/TransformerRunMojoTest.java +++ b/maven-plugins/transformer-maven-plugin/src/test/java/org/eclipse/transformer/maven/TransformerRunMojoTest.java @@ -13,7 +13,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -67,8 +66,6 @@ public void testProjectArtifactTransformerPlugin() throws Exception { mojo.setOutputDirectory(tmp.newFolder(name.getMethodName())); mojo.setAttach(true); - assertNotNull(mojo); - final File targetDirectory = this.resources.getBasedir("transform-build-artifact"); final File modelDirectory = new File(targetDirectory, "target/model"); final File pom = new File(targetDirectory, "pom.xml"); @@ -109,8 +106,6 @@ public void testMultipleArtifactTransformerPlugin() throws Exception { mojo.setProjectHelper(this.rule.lookup(MavenProjectHelper.class)); mojo.setAttach(true); - assertNotNull(mojo); - final File targetDirectory = this.resources.getBasedir("transform-build-artifact"); final File modelDirectory = new File(targetDirectory, "target/model"); final File pom = new File(targetDirectory, "pom.xml"); @@ -172,8 +167,6 @@ public void testProjectArtifactTransformerPluginNoAttach() throws Exception { mojo.setOutputDirectory(tmp.newFolder(name.getMethodName())); mojo.setAttach(false); - assertNotNull(mojo); - final File targetDirectory = this.resources.getBasedir("transform-build-artifact"); final File modelDirectory = new File(targetDirectory, "target/model"); final File pom = new File(targetDirectory, "pom.xml"); @@ -208,8 +201,6 @@ public void testProjectArtifactTransformerPluginStripSignatureFiles() throws Exc mojo.setAttach(true); mojo.setStripSignatures(true); - assertNotNull(mojo); - final File targetDirectory = this.resources.getBasedir("transform-build-jar-artifact"); final File modelDirectory = new File(targetDirectory, "target/model"); final File pom = new File(targetDirectory, "pom.xml"); diff --git a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/AppOption.java b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/AppOption.java index ff52609b..73997f4b 100644 --- a/org.eclipse.transformer/src/main/java/org/eclipse/transformer/AppOption.java +++ b/org.eclipse.transformer/src/main/java/org/eclipse/transformer/AppOption.java @@ -77,7 +77,7 @@ public enum AppOption { INVERT(new Settings("i", "invert", "Invert transformation rules", !Settings.HAS_ARG, !Settings.HAS_ARGS, !Settings.IS_REQUIRED, Settings.NO_GROUP)), - STRIP_SIGNATURES(new Settings("s", "strip-signatures", "Strips signatures from signed jar files", !Settings.HAS_ARG, !Settings.HAS_ARGS, + STRIP_SIGNATURES(new Settings("s", "stripSignatures", "Strips signatures from signed jar files", !Settings.HAS_ARG, !Settings.HAS_ARGS, !Settings.IS_REQUIRED, Settings.NO_GROUP)), FILE_TYPE(new Settings("t", "type", "Input file type", Settings.HAS_ARG, !Settings.HAS_ARGS, !Settings.IS_REQUIRED,