FELIX-518 - add unit test for signed DPs:

- created utility to create signed deployment packages for use in itests;
- verify that signed DPs are correctly installed;
- some small cleanups and typos fixed.



git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1724647 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
index 89f4ce9..512381b 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
@@ -511,6 +511,6 @@
      */
     protected boolean isMetaInfFile(String name) {
         name = name.toUpperCase(Locale.US);
-        return name.startsWith("META-INF/") && (name.endsWith("/INDEX.LIST") || name.endsWith(".SF") || name.endsWith(".DSA") || name.endsWith(".RS"));
+        return name.startsWith("META-INF/") && (name.endsWith("/INDEX.LIST") || name.endsWith(".SF") || name.endsWith(".DSA") || name.endsWith(".RSA") || name.endsWith(".EC"));
     }
 }
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStream.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStream.java
index 5396cfc..7dff1da 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStream.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ContentCopyingJarInputStream.java
@@ -18,7 +18,8 @@
  */
 package org.apache.felix.deploymentadmin;
 
-import java.io.Closeable;
+import static org.apache.felix.deploymentadmin.Utils.closeSilently;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
@@ -53,7 +54,7 @@
     private OutputStream m_entryOS;
 
     public ContentCopyingJarInputStream(InputStream in, File indexFile, File contentDir) throws IOException {
-        super(in);
+        super(in, true /* verify */);
 
         m_contentDir = contentDir;
 
@@ -122,17 +123,6 @@
         m_indexFileWriter = null;
     }
 
-    private void closeSilently(Closeable resource) {
-        try {
-            if (resource != null) {
-                resource.close();
-            }
-        }
-        catch (IOException e) {
-            // Ignore, not much we can do about this...
-        }
-    }
-
     /**
      * Creates a verbatim copy of the manifest, when it is read from the original JAR.
      */
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
index 1c17ba4..42f2c32 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
@@ -181,6 +181,8 @@
                 jarInput = new ContentCopyingJarInputStream(sourceInput, tempIndex, tempContents);
 
                 if (jarInput.getManifest() == null) {
+                    Utils.closeSilently(jarInput);
+                    
                     m_log.log(LogService.LOG_ERROR, "Stream does not contain a valid deployment package: missing manifest!");
                     throw new DeploymentException(CODE_MISSING_HEADER, "No manifest present in deployment package!");
                 }
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentPackageManifest.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentPackageManifest.java
index ed1189c..00ac13b 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentPackageManifest.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentPackageManifest.java
@@ -33,10 +33,8 @@
  * deployment package manifest and can interpret the various manifest entries and headers the OSGi specification defines.
  */
 public class DeploymentPackageManifest implements Constants {
-
     private final Manifest m_manifest;
     private final Version m_version;
-
     private final List m_bundleInfos = new ArrayList();
     private final List m_resourceInfos = new ArrayList();
     private final String m_symbolicName;
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/NonCloseableStream.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/NonCloseableStream.java
index 795192b..7c68997 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/NonCloseableStream.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/NonCloseableStream.java
@@ -28,75 +28,68 @@
  * 
  */
 public class NonCloseableStream extends InputStream {
-
     private final InputStream m_input;
-    private boolean m_closed;
+    private volatile boolean m_closed;
 
-    public NonCloseableStream(InputStream m_input) {
-        this.m_input = m_input;
+    public NonCloseableStream(InputStream input) {
+        m_input = input;
     }
 
-    // stream should not be actually closed, subsequent calls to read methods will throw an exception though
-
-    public void close() throws IOException {
-        if (m_closed) {
-            throw new IOException("Unable to read, stream is closed.");
-        }
-        m_closed = true;
-    }
-
-    public int read() throws IOException {
-        return m_input.read();
-    }
-
-    public int read(byte[] b, int off, int len) throws IOException {
-        if (m_closed) {
-            throw new IOException("Unable to read, stream is closed.");
-        }
-        return m_input.read(b, off, len);
-    }
-
-    public int read(byte[] b) throws IOException {
-        if (m_closed) {
-            throw new IOException("Unable to read, stream is closed.");
-        }
-        return m_input.read(b);
-    }
-
-    // No mark & reset support
-
-    public boolean markSupported() {
-        return false;
-    }
-
-    public void mark(int readlimit) {
-        // do nothing
-    }
-
-    public void reset() throws IOException {
-        throw new IOException("Mark and reset are not available on this type of stream.");
-    }
-
-    // Unaffected methods
-
     public int available() throws IOException {
         return m_input.available();
     }
 
-    public int hashCode() {
-        return m_input.hashCode();
-    }
-
-    public long skip(long n) throws IOException {
-        return m_input.skip(n);
+    public void close() throws IOException {
+        // stream should not be actually closed, subsequent calls to read methods will throw an exception though
+        assertOpen();
+        m_closed = true;
     }
 
     public boolean equals(Object obj) {
         return m_input.equals(obj);
     }
 
+    public int hashCode() {
+        return m_input.hashCode();
+    }
+
+    public void mark(int readlimit) {
+        // do nothing
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+
+    public int read() throws IOException {
+        return m_input.read();
+    }
+
+    public int read(byte[] b) throws IOException {
+        assertOpen();
+        return m_input.read(b);
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        assertOpen();
+        return m_input.read(b, off, len);
+    }
+
+    public void reset() throws IOException {
+        throw new IOException("Mark and reset are not available on this type of stream.");
+    }
+
+    public long skip(long n) throws IOException {
+        return m_input.skip(n);
+    }
+
     public String toString() {
         return m_input.toString();
     }
 
+    private void assertOpen() throws IOException {
+        if (m_closed) {
+            throw new IOException("Unable to read, stream is closed.");
+        }
+    }
 }
diff --git a/deploymentadmin/itest/pom.xml b/deploymentadmin/itest/pom.xml
index 4a45f59..5a0296d 100644
--- a/deploymentadmin/itest/pom.xml
+++ b/deploymentadmin/itest/pom.xml
@@ -117,6 +117,18 @@
 		</dependency>
 
 		<dependency>
+			<groupId>org.bouncycastle</groupId>
+			<artifactId>bcprov-jdk15on</artifactId>
+			<version>1.54</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.bouncycastle</groupId>
+			<artifactId>bcpkix-jdk15on</artifactId>
+			<version>1.54</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
 			<groupId>ch.qos.logback</groupId>
 			<artifactId>logback-core</artifactId>
 			<version>1.1.3</version>
@@ -148,14 +160,6 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<target>1.6</target>
-					<source>1.6</source>
-				</configuration>
-			</plugin>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-compiler-plugin</artifactId>
-				<configuration>
 					<source>1.7</source>
 					<target>1.7</target>
 				</configuration>
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java
index f13df3f..546956a 100644
--- a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java
@@ -25,6 +25,7 @@
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.security.Security;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -38,6 +39,7 @@
 import junit.framework.TestCase;
 
 import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.junit.After;
 import org.junit.Before;
 import org.ops4j.pax.exam.Configuration;
@@ -74,24 +76,23 @@
     protected volatile AtomicInteger m_gate = new AtomicInteger(0);
     protected volatile String m_testBundleBasePath;
     protected volatile Map<String, List<Version>> m_initialBundles;
-    
-    private int cnt = 0;        
-    
+
+    private int cnt = 0;
+
     @Configuration
     public Option[] config() throws Exception {
-        return options(
-            bootDelegationPackage("sun.*"),
-            systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("ERROR"),
+        return options(bootDelegationPackage("sun.*"), systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("ERROR"),
 
-            mavenBundle("org.apache.felix", "org.apache.felix.metatype").versionAsInProject(),
+            mavenBundle("org.apache.felix", "org.apache.felix.metatype").versionAsInProject(), 
             mavenBundle("org.apache.felix", "org.apache.felix.dependencymanager").versionAsInProject(),
-            mavenBundle("org.apache.felix", "org.apache.felix.deploymentadmin").versionAsInProject(),
+            mavenBundle("org.apache.felix", "org.apache.felix.deploymentadmin").versionAsInProject(), 
             mavenBundle("org.apache.felix", "org.apache.felix.eventadmin").versionAsInProject(),
             mavenBundle("org.apache.felix", "org.apache.felix.configadmin").versionAsInProject(),
             mavenBundle("commons-codec", "commons-codec").versionAsInProject(),
-            
-            junitBundles()
-        );
+            mavenBundle("org.bouncycastle", "bcprov-jdk15on").versionAsInProject(),
+            mavenBundle("org.bouncycastle", "bcpkix-jdk15on").versionAsInProject(),
+
+            junitBundles());
     }
 
     @Before
@@ -110,9 +111,9 @@
                 }
             }
         });
