From 06ecdfbf2652dafd00b9bcb521aba0267ca364e7 Mon Sep 17 00:00:00 2001 From: BJ Hargrave Date: Sat, 28 May 2022 16:33:11 -0400 Subject: [PATCH] signing: Write META-INF signing resources immediately after manifest JarInputStream requires the META-INF signing resources to come after the manifest and before any other resources. Otherwise, JarInputStream does not consider the jar to be properly signed. Later versions of Equinox now use JarInputStream to verify the jar signing replacing Equinox's custom verification code. So Bnd needs to properly generate signed jars to work with JarInputStream. We also define a standard pattern for the META-INF signing resources which is used by other classes that care. Signed-off-by: BJ Hargrave --- .../src/aQute/bnd/differ/DiffPluginImpl.java | 16 ++++---- biz.aQute.bndlib/src/aQute/bnd/osgi/Jar.java | 40 ++++++++++++++----- .../src/aQute/bnd/signing/JartoolSigner.java | 8 ++-- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/differ/DiffPluginImpl.java b/biz.aQute.bndlib/src/aQute/bnd/differ/DiffPluginImpl.java index 799b425792..84d09b411e 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/differ/DiffPluginImpl.java +++ b/biz.aQute.bndlib/src/aQute/bnd/differ/DiffPluginImpl.java @@ -1,5 +1,6 @@ package aQute.bnd.differ; +import static aQute.bnd.osgi.Jar.METAINF_SIGNING_P; import static aQute.bnd.service.diff.Delta.CHANGED; import java.io.File; @@ -13,7 +14,6 @@ import java.util.Set; import java.util.TreeSet; import java.util.jar.Manifest; -import java.util.regex.Pattern; import aQute.bnd.header.Attrs; import aQute.bnd.header.OSGiHeader; @@ -129,8 +129,6 @@ private Element bundleElement(Analyzer analyzer) throws Exception { /** * Create an element representing all resources in the JAR */ - private final static Pattern META_INF_P = Pattern.compile("META-INF/([^/]+\\.(MF|SF|DSA|RSA))|(SIG-.*)"); - private Element resourcesElement(Analyzer analyzer) throws Exception { Jar jar = analyzer.getJar(); @@ -139,16 +137,20 @@ private Element resourcesElement(Analyzer analyzer) throws Exception { for (Map.Entry entry : jar.getResources() .entrySet()) { + String path = entry.getKey(); // // The manifest and other (signer) files are ignored // since they are extremely sensitive to time // - if (META_INF_P.matcher(entry.getKey()) - .matches()) + if (jar.getManifestName() + .equals(path) + || METAINF_SIGNING_P.matcher(path) + .matches()) { continue; + } - if (localIgnore != null && localIgnore.matches(entry.getKey())) + if (localIgnore != null && localIgnore.matches(path)) continue; // @@ -159,8 +161,6 @@ private Element resourcesElement(Analyzer analyzer) throws Exception { // directory with source code can be found. // - String path = entry.getKey(); - if (path.endsWith(Constants.EMPTY_HEADER)) continue; diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Jar.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Jar.java index ea35b585df..10ec3fd0f8 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Jar.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Jar.java @@ -41,6 +41,7 @@ import java.util.function.Predicate; import java.util.jar.Attributes; import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.regex.Matcher; @@ -96,7 +97,6 @@ public enum Compression { STORE } - private static final String DEFAULT_MANIFEST_NAME = "META-INF/MANIFEST.MF"; private static final Pattern DEFAULT_DO_NOT_COPY = Pattern .compile(Constants.DEFAULT_DO_NOT_COPY); @@ -106,7 +106,7 @@ public enum Compression { private Optional manifest; private Optional moduleAttribute; private boolean manifestFirst; - private String manifestName = DEFAULT_MANIFEST_NAME; + private String manifestName = JarFile.MANIFEST_NAME; private String name; private File source; private ZipFile zipFile; @@ -122,6 +122,8 @@ public enum Compression { private boolean calculateFileDigest; private int fileLength = -1; private long zipEntryConstantTime = ZIP_ENTRY_CONSTANT_TIME; + public static final Pattern METAINF_SIGNING_P = Pattern + .compile("META-INF/([^/]+\\.(?:DSA|RSA|EC|SF)|SIG-[^/]+)", Pattern.CASE_INSENSITIVE); public Jar(String name) { this.name = name; @@ -590,6 +592,8 @@ public void write(OutputStream to) throws Exception { Set done = new HashSet<>(); Set directories = new HashSet<>(); + + // Write manifest first if (doNotTouchManifest) { Resource r = getResource(manifestName); if (r != null) { @@ -601,6 +605,22 @@ public void write(OutputStream to) throws Exception { done.add(manifestName); } + // Then write any signature info next since JarInputStream really cares! + Map metainf = getDirectory("META-INF"); + if (metainf != null) { + List signing = metainf.keySet() + .stream() + .filter(path -> METAINF_SIGNING_P.matcher(path) + .matches()) + .collect(toList()); + for (String path : signing) { + if (done.add(path)) { + writeResource(jout, directories, path, metainf.get(path)); + } + } + } + + // Write all remaining entries for (Map.Entry entry : getResources().entrySet()) { // Skip metainf contents if (!done.contains(entry.getKey())) @@ -1245,16 +1265,16 @@ public byte[] getTimelessDigest() throws Exception { return md.digest(); } - private final static Pattern SIGNER_FILES_P = Pattern.compile("(.+\\.(SF|DSA|RSA))|(.*/SIG-.*)", - Pattern.CASE_INSENSITIVE); - public void stripSignatures() { - Map map = getDirectory("META-INF"); - if (map != null) { - for (String file : new HashSet<>(map.keySet())) { - if (SIGNER_FILES_P.matcher(file) + Map metainf = getDirectory("META-INF"); + if (metainf != null) { + List signing = metainf.keySet() + .stream() + .filter(path -> METAINF_SIGNING_P.matcher(path) .matches()) - remove(file); + .collect(toList()); + for (String path : signing) { + remove(path); } } } diff --git a/biz.aQute.bndlib/src/aQute/bnd/signing/JartoolSigner.java b/biz.aQute.bndlib/src/aQute/bnd/signing/JartoolSigner.java index 2158ce1034..28bc8e9196 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/signing/JartoolSigner.java +++ b/biz.aQute.bndlib/src/aQute/bnd/signing/JartoolSigner.java @@ -1,11 +1,13 @@ package aQute.bnd.signing; +import static aQute.bnd.osgi.Jar.METAINF_SIGNING_P; + import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; +import java.util.jar.JarFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -101,8 +103,6 @@ public void setRegistry(Registry registry) { processor = registry.getPlugin(Processor.class); } - private static Pattern SIGNING_P = Pattern.compile("META-INF/([^/]*\\.(DSA|RSA|EC|SF|MF)|SIG-[^/]*)"); - @Override public void sign(Builder builder, String alias) throws Exception { File f = builder.getFile(keystore); @@ -188,7 +188,7 @@ public void sign(Builder builder, String alias) throws Exception { builder.addClose(signed); MapStream.of(signed.getDirectory("META-INF")) - .filterKey(path -> SIGNING_P.matcher(path) + .filterKey(path -> JarFile.MANIFEST_NAME.equals(path) || METAINF_SIGNING_P.matcher(path) .matches()) .forEachOrdered(jar::putResource); jar.setDoNotTouchManifest();