Skip to content

Commit

Permalink
Remove signatures from wrapped source bundles
Browse files Browse the repository at this point in the history
Currently when the source bundle is signed, wrapping it into a source
bundle will break the signature and make the jar invalid.

This now removes the signature from source bundles as they are
transformed.
  • Loading branch information
laeubi committed Feb 9, 2025
1 parent c464287 commit 24edc59
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.IStatus;
Expand Down Expand Up @@ -163,6 +165,17 @@ static Attributes getManifestMainAttributes(TargetBundle targetBundle) throws IO
}
}

static void assertValidSignature(TargetBundle targetBundle) throws IOException {
try (JarInputStream stream = new JarInputStream(
targetBundle.getBundleInfo().getLocation().toURL().openStream())) {
for (JarEntry entry = stream.getNextJarEntry(); entry != null; entry = stream.getNextJarEntry()) {
for (byte[] drain = new byte[4096]; stream.read(drain, 0, drain.length) != -1;) {
// nothing we just want to trigger the signature verification
}
}
}
}

static void assertTargetBundles(ITargetLocation target, List<ExpectedBundle> expectedUnits) {
assertTargetContent(expectedUnits, target.getBundles(), //
(expectedBundle, bundle) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,39 @@ public void testBadSymbolicName() throws Exception {
assertStatusOk(getTargetStatus(target));
}

@Test
public void testSourceWithSignature() throws Exception {
ITargetLocation target = resolveMavenTarget(
"""
<location includeDependencyDepth="none" includeDependencyScopes="compile" label="LemMinX" includeSource="true" missingManifest="error" type="Maven">
<dependencies>
<dependency>
<groupId>org.eclipse.lemminx</groupId>
<artifactId>org.eclipse.lemminx</artifactId>
<version>0.29.0</version>
<type>jar</type>
</dependency>
</dependencies>
<repositories>
<repository>
<id>lemminx-releases</id>
<url>https://repo.eclipse.org/content/repositories/lemminx-releases/</url>
</repository>
</repositories>
</location>
""");
assertStatusOk(getTargetStatus(target));
TargetBundle[] allBundles = target.getBundles();
boolean sourcesFound = false;
for (TargetBundle targetBundle : allBundles) {
if (targetBundle.isSourceBundle()) {
sourcesFound = true;
assertValidSignature(targetBundle);
}
}
assertTrue("No source bundle generated!", sourcesFound);
}

@Test
public void testBadDependencyDirect() throws Exception {
ITargetLocation target = resolveMavenTarget("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,80 +13,41 @@
package org.eclipse.m2e.pde.target;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Objects;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

import org.eclipse.aether.artifact.Artifact;
import org.eclipse.equinox.frameworkadmin.BundleInfo;
import org.eclipse.m2e.pde.target.shared.MavenBundleWrapper;
import org.eclipse.pde.core.target.TargetBundle;
import org.osgi.framework.Constants;

public class MavenSourceBundle extends TargetBundle {

@SuppressWarnings("restriction")
public static final String ECLIPSE_SOURCE_BUNDLE_HEADER = org.eclipse.pde.internal.core.ICoreConstants.ECLIPSE_SOURCE_BUNDLE;

public MavenSourceBundle(BundleInfo sourceTarget, Artifact artifact, CacheManager cacheManager) throws Exception {
this.fSourceTarget = sourceTarget;
fInfo.setSymbolicName(sourceTarget.getSymbolicName() + ".source");
fInfo.setVersion(sourceTarget.getVersion());
String symbolicName = sourceTarget.getSymbolicName();
String version = sourceTarget.getVersion();
fInfo.setSymbolicName(MavenBundleWrapper.getSourceBundleName(symbolicName));
fInfo.setVersion(version);
Manifest manifest;
File sourceFile = artifact.getFile();
try (JarFile jar = new JarFile(sourceFile)) {
manifest = Objects.requireNonNullElseGet(jar.getManifest(), Manifest::new);
}
if (isValidSourceManifest(manifest)) {
if (MavenBundleWrapper.isValidSourceManifest(manifest)) {
fInfo.setLocation(sourceFile.toURI());
} else {
File generatedSourceBundle = cacheManager.accessArtifactFile(artifact, file -> {
if (CacheManager.isOutdated(file, sourceFile)) {
addSourceBundleMetadata(manifest, sourceTarget);
transferJarEntries(sourceFile, manifest, file);
MavenBundleWrapper.addSourceBundleMetadata(manifest, symbolicName, version);
MavenBundleWrapper.transferJarEntries(sourceFile, manifest, file);
}
return file;
});
fInfo.setLocation(generatedSourceBundle.toURI());
}
}

private void addSourceBundleMetadata(Manifest manifest, BundleInfo bundle) {
Attributes attr = manifest.getMainAttributes();
if (attr.isEmpty()) {
attr.put(Name.MANIFEST_VERSION, "1.0");
}
attr.putValue(ECLIPSE_SOURCE_BUNDLE_HEADER,
bundle.getSymbolicName() + ";version=\"" + bundle.getVersion() + "\";roots:=\".\"");
attr.putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
attr.putValue(Constants.BUNDLE_NAME,
"Source Bundle for " + bundle.getSymbolicName() + ":" + bundle.getVersion());
attr.putValue(Constants.BUNDLE_SYMBOLICNAME, fInfo.getSymbolicName());
attr.putValue(Constants.BUNDLE_VERSION, fInfo.getVersion());
}

private void transferJarEntries(File source, Manifest manifest, File target) throws IOException {
try (var output = new JarOutputStream(new FileOutputStream(target), manifest);
var input = new JarInputStream(new FileInputStream(source));) {
for (JarEntry entry; (entry = input.getNextJarEntry()) != null;) {
if (!JarFile.MANIFEST_NAME.equals(entry.getName())) {
output.putNextEntry(new ZipEntry(entry.getName()));
input.transferTo(output);
}
}
}
}

private static boolean isValidSourceManifest(Manifest manifest) {
return manifest != null && manifest.getMainAttributes().getValue(ECLIPSE_SOURCE_BUNDLE_HEADER) != null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
package org.eclipse.m2e.pde.target.shared;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -27,10 +29,16 @@
import java.util.Set;
import java.util.function.Function;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.maven.model.Model;
Expand Down Expand Up @@ -71,6 +79,10 @@
* </ul>
*/
public class MavenBundleWrapper {

@SuppressWarnings("restriction")
public static final String ECLIPSE_SOURCE_BUNDLE_HEADER = org.eclipse.pde.internal.core.ICoreConstants.ECLIPSE_SOURCE_BUNDLE;

private MavenBundleWrapper() {
}

Expand Down Expand Up @@ -308,4 +320,49 @@ public static boolean isOutdated(Path cacheFile, Path sourceFile) throws IOExcep
}
return true;
}

private static boolean isExcludedFromWrapping(String name) {
return name.equals(JarFile.MANIFEST_NAME) || name.startsWith("META-INF/SIG-") || name.startsWith("META-INF/")
&& (name.endsWith(".SF") || name.endsWith(".RSA") || name.endsWith(".DSA"));
}

public static void addSourceBundleMetadata(Manifest manifest, String symbolicName, String version) {

Attributes attr = manifest.getMainAttributes();
if (attr.isEmpty()) {
attr.put(Name.MANIFEST_VERSION, "1.0");
}
attr.putValue(ECLIPSE_SOURCE_BUNDLE_HEADER, symbolicName + ";version=\"" + version + "\";roots:=\".\"");
attr.putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
attr.putValue(Constants.BUNDLE_NAME, "Source Bundle for " + symbolicName + ":" + version);
attr.putValue(Constants.BUNDLE_SYMBOLICNAME, getSourceBundleName(symbolicName));
attr.putValue(Constants.BUNDLE_VERSION, version);
}

public static String getSourceBundleName(String symbolicName) {
return symbolicName + ".source";
}

public static void transferJarEntries(File source, Manifest manifest, File target) throws IOException {
Map<String, Attributes> manifestEntries = manifest.getEntries();
if (manifestEntries != null) {
// need to clear out signature infos
manifestEntries.clear();
}
try (var output = new JarOutputStream(new FileOutputStream(target), manifest);
var input = new JarInputStream(new FileInputStream(source));) {
for (JarEntry entry; (entry = input.getNextJarEntry()) != null;) {
if (MavenBundleWrapper.isExcludedFromWrapping(entry.getName())) {
// Exclude manifest and signatures
continue;
}
output.putNextEntry(new ZipEntry(entry.getName()));
input.transferTo(output);
}
}
}

public static boolean isValidSourceManifest(Manifest manifest) {
return manifest != null && manifest.getMainAttributes().getValue(ECLIPSE_SOURCE_BUNDLE_HEADER) != null;
}
}
2 changes: 1 addition & 1 deletion target-platform/target-platform.target
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
</dependency>
</dependencies>
</location>
<location includeDependencyDepth="none" includeDependencyScopes="compile" label="LemMinX" includeSource="true" missingManifest="error" type="Maven">
<location includeDependencyDepth="none" includeDependencyScopes="compile" label="LemMinX" includeSource="false" missingManifest="error" type="Maven">
<dependencies>
<dependency>
<groupId>org.eclipse.lemminx</groupId>
Expand Down

0 comments on commit 24edc59

Please sign in to comment.