-        
+
         m_initialBundles = new HashMap<String, List<Version>>();
-        
+
         for (Bundle bundle : m_context.getBundles()) {
             List<Version> versions = m_initialBundles.get(bundle.getSymbolicName());
             if (versions == null) {
@@ -121,12 +122,16 @@
             }
             versions.add(bundle.getVersion());
         }
+
+        Security.addProvider(new BouncyCastleProvider());
     }
-    
+
     @After
     public void tearDown() throws Exception {
         System.setProperty("rp1", "");
         System.setProperty("bundle3", "");
+
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
     }
 
     protected void assertBundleExists(String symbolicName, String version) {
@@ -168,24 +173,26 @@
         }
         return result;
     }
-    
+
     protected final DeploymentPackage installDeploymentPackage(DeploymentPackageBuilder dpBuilder) throws Exception {
         return installDeploymentPackage(dpBuilder.generate());
     }
-    
+
     protected final DeploymentPackage installDeploymentPackage(InputStream is) throws Exception {
         try {
             return m_deploymentAdmin.installDeploymentPackage(is);
-        } finally {
+        }
+        finally {
             try {
                 is.close();
-            } catch (IOException e) {
+            }
+            catch (IOException e) {
                 // Nothing we can do about this, but log it...
                 e.printStackTrace();
             }
         }
     }
-    
+
     protected final int countDeploymentPackages() {
         return m_deploymentAdmin.listDeploymentPackages().length;
     }
@@ -193,17 +200,17 @@
     protected DeploymentPackageBuilder createNewDeploymentPackageBuilder(String version) {
         return createDeploymentPackageBuilder(String.format("itest%d", ++cnt), version);
     }
-    
+
     protected DeploymentPackageBuilder createDeploymentPackageBuilder(String symName, String version) {
         return DeploymentPackageBuilder.create(symName, version);
     }
-    
+
     protected Map<String, List<Version>> getCurrentBundles() {
         Map<String, List<Version>> bundles = new HashMap<String, List<Version>>();
         for (Bundle bundle : m_context.getBundles()) {
             String symbolicName = bundle.getSymbolicName();
             Version version = bundle.getVersion();
-            
+
             // Is is not part of any of the initially provisioned bundles?
             List<Version> versions = m_initialBundles.get(symbolicName);
             if ((versions == null) || !versions.contains(version)) {
@@ -217,7 +224,7 @@
         }
         return bundles;
     }
-    
+
     protected String getSymbolicName(String baseName) {
         return "testbundles.".concat(baseName);
     }
@@ -241,10 +248,11 @@
     }
 
     protected URL getTestBundleURL(String baseName) throws MalformedURLException {
-    	return getTestBundleURL(baseName, "1.0.0");
+        return getTestBundleURL(baseName, "1.0.0");
     }
+
     protected URL getTestBundleURL(String baseName, String version) throws MalformedURLException {
-    	return getTestBundleURL(baseName, baseName, version);
+        return getTestBundleURL(baseName, baseName, version);
     }
 
     protected URL getTestBundleURL(String artifactName, String baseName, String version) throws MalformedURLException {
@@ -253,7 +261,7 @@
         assertTrue("No such bundle: " + f, f.exists() && f.isFile());
         return f.toURI().toURL();
     }
-    
+
     protected boolean isBundleActive(Bundle bundle) {
         return isBundleInState(bundle, Bundle.ACTIVE);
     }
@@ -280,7 +288,7 @@
     protected boolean isBundleRemoved(String symbolicName, String version) {
         return isBundleRemoved(symbolicName, new Version(version));
     }
-    
+
     protected boolean isBundleRemoved(String symbolicName, Version version) {
         Map<String, List<Version>> bundles = getCurrentBundles();
 
@@ -297,13 +305,13 @@
 
         FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
         frameworkWiring.resolveBundles(Arrays.asList(bundles));
-        
+
         for (Bundle bundle : bundles) {
             if ((bundle.getState() & Bundle.RESOLVED) == 0) {
                 return false;
             }
         }
-        
+
         return true;
     }
 }
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DPSignerTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DPSignerTest.java
new file mode 100644
index 0000000..499906b
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DPSignerTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.itest;
+
+import static junit.framework.TestCase.assertNotNull;
+import static org.apache.felix.deploymentadmin.itest.BaseIntegrationTest.TEST_FAILING_BUNDLE_RP1;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.net.URL;
+import java.security.Security;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import org.apache.felix.deploymentadmin.itest.util.CertificateUtil;
+import org.apache.felix.deploymentadmin.itest.util.CertificateUtil.KeyType;
+import org.apache.felix.deploymentadmin.itest.util.CertificateUtil.SignerInfo;
+import org.apache.felix.deploymentadmin.itest.util.DPSigner;
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test cases for {@link DPSigner}.
+ */
+public class DPSignerTest {
+
+    @Test
+    public void testSignArtifactsOk() throws Exception {
+        URL dpProps = getClass().getResource("/dp.properties");
+        assertNotNull(dpProps);
+
+        DeploymentPackageBuilder builder = DeploymentPackageBuilder.create("dpSignerTest1", "1.0.0");
+        builder.add(builder.createLocalizationResource().setUrl(dpProps).setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setFilename("dp.properties"));
+
+        SignerInfo signerInfo = CertificateUtil.createSelfSignedCert("CN=testCert", KeyType.RSA);
+
+        DPSigner signer = new DPSigner();
+
+        byte[] rawData;
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            signer.sign(builder, signerInfo.getPrivate(), signerInfo.getCert(), baos);
+            rawData = baos.toByteArray();
+        }
+
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(rawData); JarInputStream jis = new JarInputStream(bais, true)) {
+            JarEntry entry;
+            while ((entry = jis.getNextJarEntry()) != null) {
+                assertNotNull(entry);
+                jis.closeEntry();
+            }
+            
+            assertNotNull(jis.getManifest());
+        }
+    }
+
+    @Before
+    public void setUp() {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    @After
+    public void tearDown() {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallDeploymentPackageTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallDeploymentPackageTest.java
index 2b0ff9a..24f4ffa 100644
--- a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallDeploymentPackageTest.java
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallDeploymentPackageTest.java
@@ -18,9 +18,13 @@
  */
 package org.apache.felix.deploymentadmin.itest;
 
+import static org.apache.felix.deploymentadmin.itest.util.CertificateUtil.createSelfSignedCert;
+
 import java.io.File;
 import java.net.URL;
 
+import org.apache.felix.deploymentadmin.itest.util.CertificateUtil.KeyType;
+import org.apache.felix.deploymentadmin.itest.util.CertificateUtil.SignerInfo;
 import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
 import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder.JarManifestManipulatingFilter;
 import org.junit.Test;
@@ -35,8 +39,7 @@
  * Provides test cases regarding the use of "normal" deployment packages in DeploymentAdmin.
  */
 @RunWith(PaxExam.class)
-public class InstallDeploymentPackageTest extends BaseIntegrationTest
-{
+public class InstallDeploymentPackageTest extends BaseIntegrationTest {
     /**
      * FELIX-518 - Test that DP with localization and signature files are properly deployed.
      */
@@ -44,36 +47,40 @@
     public void testInstallDeploymentPackageWithLocalizationAndSignatureFilesOk() throws Exception {
         URL dpProps = getClass().getResource("/dp.properties");
         assertNotNull(dpProps);
-        
-        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
-        dpBuilder
-            .addSignatures()
-            .add(dpBuilder.createLocalizationResource().setUrl(dpProps).setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setFilename("dp.properties"))
-            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundleURL("rp1")));
 
-        installDeploymentPackage(dpBuilder);
+        SignerInfo signer = createSelfSignedCert("CN=dpTest", KeyType.EC);
+
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder.signOutput(signer.getPrivate(), signer.getCert())
+            .add(dpBuilder.createLocalizationResource().setUrl(dpProps).setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setFilename("dp.properties"))
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundleURL("rp1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle2")));
+
+        installDeploymentPackage(dpBuilder); // should succeed.
+        
+        assertBundleExists("testbundles.bundle1", "1.0.0");
+        assertBundleExists("testbundles.bundle2", "1.0.0");
+        assertBundleExists("testbundles.rp1", "1.0.0");
     }
-    
-    
+
     /**
      * FELIX-4409/4410/4463 - test the installation of an invalid deployment package.
      */
     @Test
-    public void testInstallInvalidDeploymentPackageFail() throws Exception
-    {
+    public void testInstallInvalidDeploymentPackageFail() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
-        // incluse two different versions of the same bundle (with the same BSN), this is *not* allowed per the DA spec...
+        // incluse two different versions of the same bundle (with the same BSN), this is *not* allowed per the DA
+        // spec...
         dpBuilder
             .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundleapi1", "bundleapi1", "1.0.0")))
             .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundleapi2", "bundleapi2", "2.0.0")));
 
-        try
-        {
+        try {
             installDeploymentPackage(dpBuilder);
             fail("DeploymentException expected!");
         }
-        catch (DeploymentException e)
-        {
+        catch (DeploymentException e) {
             // Ok; expected...
         }
 
@@ -86,10 +93,10 @@
      * FELIX-1835 - test whether we can install bundles with a non-root path inside the DP.
      */
     @Test
-    public void testInstallBundlesWithPathsOk() throws Exception
-    {
+    public void testInstallBundlesWithPathsOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
-        // incluse two different versions of the same bundle (with the same BSN), this is *not* allowed per the DA spec...
+        // incluse two different versions of the same bundle (with the same BSN), this is *not* allowed per the DA
+        // spec...
         dpBuilder
             .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundleapi1", "bundleapi1", "1.0.0")).setFilename("bundles/bundleapi1.jar"))
             .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundleimpl1", "bundleimpl1", "1.0.0")).setFilename("bundles/bundleimpl1.jar"));
@@ -106,11 +113,11 @@
     }
 
     /**
-     * Tests that adding the dependency for a bundle in an update package causes the depending bundle to be resolved and started.
+     * Tests that adding the dependency for a bundle in an update package causes the depending bundle to be resolved and
+     * started.
      */
     @Test
-    public void testInstallBundleWithDependencyInPackageUpdateOk() throws Exception
-    {
+    public void testInstallBundleWithDependencyInPackageUpdateOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         // missing bundle1 as dependency...
         dpBuilder.add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle2")));
@@ -140,11 +147,11 @@
     }
 
     /**
-     * Tests that installing a bundle with a dependency installed by another deployment package is not started, but is resolved.
+     * Tests that installing a bundle with a dependency installed by another deployment package is not started, but is
+     * resolved.
      */
     @Test
-    public void testInstallBundleWithDependencyInSeparatePackageOk() throws Exception
-    {
+    public void testInstallBundleWithDependencyInSeparatePackageOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         dpBuilder.add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle2")));
 
@@ -183,8 +190,7 @@
      * Tests that if an exception is thrown in the start method of a bundle, the installation is not rolled back.
      */
     @Test
-    public void testInstallBundleWithExceptionThrownInStartCausesNoRollbackOk() throws Exception
-    {
+    public void testInstallBundleWithExceptionThrownInStartCausesNoRollbackOk() throws Exception {
         System.setProperty("bundle3", "start");
 
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
@@ -208,11 +214,11 @@
     }
 
     /**
-     * Tests that installing a bundle along with a fragment bundle succeeds (DA should not try to start the fragment, see FELIX-4167).
+     * Tests that installing a bundle along with a fragment bundle succeeds (DA should not try to start the fragment,
+     * see FELIX-4167).
      */
     @Test
-    public void testInstallBundleWithFragmentOk() throws Exception
-    {
+    public void testInstallBundleWithFragmentOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         dpBuilder
             .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle1")))
@@ -236,8 +242,7 @@
      * Tests that installing a bundle whose dependencies cannot be met, is installed, but not started.
      */
     @Test
-    public void testInstallBundleWithMissingDependencyOk() throws Exception
-    {
+    public void testInstallBundleWithMissingDependencyOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         dpBuilder.add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle2")));
 
@@ -258,8 +263,7 @@
      * Tests that installing a bundle along with other (non-bundle) artifacts succeeds.
      */
     @Test
-    public void testInstallBundleWithOtherArtifactsOk() throws Exception
-    {
+    public void testInstallBundleWithOtherArtifactsOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         dpBuilder
             .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundleURL("rp1")))
@@ -282,8 +286,7 @@
      * Tests that installing a new bundle works as expected.
      */
     @Test
-    public void testInstallSingleValidBundleOk() throws Exception
-    {
+    public void testInstallSingleValidBundleOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         dpBuilder.add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle1")));
 
@@ -302,8 +305,7 @@
      * Tests that installing two bundles works as expected.
      */
     @Test
-    public void testInstallTwoValidBundlesOk() throws Exception
-    {
+    public void testInstallTwoValidBundlesOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         dpBuilder
             .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle1")))
@@ -324,11 +326,11 @@
     }
 
     /**
-     * Tests that if an exception is thrown during the uninstall of a bundle, the installation/update continues and succeeds.
+     * Tests that if an exception is thrown during the uninstall of a bundle, the installation/update continues and
+     * succeeds.
      */
     @Test
-    public void testUninstallBundleWithExceptionThrownInStopCauseNoRollbackOk() throws Exception
-    {
+    public void testUninstallBundleWithExceptionThrownInStopCauseNoRollbackOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         dpBuilder
             .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle1")))
@@ -365,8 +367,7 @@
      * Tests that if an exception is thrown during the stop of a bundle, the installation/update continues and succeeds.
      */
     @Test
-    public void testUpdateBundleWithExceptionThrownInStopCauseNoRollbackOk() throws Exception
-    {
+    public void testUpdateBundleWithExceptionThrownInStopCauseNoRollbackOk() throws Exception {
         DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
         dpBuilder
             .add(dpBuilder.createBundleResource().setUrl(getTestBundleURL("bundle1")))
@@ -402,7 +403,8 @@
     }
 
     /**
-     * Tests that we can correctly rollback the installation of a deployment package for bundles that have their data area populated. 
+     * Tests that we can correctly rollback the installation of a deployment package for bundles that have their data
+     * area populated.
      */
     @Test
     public void testRollbackWithPopulatedDataAreaOk() throws Exception {
@@ -434,7 +436,7 @@
         // Simulate an upgrade for our bundle, which should cause its data area to be retained...
         dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
         dpBuilder
-        .add(dpBuilder.createBundleResource().setVersion("1.1.0").setUrl(getTestBundleURL("bundle1")).setFilter(new JarManifestManipulatingFilter("Bundle-Version", "1.1.0")))
+            .add(dpBuilder.createBundleResource().setVersion("1.1.0").setUrl(getTestBundleURL("bundle1")).setFilter(new JarManifestManipulatingFilter("Bundle-Version", "1.1.0")))
             .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundleURL("rp1")))
             .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
 
@@ -449,7 +451,7 @@
         // We should still have this bundle..
         bundle1 = getBundle("testbundles.bundle1");
         assertNotNull("Unable to get installed test bundle?!", bundle1);
-        
+
         dataArea = bundle1.getDataFile("");
         assertNotNull("No data area obtained for test bundle?!", dataArea);
 
@@ -460,7 +462,7 @@
     }
 
     /**
-     * Tests that we can correctly install a deployment package with bundles that have their data area populated. 
+     * Tests that we can correctly install a deployment package with bundles that have their data area populated.
      */
     @Test
     public void testUpgradeWithPopulatedDataAreaOk() throws Exception {
@@ -497,7 +499,7 @@
         // We should still have this bundle..
         bundle1 = getBundle("testbundles.bundle1");
         assertNotNull("Unable to get installed test bundle?!", bundle1);
-        
+
         dataArea = bundle1.getDataFile("");
         assertNotNull("No data area obtained for test bundle?!", dataArea);
 
@@ -508,7 +510,7 @@
     }
 
     /**
-     * Tests that we can correctly install a deployment package with bundles that have their data area populated. 
+     * Tests that we can correctly install a deployment package with bundles that have their data area populated.
      */
     @Test
     public void testUninstallBundleWithPopulatedDataAreaOk() throws Exception {
@@ -548,8 +550,9 @@
         // Data area should be restored exactly as-is...
         assertFalse("Data area not purged?!", dataArea.exists());
     }
+
     /**
-     * Tests that we can correctly install a deployment package with bundles that have their data area populated. 
+     * Tests that we can correctly install a deployment package with bundles that have their data area populated.
      */
     @Test
     public void testRollbackUninstallBundleWithPopulatedDataAreaOk() throws Exception {
@@ -594,7 +597,7 @@
         // We should still have this bundle..
         bundle1 = getBundle("testbundles.bundle1");
         assertNotNull("Unable to get installed test bundle?!", bundle1);
-        
+
         dataArea = bundle1.getDataFile("");
         assertNotNull("No data area obtained for test bundle?!", dataArea);
 
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactData.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactData.java
index a4a6939..9ca98e6 100644
--- a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactData.java
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactData.java
@@ -18,6 +18,8 @@
  */
 package org.apache.felix.deploymentadmin.itest.util;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 
 public class ArtifactData {
@@ -38,6 +40,13 @@
         m_filename = filename;
     }
 
+    public final InputStream createInputStream() throws IOException {
+        if (m_filter != null) {
+            return m_filter.createInputStream(m_url);
+        }
+        return m_url.openStream();
+    }
+
     public String getFilename() {
         return m_filename;
     }
@@ -77,7 +86,7 @@
     public boolean isMissing() {
         return m_missing;
     }
-    
+
     public boolean isResourceProcessorNeeded() {
         return m_needRP;
     }
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/CertificateUtil.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/CertificateUtil.java
new file mode 100644
index 0000000..ad81fbb
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/CertificateUtil.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.itest.util;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Random;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class CertificateUtil {
+
+    public enum KeyType {
+        RSA, DSA, EC;
+    }
+
+    public static class SignerInfo {
+        private final KeyPair m_keyPair;
+        private final X509Certificate m_cert;
+
+        public SignerInfo(KeyPair keyPair, X509Certificate cert) {
+            m_keyPair = keyPair;
+            m_cert = cert;
+        }
+
+        public X509Certificate getCert() {
+            return m_cert;
+        }
+
+        public PrivateKey getPrivate() {
+            return m_keyPair.getPrivate();
+        }
+
+        public PublicKey getPublic() {
+            return m_keyPair.getPublic();
+        }
+    }
+
+    public static SignerInfo createSelfSignedCert(String commonName, KeyType type) throws Exception {
+        KeyPairGenerator kpGen;
+        switch (type) {
+            case RSA:
+                kpGen = KeyPairGenerator.getInstance("RSA");
+                kpGen.initialize(1024);
+                break;
+            case DSA:
+                kpGen = KeyPairGenerator.getInstance("DSA");
+                break;
+            case EC:
+                kpGen = KeyPairGenerator.getInstance("EC");
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid key type!");
+        }
+
+        KeyPair keyPair = kpGen.generateKeyPair();
+        X509Certificate cert = createSelfSignedCert(commonName, keyPair);
+
+        return new SignerInfo(keyPair, cert);
+    }
+
+    private static X509Certificate createSelfSignedCert(String commonName, KeyPair keypair) throws Exception {
+        PublicKey publicKey = keypair.getPublic();
+        String keyAlg = DPSigner.getSignatureAlgorithm(publicKey);
+
+        X500Name issuer = new X500Name(commonName);
+        BigInteger serial = BigInteger.probablePrime(16, new Random());
+        Date notBefore = new Date(System.currentTimeMillis() - 1000);
+        Date notAfter = new Date(notBefore.getTime() + 6000);
+
+        SubjectPublicKeyInfo pubKeyInfo;
+        try (ASN1InputStream is = new ASN1InputStream(publicKey.getEncoded())) {
+            pubKeyInfo = SubjectPublicKeyInfo.getInstance(is.readObject());
+        }
+
+        X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issuer, serial, notBefore, notAfter, issuer, pubKeyInfo);
+        builder.addExtension(new Extension(Extension.basicConstraints, true, new DEROctetString(new BasicConstraints(false))));
+
+        X509CertificateHolder certHolder = builder.build(new JcaContentSignerBuilder(keyAlg).build(keypair.getPrivate()));
+        return new JcaX509CertificateConverter().getCertificate(certHolder);
+    }
+
+    /**
+     * Creates a new CertificateUtil instance.
+     */
+    private CertificateUtil() {
+        // Nop
+    }
+
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DPSigner.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DPSigner.java
new file mode 100644
index 0000000..351533c
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DPSigner.java
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.itest.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.RSAKey;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.commons.codec.binary.Base64;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+
+/**
+ * Signs a deployment package using a given keypair.
+ */
+public class DPSigner {
+    private static final String META_INF = "META-INF/";
+
+    public static String getSignatureAlgorithm(Key key) {
+        if (key instanceof RSAKey) {
+            return "SHA256withRSA";
+        }
+        else if (key instanceof DSAKey) {
+            return "SHA1withDSA";
+        }
+        else if (key instanceof ECKey) {
+            return "SHA256withECDSA";
+        }
+        else {
+            throw new IllegalArgumentException("Invalid/unsupported key: " + key.getClass().getName());
+        }
+    }
+    private static String getBlockFileExtension(Key key) {
+        if (key instanceof RSAKey) {
+            return ".RSA";
+        }
+        else if (key instanceof DSAKey) {
+            return ".DSA";
+        }
+        else if (key instanceof ECKey) {
+            return ".EC";
+        }
+        else {
+            throw new IllegalArgumentException("Invalid/unsupported key: " + key.getClass().getName());
+        }
+    }
+    private final MessageDigest m_digest;
+
+    private final String m_digestAlg;
+
+    private final String m_baseName;
+
+    public DPSigner() {
+        this("DP");
+    }
+
+    public DPSigner(String baseName) {
+        try {
+            m_baseName = META_INF.concat(baseName);
+            m_digest = MessageDigest.getInstance("SHA-256");
+            m_digestAlg = m_digest.getAlgorithm();
+        }
+        catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("SHA-256 not supported by default?!");
+        }
+    }
+    
+    public void addDigestAttribute(Attributes attrs, ArtifactData file) throws IOException {
+        attrs.putValue(m_digestAlg.concat("-Digest"), calculateDigest(file));
+    }
+
+    public void sign(DeploymentPackageBuilder builder, PrivateKey privKey, X509Certificate cert, OutputStream os) throws Exception {
+        Manifest manifest = builder.createManifest();
+        List<ArtifactData> artifacts = builder.getArtifactList();
+        sign(manifest, artifacts, privKey, cert, os);
+    }
+
+    public void sign(Manifest manifest, List<ArtifactData> files, PrivateKey privKey, X509Certificate cert, OutputStream os) throws Exception {
+        // For each file, add its signature to the manifest
+        for (ArtifactData file : files) {
+            String filename = file.getFilename();
+            Attributes attrs = manifest.getAttributes(filename);
+            addDigestAttribute(attrs, file);
+        }
+
+        try (ZipOutputStream zos = new ZipOutputStream(os)) {
+            writeSignedManifest(manifest, zos, privKey, cert);
+
+            for (ArtifactData file : files) {
+                ZipEntry entry = new ZipEntry(file.getFilename());
+                zos.putNextEntry(entry);
+
+                try (InputStream is = file.createInputStream()) {
+                    byte[] buf = new byte[1024];
+                    int read;
+                    while ((read = is.read(buf)) > 0) {
+                        zos.write(buf, 0, read);
+                    }
+                }
+            }
+        }
+    }
+
+    public void writeSignedManifest(Manifest manifest, ZipOutputStream zos, PrivateKey privKey, X509Certificate cert) throws Exception {
+        zos.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
+        manifest.write(zos);
+        zos.closeEntry();
+
+        long now = System.currentTimeMillis();
+
+        // Determine the signature-file manifest...
+        Manifest sf = createSignatureFile(manifest);
+
+        byte[] sfRawBytes;
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            sf.write(baos);
+            sfRawBytes = baos.toByteArray();
+        }
+
+        ZipEntry sigFileEntry = new ZipEntry(m_baseName.concat(".SF"));
+        sigFileEntry.setTime(now);
+        zos.putNextEntry(sigFileEntry);
+        // Write the actual entry data...
+        zos.write(sfRawBytes, 0, sfRawBytes.length);
+        zos.closeEntry();
+
+        // Create a PKCS#7 signature...
+        byte[] encoded = calculateSignatureBlock(privKey, cert, sfRawBytes);
+
+        ZipEntry blockFileEntry = new ZipEntry(m_baseName.concat(getBlockFileExtension(privKey)));
+        blockFileEntry.setTime(now);
+        zos.putNextEntry(blockFileEntry);
+        zos.write(encoded);
+        zos.closeEntry();
+    }
+
+    private String calculateDigest(ArtifactData file) throws IOException {
+        m_digest.reset();
+        try (InputStream is = file.createInputStream()) {
+            byte[] buffer = new byte[1024];
+            int read;
+            while ((read = is.read(buffer)) > 0) {
+                m_digest.update(buffer, 0, read);
+            }
+        }
+        return Base64.encodeBase64String(m_digest.digest());
+    }
+
+    private String calculateDigest(byte[] rawData) throws IOException {
+        m_digest.reset();
+        m_digest.update(rawData, 0, rawData.length);
+        return Base64.encodeBase64String(m_digest.digest());
+    }
+
+    private byte[] calculateSignatureBlock(PrivateKey privKey, X509Certificate cert, byte[] sfRawBytes) throws Exception {
+        String signatureAlgorithm = getSignatureAlgorithm(privKey);
+        
+        DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().build();
+        ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).build(privKey);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider).build(signer, cert));
+        gen.addCertificates(new JcaCertStore(Arrays.asList(cert)));
+
+        CMSSignedData sigData = gen.generate(new CMSProcessableByteArray(sfRawBytes));
+
+        return sigData.getEncoded();
+    }
+
+    private Manifest createSignatureFile(Manifest manifest) throws IOException {
+        byte[] mfRawBytes;
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            manifest.write(baos);
+            mfRawBytes = baos.toByteArray();
+        }
+
+        Manifest sf = new Manifest();
+        Attributes sfMain = sf.getMainAttributes();
+        Map<String, Attributes> sfEntries = sf.getEntries();
+
+        sfMain.put(Attributes.Name.SIGNATURE_VERSION, "1.0");
+        sfMain.putValue("Created-By", "Apache Felix DeploymentPackageBuilder");
+        sfMain.putValue(m_digestAlg + "-Digest-Manifest", calculateDigest(mfRawBytes));
+        sfMain.putValue(m_digestAlg + "-Digest-Manifest-Main-Attribute", calculateDigest(getRawBytesMainAttributes(manifest)));
+
+        for (Entry<String, Attributes> entry : manifest.getEntries().entrySet()) {
+            String name = entry.getKey();
+            byte[] entryData = getRawBytesAttributes(entry.getValue());
+
+            sfEntries.put(name, getDigestAttributes(entryData));
+        }
+        return sf;
+    }
+
+    private Attributes getDigestAttributes(byte[] rawData) throws IOException {
+        Attributes attrs = new Attributes();
+        attrs.putValue(m_digestAlg + "-Digest", calculateDigest(rawData));
+        return attrs;
+    }
+
+    private byte[] getRawBytesAttributes(Attributes attrs) throws IOException {
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos)) {
+
+            Method m = Attributes.class.getDeclaredMethod("write", DataOutputStream.class);
+            m.setAccessible(true);
+            m.invoke(attrs, dos);
+
+            return baos.toByteArray();
+        }
+        catch (NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException e) {
+            throw new RuntimeException("Failed to get raw bytes of main attributes!", e);
+        }
+    }
+
+    private byte[] getRawBytesMainAttributes(Manifest manifest) throws IOException {
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos)) {
+            Attributes attrs = manifest.getMainAttributes();
+
+            Method m = Attributes.class.getDeclaredMethod("writeMain", DataOutputStream.class);
+            m.setAccessible(true);
+            m.invoke(attrs, dos);
+
+            return baos.toByteArray();
+        }
+        catch (NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException e) {
+            throw new RuntimeException("Failed to get raw bytes of main attributes!", e);
+        }
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DeploymentPackageBuilder.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DeploymentPackageBuilder.java
index 727f20d..a050ef3 100644
--- a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DeploymentPackageBuilder.java
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DeploymentPackageBuilder.java
@@ -25,6 +25,8 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URL;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -32,9 +34,11 @@
 import java.util.Map;
 import java.util.jar.Attributes;
 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.osgi.framework.Version;
 
@@ -100,6 +104,27 @@
 
     private static final int BUFFER_SIZE = 32 * 1024;
 
+    private final DPSigner m_signer;
+    private final String m_symbolicName;
+    private final String m_version;
+    private final List<ArtifactData> m_localizationFiles = new ArrayList<ArtifactData>();
+    private final List<ArtifactData> m_bundles = new ArrayList<ArtifactData>();
+    private final List<ArtifactData> m_processors = new ArrayList<ArtifactData>();
+    private final List<ArtifactData> m_artifacts = new ArrayList<ArtifactData>();
+
+    private String m_fixPackageVersion;
+    private boolean m_verification;
+    private PrivateKey m_signingKey;
+    private X509Certificate m_signingCert;
+
+    private DeploymentPackageBuilder(String symbolicName, String version) {
+        m_symbolicName = symbolicName;
+        m_version = version;
+
+        m_verification = true;
+        m_signer = new DPSigner();
+    }
+
     /**
      * Creates a new deployment package builder.
      * 
@@ -111,25 +136,15 @@
         return new DeploymentPackageBuilder(name, version);
     }
 
-    private final String m_symbolicName;
-    private final String m_version;
-    private final List<ArtifactData> m_localizationFiles = new ArrayList<ArtifactData>();
-    private final List<ArtifactData> m_bundles = new ArrayList<ArtifactData>();
-    private final List<ArtifactData> m_processors = new ArrayList<ArtifactData>();
-
-    private final List<ArtifactData> m_artifacts = new ArrayList<ArtifactData>();
-
-    private String m_fixPackageVersion;
-
-    private boolean m_addSignatures;
-    private boolean m_verification;
-
-    private DeploymentPackageBuilder(String symbolicName, String version) {
-        m_symbolicName = symbolicName;
-        m_version = version;
-
-        m_addSignatures = false;
-        m_verification = true;
+    static void closeSilently(Closeable resource) {
+        if (resource != null) {
+            try {
+                resource.close();
+            }
+            catch (IOException e) {
+                // Ignore...
+            }
+        }
     }
 
     /**
@@ -156,11 +171,6 @@
         return this;
     }
 
-    public DeploymentPackageBuilder addSignatures() {
-        m_addSignatures = true;
-        return this;
-    }
-
     /**
      * Creates a new deployment package builder with the same symbolic name as this builder.
      * 
@@ -210,7 +220,6 @@
     public InputStream generate() throws Exception {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         generate(baos);
-
         return new ByteArrayInputStream(baos.toByteArray());
     }
 
@@ -223,26 +232,8 @@
      * @throws Exception if something goes wrong while validating or generating
      */
     public void generate(OutputStream output) throws Exception {
-        List<ArtifactData> artifacts = new ArrayList<ArtifactData>();
-        artifacts.addAll(m_localizationFiles);
-        artifacts.addAll(m_bundles);
-        artifacts.addAll(m_processors);
-        artifacts.addAll(m_artifacts);
-
-        if (m_verification) {
-            validateProcessedArtifacts();
-            validateMissingArtifacts(artifacts);
-        }
-
-        Manifest m = createManifest(artifacts);
-
-        // The order in which the actual entries are added to the JAR is different than we're using for the manifest...
-        artifacts.clear();
-        artifacts.addAll(m_bundles);
-        artifacts.addAll(m_processors);
-        artifacts.addAll(m_localizationFiles);
-        artifacts.addAll(m_artifacts);
-
+        Manifest m = createManifest();
+        List<ArtifactData> artifacts = getArtifactList();
         writeStream(artifacts, m, output);
     }
 
@@ -283,6 +274,47 @@
         return this;
     }
 
+    /**
+     * Enables the creating of a signed deployment package, equivalent to creating a signed JAR file.
+     * <p>
+     * This method assumes the use of self-signed certificates for the signing process.
+     * </p>
+     * 
+     * @param signingKey the private key of the signer;
+     * @param signingCert the public certificate of the signer.
+     * @return this builder.
+     */
+    public DeploymentPackageBuilder signOutput(PrivateKey signingKey, X509Certificate signingCert) {
+        m_signingKey = signingKey;
+        m_signingCert = signingCert;
+        return this;
+    }
+
+    final Manifest createManifest() throws Exception {
+        List<ArtifactData> artifacts = new ArrayList<ArtifactData>();
+        artifacts.addAll(m_localizationFiles);
+        artifacts.addAll(m_bundles);
+        artifacts.addAll(m_processors);
+        artifacts.addAll(m_artifacts);
+
+        if (m_verification) {
+            validateProcessedArtifacts();
+            validateMissingArtifacts(artifacts);
+        }
+
+        return createManifest(artifacts);
+    }
+
+    final List<ArtifactData> getArtifactList() {
+        // The order in which the actual entries are added to the JAR is different than we're using for the manifest...
+        List<ArtifactData> artifacts = new ArrayList<ArtifactData>();
+        artifacts.addAll(m_bundles);
+        artifacts.addAll(m_processors);
+        artifacts.addAll(m_localizationFiles);
+        artifacts.addAll(m_artifacts);
+        return artifacts;
+    }
+
     private Manifest createManifest(List<ArtifactData> files) throws Exception {
         Manifest manifest = new Manifest();
         Attributes main = manifest.getMainAttributes();
@@ -296,39 +328,40 @@
 
         Map<String, Attributes> entries = manifest.getEntries();
 
-        Iterator<ArtifactData> filesIter = files.iterator();
-        while (filesIter.hasNext()) {
-            ArtifactData file = filesIter.next();
-
-            Attributes a = new Attributes();
-            a.putValue("Name", file.getFilename());
+        for (ArtifactData file : files) {
+            Attributes attrs = new Attributes();
+            attrs.putValue("Name", file.getFilename());
 
             if (file.isBundle()) {
-                a.putValue("Bundle-SymbolicName", file.getSymbolicName());
-                a.putValue("Bundle-Version", file.getVersion());
+                attrs.putValue("Bundle-SymbolicName", file.getSymbolicName());
+                attrs.putValue("Bundle-Version", file.getVersion());
                 if (file.isCustomizer()) {
-                    a.putValue("DeploymentPackage-Customizer", "true");
-                    a.putValue("Deployment-ProvidesResourceProcessor", file.getProcessorPid());
+                    attrs.putValue("DeploymentPackage-Customizer", "true");
+                    attrs.putValue("Deployment-ProvidesResourceProcessor", file.getProcessorPid());
                 }
             }
             else if (file.isResourceProcessorNeeded()) {
-                a.putValue("Resource-Processor", file.getProcessorPid());
+                attrs.putValue("Resource-Processor", file.getProcessorPid());
             }
 
             if (file.isMissing()) {
-                a.putValue("DeploymentPackage-Missing", "true");
+                attrs.putValue("DeploymentPackage-Missing", "true");
             }
 
-            if (m_addSignatures) {
-                a.putValue("SHA-256-Digest", "bogusdata=");
+            if (isAddSignatures()) {
+                m_signer.addDigestAttribute(attrs, file);
             }
 
-            entries.put(file.getFilename(), a);
+            entries.put(file.getFilename(), attrs);
         }
 
         return manifest;
     }
 
+    private boolean isAddSignatures() {
+        return m_signingKey != null && m_signingCert != null;
+    }
+
     private void validateMissingArtifacts(List<ArtifactData> files) throws Exception {
         boolean missing = false;
 
@@ -367,44 +400,21 @@
         }
     }
 
-    private InputStream getArtifactDataInputStream(ArtifactData file) throws IOException {
-        ResourceFilter filter = file.getFilter();
-        if (filter != null) {
-            return filter.createInputStream(file.getURL());
-        }
-        return file.getURL().openStream();
-    }
-
     private void writeStream(List<ArtifactData> files, Manifest manifest, OutputStream outputStream) throws Exception {
         byte[] buffer = new byte[BUFFER_SIZE];
-        JarOutputStream output = null;
-        InputStream is = null;
-        try {
-            output = new JarOutputStream(outputStream, manifest);
 
-            if (m_addSignatures) {
-                // Empty file index...
-                output.putNextEntry(new JarEntry("META-INF/INDEX.LIST"));
-                output.write(new byte[0]);
-                output.closeEntry();
-                
-                // Create a signature file + signature block
-                Manifest mf = new Manifest();
-                mf.getMainAttributes().put(Attributes.Name.SIGNATURE_VERSION, "1.0");
-                mf.getMainAttributes().putValue("SHA-256-Digest-Manifest", "bogusdata=");
-
-                output.putNextEntry(new JarEntry("META-INF/DP.SF"));
-                mf.write(output);
-                output.closeEntry();
-
-                output.putNextEntry(new JarEntry("META-INF/DP.DSA"));
-                output.write(new byte[] { 1, 2, 3, 4 });
+        try (JarOutputStream output = new JarOutputStream(outputStream)) {
+            // Write out the manifest...
+            if (isAddSignatures()) {
+                m_signer.writeSignedManifest(manifest, output, m_signingKey, m_signingCert);
+            }
+            else {
+                output.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
+                manifest.write(output);
                 output.closeEntry();
             }
 
-            Iterator<ArtifactData> filesIter = files.iterator();
-            while (filesIter.hasNext()) {
-                ArtifactData file = filesIter.next();
+            for (ArtifactData file : files) {
                 if (file.isMissing()) {
                     // No need to write the 'missing' files...
                     continue;
@@ -412,34 +422,16 @@
 
                 output.putNextEntry(new JarEntry(file.getFilename()));
 
-                is = getArtifactDataInputStream(file);
-                try {
+                try (InputStream is = file.createInputStream()) {
                     int bytes;
                     while ((bytes = is.read(buffer)) != -1) {
                         output.write(buffer, 0, bytes);
                     }
                 }
                 finally {
-                    closeSilently(is);
-
                     output.closeEntry();
                 }
             }
         }
-        finally {
-            closeSilently(is);
-            closeSilently(output);
-        }
-    }
-
-    static void closeSilently(Closeable resource) {
-        if (resource != null) {
-            try {
-                resource.close();
-            }
-            catch (IOException e) {
-                // Ignore...
-            }
-        }
     }
 }