Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BaseSecurityTest.java15
-rw-r--r--bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BundleToJarInputStreamTest.java152
-rw-r--r--bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SecurityTestSuite.java2
-rw-r--r--bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SignedBundleTest.java92
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed.jarbin7259 -> 7587 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed_with_corrupt.jarbin7250 -> 0 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed.jarbin4846 -> 5046 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/signedJava16.jarbin0 -> 5129 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_corrupt.jarbin4827 -> 4295 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata.jarbin3559 -> 4970 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_added.jarbin3851 -> 5202 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_corrupt.jarbin3565 -> 4998 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_removed.jarbin3416 -> 4827 bytes
-rw-r--r--bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_sf_corrupted.jarbin4856 -> 4319 bytes
-rw-r--r--bundles/org.eclipse.osgi/META-INF/MANIFEST.MF2
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/SecureAction.java13
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxBundle.java42
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java39
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/ModuleClassLoader.java18
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java279
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/Base64.java201
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BundleToJarInputStream.java133
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java173
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java49
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java505
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java514
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java173
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java175
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFile.java148
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java315
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java213
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java19
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties20
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java192
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java82
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java32
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java10
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java6
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java7
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java7
40 files changed, 847 insertions, 2781 deletions
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BaseSecurityTest.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BaseSecurityTest.java
index 4ea5ea5d6..0fd8fc8f9 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BaseSecurityTest.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BaseSecurityTest.java
@@ -13,7 +13,11 @@
*******************************************************************************/
package org.eclipse.osgi.tests.security;
-import java.io.*;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -28,7 +32,10 @@ import org.eclipse.osgi.internal.service.security.KeyStoreTrustEngine;
import org.eclipse.osgi.service.security.TrustEngine;
import org.eclipse.osgi.signedcontent.SignedContentFactory;
import org.eclipse.osgi.tests.OSGiTestsActivator;
-import org.osgi.framework.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
public class BaseSecurityTest extends CoreTest {
@@ -145,14 +152,14 @@ public class BaseSecurityTest extends CoreTest {
return null;
}
- protected File getEntryFile(String entryPath) throws IOException {
+ protected static File getEntryFile(String entryPath) throws IOException {
URL entryURL = OSGiTestsActivator.getContext().getBundle().getEntry(entryPath);
if (entryURL == null)
return null;
return new File(FileLocator.toFileURL(entryURL).toExternalForm().substring(5));
}
- protected File copyEntryFile(String entryPath) throws IOException {
+ protected static File copyEntryFile(String entryPath) throws IOException {
URL entryURL = OSGiTestsActivator.getContext().getBundle().getEntry(entryPath);
if (entryURL == null)
return null;
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BundleToJarInputStreamTest.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BundleToJarInputStreamTest.java
new file mode 100644
index 000000000..52ffb2f1d
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/BundleToJarInputStreamTest.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2021 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.tests.security;
+
+import static org.eclipse.osgi.tests.security.BaseSecurityTest.copy;
+import static org.eclipse.osgi.tests.security.BaseSecurityTest.getEntryFile;
+import static org.eclipse.osgi.tests.security.BaseSecurityTest.getTestJarPath;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import org.eclipse.osgi.internal.signedcontent.BundleToJarInputStream;
+import org.eclipse.osgi.storage.bundlefile.DirBundleFile;
+import org.eclipse.osgi.tests.OSGiTestsActivator;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+public class BundleToJarInputStreamTest {
+
+ static private final List<String> testJarNames = Arrays.asList("multiply_signed", "SHA1withDSA", "SHA1withRSA",
+ "SHA256withRSA", "SHA384withRSA", "SHA512withRSA", "signed_tsa", "signed_with_corrupt",
+ "signed_with_metadata_added", "signed_with_metadata_corrupt", "signed_with_metadata_removed",
+ "signed_with_metadata", "signed_with_missing_digest", "signed_with_sf_corrupted", "signed", "signedJava16",
+ "test.bug252098", "unsigned");
+
+ @Test
+ public void testInputStreamEquality() throws IOException {
+ for (String testJarName : testJarNames) {
+ compareContent(testJarName);
+ }
+ }
+
+ private void compareContent(String testJarName) throws IOException {
+ File jar = getEntryFile(getTestJarPath(testJarName));
+ File extracted = extract(jar);
+ compare(jar, extracted);
+ }
+
+ private void compare(File jar, File extracted) throws IOException {
+ // Using ZipFile and ZipInputStream to avoid validation
+ try (ZipFile jarFile = new ZipFile(jar)) {
+ Set<String> validated = new LinkedHashSet<>();
+ BundleToJarInputStream inputToJar = new BundleToJarInputStream(new DirBundleFile(extracted, false));
+ try (ZipInputStream jarInput = new ZipInputStream(inputToJar)) {
+ for (ZipEntry extractedEntry = jarInput
+ .getNextEntry(); extractedEntry != null; extractedEntry = jarInput.getNextEntry()) {
+ if (!extractedEntry.isDirectory()) {
+ byte[] extractedBytes = getBytes(jarInput);
+ byte[] originalBytes = getBytes(
+ jarFile.getInputStream(jarFile.getEntry(extractedEntry.getName())));
+ assertArrayEquals("Wrong entry content: " + extractedEntry.getName(), originalBytes,
+ extractedBytes);
+ validated.add(extractedEntry.getName());
+ }
+ }
+ }
+ // make sure manifest and signature files are first
+ Iterator<String> validpaths = validated.iterator();
+ String first = validpaths.next();
+ if (first.toUpperCase().endsWith("META-INF/")) {
+ first = validpaths.next();
+ }
+ assertEquals("Expected manifest.", JarFile.MANIFEST_NAME, first.toUpperCase());
+ // If there are signature files, make sure they are before all other entries
+ AtomicReference<String> foundNonSignatureFile = new AtomicReference<>();
+ validpaths.forEachRemaining((s) -> {
+ if (isSignatureFile(s)) {
+ assertNull("Found non signature file before.", foundNonSignatureFile.get());
+ } else {
+ foundNonSignatureFile.compareAndSet(null, s);
+ }
+ });
+
+ for (Enumeration<? extends ZipEntry> originalEntries = jarFile.entries(); originalEntries
+ .hasMoreElements();) {
+ ZipEntry originalEntry = originalEntries.nextElement();
+ validated.remove(originalEntry.getName());
+ }
+ assertTrue("More paths extracted content: " + validated, validated.isEmpty());
+ }
+ }
+
+ private boolean isSignatureFile(String s) {
+ s = s.toUpperCase();
+ if (s.startsWith("META-INF/") && s.indexOf('/', "META-INF/".length()) == -1) { //$NON-NLS-1$ //$NON-NLS-2$
+ return s.endsWith(".SF") //$NON-NLS-1$
+ || s.endsWith(".DSA") //$NON-NLS-1$
+ || s.endsWith(".RSA") //$NON-NLS-1$
+ || s.endsWith(".EC"); //$NON-NLS-1$
+ }
+ return false;
+ }
+
+ byte[] getBytes(InputStream in) throws IOException {
+ ByteArrayOutputStream content = new ByteArrayOutputStream();
+ byte[] drain = new byte[4096];
+ for (int read = in.read(drain, 0, drain.length); read != -1; read = in.read(drain, 0, drain.length)) {
+ content.write(drain, 0, read);
+ }
+ return content.toByteArray();
+ }
+
+ private File extract(File jar) throws IOException {
+ BundleContext bc = OSGiTestsActivator.getContext();
+ File dir = bc.getDataFile("extracted/" + jar.getName());
+ if (dir.isDirectory()) {
+ return dir;
+ }
+ dir.mkdirs();
+ try (ZipFile jarFile = new ZipFile(jar)) {
+ for (Enumeration<? extends ZipEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
+ ZipEntry entry = entries.nextElement();
+ if (!entry.isDirectory()) {
+ try (InputStream in = jarFile.getInputStream(entry)) {
+ File destination = new File(dir, entry.getName());
+ destination.getParentFile().mkdirs();
+ copy(in, destination);
+ }
+ }
+ }
+ }
+ return dir;
+ }
+}
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SecurityTestSuite.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SecurityTestSuite.java
index 8bd1f9eb8..81b20714f 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SecurityTestSuite.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SecurityTestSuite.java
@@ -13,6 +13,7 @@
*******************************************************************************/
package org.eclipse.osgi.tests.security;
+import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
@@ -20,6 +21,7 @@ import junit.framework.TestSuite;
public class SecurityTestSuite extends TestCase {
public static Test suite() {
TestSuite suite = new TestSuite("Unit tests for Equinox security");
+ suite.addTest(new JUnit4TestAdapter(BundleToJarInputStreamTest.class));
// trust engine tests
suite.addTest(KeyStoreTrustEngineTest.suite());
// signed bundle tests - *uses* trust engine
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SignedBundleTest.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SignedBundleTest.java
index 12ea547a8..823e17d41 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SignedBundleTest.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/security/SignedBundleTest.java
@@ -14,7 +14,7 @@
package org.eclipse.osgi.tests.security;
import java.io.File;
-import java.security.SignatureException;
+import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import junit.framework.Test;
@@ -354,20 +354,23 @@ public class SignedBundleTest extends BaseSecurityTest {
SignedContentEntry[] entries = signedContent.getSignedEntries();
assertNotNull("Entries is null", entries);
for (SignedContentEntry entry : entries) {
+ SignerInfo[] entryInfos = entry.getSignerInfos();
+ assertNotNull("SignerInfo is null", entryInfos);
try {
entry.verify();
if ("org/eclipse/equinox/security/junit/CorruptClass.class".equals(entry.getName())) {
fail("Expected a corruption for: " + entry.getName());
}
+ assertEquals("wrong number of entry signers", 1, entryInfos.length);
+ assertEquals("Entry signer does not equal content signer", infos[0], entryInfos[0]);
} catch (InvalidContentException e) {
if (!"org/eclipse/equinox/security/junit/CorruptClass.class".equals(entry.getName())) {
fail("Unexpected corruption in: " + entry.getName(), e);
}
+ // no signers if entry is corrupt
+ assertEquals("wrong number of entry signers", 0, entryInfos.length);
}
- SignerInfo[] entryInfos = entry.getSignerInfos();
- assertNotNull("SignerInfo is null", entryInfos);
- assertEquals("wrong number of entry signers", 1, entryInfos.length);
- assertEquals("Entry signer does not equal content signer", infos[0], entryInfos[0]);
+
}
} catch (Exception e) {
@@ -629,20 +632,21 @@ public class SignedBundleTest extends BaseSecurityTest {
SignedContentEntry[] entries = signedContent.getSignedEntries();
assertNotNull("Entries is null", entries);
for (SignedContentEntry entry : entries) {
+ SignerInfo[] entryInfos = entry.getSignerInfos();
+ assertNotNull("SignerInfo is null", entryInfos);
try {
entry.verify();
if ("org/eclipse/equinox/security/junit/CorruptClass.class".equals(entry.getName())) {
fail("Expected a corruption for: " + entry.getName());
}
+ assertEquals("wrong number of entry signers", 1, entryInfos.length);
+ assertEquals("Entry signer does not equal content signer", infos[0], entryInfos[0]);
} catch (InvalidContentException e) {
if (!"org/eclipse/equinox/security/junit/CorruptClass.class".equals(entry.getName())) {
fail("Unexpected corruption in: " + entry.getName(), e);
}
+ assertEquals("wrong number of entry signers", 0, entryInfos.length);
}
- SignerInfo[] entryInfos = entry.getSignerInfos();
- assertNotNull("SignerInfo is null", entryInfos);
- assertEquals("wrong number of entry signers", 1, entryInfos.length);
- assertEquals("Entry signer does not equal content signer", infos[0], entryInfos[0]);
}
} catch (Exception e) {
@@ -742,7 +746,7 @@ public class SignedBundleTest extends BaseSecurityTest {
assertTrue("Content is not signed!!", signedContent.isSigned());
SignedContentEntry[] entries = signedContent.getSignedEntries();
assertNotNull("Entries is null", entries);
- assertEquals("Incorrect number of signed entries", 4, entries.length);
+ assertEquals("Incorrect number of signed entries", 5, entries.length);
for (SignedContentEntry entry : entries) {
entry.verify();
SignerInfo[] entryInfos = entry.getSignerInfos();
@@ -788,17 +792,21 @@ public class SignedBundleTest extends BaseSecurityTest {
assertTrue("Content is not signed!!", signedContent.isSigned());
SignedContentEntry[] entries = signedContent.getSignedEntries();
assertNotNull("Entries is null", entries);
- assertEquals("Incorrect number of signed entries", 4, entries.length);
+ assertEquals("Incorrect number of signed entries", 5, entries.length);
for (SignedContentEntry entry : entries) {
try {
entry.verify();
assertFalse("Wrong entry is validated: " + entry.getName(), "META-INF/test/test1.file".equals(entry.getName()));
+ SignerInfo[] entryInfos = entry.getSignerInfos();
+ assertNotNull("SignerInfo is null", entryInfos);
+ assertEquals("wrong number of entry signers", 1, entryInfos.length);
} catch (InvalidContentException e) {
assertEquals("Wrong entry is corrupted", "META-INF/test/test1.file", entry.getName());
+ SignerInfo[] entryInfos = entry.getSignerInfos();
+ assertNotNull("SignerInfo is null", entryInfos);
+ assertEquals("wrong number of entry signers", 0, entryInfos.length);
}
- SignerInfo[] entryInfos = entry.getSignerInfos();
- assertNotNull("SignerInfo is null", entryInfos);
- assertEquals("wrong number of entry signers", 1, entryInfos.length);
+
}
signedFile.delete();
assertFalse("File should not exist", signedFile.exists());
@@ -820,9 +828,8 @@ public class SignedBundleTest extends BaseSecurityTest {
for (SignedContentEntry entry : entries) {
try {
entry.verify();
- assertFalse("Wrong entry is validated: " + entry.getName(), "META-INF/test.file".equals(entry.getName()));
} catch (InvalidContentException e) {
- assertEquals("Wrong entry is corrupted", "META-INF/test.file", entry.getName());
+ fail("Unexpected verify error.", e);
}
SignerInfo[] entryInfos = entry.getSignerInfos();
assertNotNull("SignerInfo is null", entryInfos);
@@ -842,7 +849,7 @@ public class SignedBundleTest extends BaseSecurityTest {
try {
getSignedContentFactory().getSignedContent(signedFile);
fail("Should have gotten a SignatureException for file: " + signedFile);
- } catch (SignatureException e) {
+ } catch (IOException e) {
// expected
}
}
@@ -941,33 +948,68 @@ public class SignedBundleTest extends BaseSecurityTest {
// get the signed content for the bundle
SignedContent signedContent = getSignedContentFactory().getSignedContent(testBundle);
assertNotNull("SignedContent is null", signedContent);
+ // Notice that if all entries are missing that have corresponding digests then
+ // the built in JarFile APIs treat the JAR is unsigned
+ // check if it is signed
+ assertFalse("Should not be signed", signedContent.isSigned());
+ // get the signer infos
+ SignerInfo[] infos = signedContent.getSignerInfos();
+ assertNotNull("SignerInfo is null", infos);
+ assertEquals("wrong number of signers", 0, infos.length);
+
+ SignedContentEntry[] entries = signedContent.getSignedEntries();
+ assertNotNull("Entries is null", entries);
+ assertEquals("Expected no signed entries.", 0, entries.length);
+
+ } catch (Exception e) {
+ fail("Unexpected exception", e);
+ } finally {
+ try {
+ testBundle.uninstall();
+ } catch (Exception e) {
+ fail("Failed to uninstall bundle", e);
+ }
+ }
+ }
+
+ public void testSignedContentJava16() {
+
+ Bundle testBundle = null;
+ try {
+ testBundle = installBundle(getTestJarPath("signedJava16"));
+ getTrustEngine().addTrustAnchor(getTestCertificate("ca2_leafa"), "ca2_leafa");
+
+ // get the signed content for the bundle
+ SignedContent signedContent = getSignedContentFactory().getSignedContent(testBundle);
+ assertNotNull("SignedContent is null", signedContent);
// check if it is signed
assertTrue("Should be signed", signedContent.isSigned());
// get the signer infos
SignerInfo[] infos = signedContent.getSignerInfos();
assertNotNull("SignerInfo is null", infos);
assertEquals("wrong number of signers", 1, infos.length);
-
+ // check the signer validity
+ signedContent.checkValidity(infos[0]);
+ // check the signer trust
+ assertTrue("Signer is not trusted", infos[0].isTrusted());
+ // check the trust anchor
+ assertNotNull("Trust anchor is null", infos[0].getTrustAnchor());
+ // verify and validate the entries
SignedContentEntry[] entries = signedContent.getSignedEntries();
assertNotNull("Entries is null", entries);
for (SignedContentEntry entry : entries) {
- try {
- entry.verify();
- fail("Expected a corruption for: " + entry.getName());
- } catch (InvalidContentException e) {
- // expected
- }
+ entry.verify();
SignerInfo[] entryInfos = entry.getSignerInfos();
assertNotNull("SignerInfo is null", entryInfos);
assertEquals("wrong number of entry signers", 1, entryInfos.length);
assertEquals("Entry signer does not equal content signer", infos[0], entryInfos[0]);
}
-
} catch (Exception e) {
fail("Unexpected exception", e);
} finally {
try {
testBundle.uninstall();
+ getTrustEngine().removeTrustAnchor("ca2_leafa");
} catch (Exception e) {
fail("Failed to uninstall bundle", e);
}
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed.jar
index 82fc34897..98d74e1a3 100644
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed.jar
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed_with_corrupt.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed_with_corrupt.jar
deleted file mode 100644
index 84c087c41..000000000
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed_with_corrupt.jar
+++ /dev/null
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed.jar
index e1cad7b41..44a927c5a 100644
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed.jar
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signedJava16.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signedJava16.jar
new file mode 100644
index 000000000..24cf148f5
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signedJava16.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_corrupt.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_corrupt.jar
index e61fab446..9650af17e 100644
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_corrupt.jar
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_corrupt.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata.jar
index 9d693cae2..6bad54426 100644
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata.jar
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_added.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_added.jar
index e97d7c027..c7f20da18 100644
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_added.jar
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_added.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_corrupt.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_corrupt.jar
index fc717d2b1..e594e05b6 100644
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_corrupt.jar
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_corrupt.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_removed.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_removed.jar
index 3f6493bf3..b9206b34b 100644
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_removed.jar
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_metadata_removed.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_sf_corrupted.jar b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_sf_corrupted.jar
index 99b955046..3a5dbf540 100644
--- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_sf_corrupted.jar
+++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signed_with_sf_corrupted.jar
Binary files differ
diff --git a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
index a4d71fbba..397782315 100644
--- a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
@@ -46,7 +46,7 @@ Export-Package: org.eclipse.core.runtime.adaptor;x-friends:="org.eclipse.core.ru
org.eclipse.osgi.service.runnable;version="1.1",
org.eclipse.osgi.service.security;version="1.0",
org.eclipse.osgi.service.urlconversion;version="1.0",
- org.eclipse.osgi.signedcontent;version="1.0";uses:="org.osgi.framework",
+ org.eclipse.osgi.signedcontent;version="1.1";uses:="org.osgi.framework",
org.eclipse.osgi.storage;x-friends:="org.eclipse.osgi.tests",
org.eclipse.osgi.storage.bundlefile;x-internal:=true,
org.eclipse.osgi.storage.url.reference;x-internal:=true,
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/SecureAction.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/SecureAction.java
index 207bb1fec..e63b64281 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/SecureAction.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/SecureAction.java
@@ -28,6 +28,7 @@ import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Properties;
+import java.util.jar.JarFile;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import org.eclipse.osgi.container.Module;
@@ -323,13 +324,14 @@ public class SecureAction {
}
/**
- * Returns a ZipFile. Same as calling
- * new ZipFile(file)
- * @param file the file to get a ZipFile for
+ * Returns a ZipFile. Same as calling new ZipFile(file)
+ *
+ * @param file the file to get a ZipFile for
+ * @param verify whether or not to verify the zip file if it is signed.
* @return a ZipFile
* @throws IOException if an error occured
*/
- public ZipFile getZipFile(final File file) throws IOException {
+ public ZipFile getZipFile(final File file, final boolean verify) throws IOException {
try {
if (System.getSecurityManager() == null)
return new ZipFile(file);
@@ -337,6 +339,9 @@ public class SecureAction {
return AccessController.doPrivileged(new PrivilegedExceptionAction<ZipFile>() {
@Override
public ZipFile run() throws IOException {
+ if (verify) {
+ return new JarFile(file);
+ }
return new ZipFile(file);
}
}, controlContext);
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxBundle.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxBundle.java
index 924f20a60..a10414785 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxBundle.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxBundle.java
@@ -19,10 +19,15 @@ import java.io.InputStream;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.AccessController;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
+import java.security.SignatureException;
import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
@@ -357,7 +362,7 @@ public class EquinoxBundle implements Bundle, BundleReference {
private final Module module;
private final Object monitor = new Object();
private BundleContextImpl context;
- private volatile SignerInfo[] signerInfos;
+ private volatile SignedContent signedContent;
private class EquinoxModule extends Module {
@@ -483,7 +488,7 @@ public class EquinoxBundle implements Bundle, BundleReference {
public void update(InputStream input) throws BundleException {
Storage storage = equinoxContainer.getStorage();
storage.update(module, input);
- signerInfos = null;
+ signedContent = null;
}
@Override
@@ -746,18 +751,9 @@ public class EquinoxBundle implements Bundle, BundleReference {
@Override
public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
- SignedContentFactory factory = equinoxContainer.getSignedContentFactory();
- if (factory == null) {
- return Collections.emptyMap();
- }
-
try {
- SignerInfo[] infos = signerInfos;
- if (infos == null) {
- SignedContent signedContent = factory.getSignedContent(this);
- infos = signedContent.getSignerInfos();
- signerInfos = infos;
- }
+ SignedContent current = getSignedContent();
+ SignerInfo[] infos = current == null ? null : current.getSignerInfos();
if (infos.length == 0)
return Collections.emptyMap();
Map<X509Certificate, List<X509Certificate>> results = new HashMap<>(infos.length);
@@ -964,9 +960,29 @@ public class EquinoxBundle implements Bundle, BundleReference {
Generation current = (Generation) module.getCurrentRevision().getRevisionInfo();
return (A) current.getDomain();
}
+ if (SignedContent.class.equals(adapterType)) {
+ return (A) getSignedContent();
+ }
return null;
}
+ private SignedContent getSignedContent() {
+ SignedContent current = signedContent;
+ if (current == null) {
+ SignedContentFactory factory = equinoxContainer.getSignedContentFactory();
+ if (factory == null) {
+ return null;
+ }
+ try {
+ signedContent = current = factory.getSignedContent(this);
+ } catch (InvalidKeyException | SignatureException | CertificateException | NoSuchAlgorithmException
+ | NoSuchProviderException | IOException e) {
+ return null;
+ }
+ }
+ return current;
+ }
+
/**
* Check for permission to adapt.
*/
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
index 70e7ea0b0..9f181d11b 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
@@ -249,6 +249,21 @@ public class EquinoxConfiguration implements EnvironmentInfo {
public static final String PROP_SECURE_UUID = "equinox.uuid.secure"; //$NON-NLS-1$
+ public final static String SIGNED_BUNDLE_SUPPORT = "osgi.support.signature.verify"; //$NON-NLS-1$
+ public final static String SIGNED_CONTENT_SUPPORT = "osgi.signedcontent.support"; //$NON-NLS-1$
+ public static final int SIGNED_CONTENT_VERIFY_CERTIFICATE = 0x01;
+ public static final int SIGNED_CONTENT_VERIFY_TRUST = 0x02;
+ public static final int SIGNED_CONTENT_VERIFY_RUNTIME = 0x04;
+ public static final int SIGNED_CONTENT_VERIFY_ALL = SIGNED_CONTENT_VERIFY_CERTIFICATE | SIGNED_CONTENT_VERIFY_TRUST
+ | SIGNED_CONTENT_VERIFY_RUNTIME;
+ private final static String SIGNED_CONTENT_SUPPORT_CERTIFICATE = "certificate"; //$NON-NLS-1$
+ private final static String SIGNED_CONTENT_SUPPORT_TRUST = "trust"; //$NON-NLS-1$
+ private final static String SIGNED_CONTENT_SUPPORT_RUNTIME = "runtime"; //$NON-NLS-1$
+ private final static String SIGNED_CONTENT_SUPPORT_ALL = "all"; //$NON-NLS-1$
+ private final static String SIGNED_CONTENT_SUPPORT_TRUE = "true"; //$NON-NLS-1$
+ public final int supportSignedBundles;
+ public final boolean runtimeVerifySignedBundles;
+
public static final class ConfigValues {
/**
* Value of {@link #localConfig} properties that should be considered
@@ -611,7 +626,6 @@ public class EquinoxConfiguration implements EnvironmentInfo {
throwErrorOnFailedStart = "true".equals(getConfiguration(PROP_COMPATIBILITY_ERROR_FAILED_START, "true")); //$NON-NLS-1$//$NON-NLS-2$
- CLASS_CERTIFICATE = Boolean.valueOf(getConfiguration(PROP_CLASS_CERTIFICATE_SUPPORT, "true")).booleanValue(); //$NON-NLS-1$
PARALLEL_CAPABLE = CLASS_LOADER_TYPE_PARALLEL.equals(getConfiguration(PROP_CLASS_LOADER_TYPE));
// A specified osgi.dev property but unspecified osgi.checkConfiguration
@@ -621,6 +635,29 @@ public class EquinoxConfiguration implements EnvironmentInfo {
if (inCheckConfigurationMode && getConfiguration(PROP_CHECK_CONFIGURATION) == null) {
setConfiguration(PROP_CHECK_CONFIGURATION, "true"); //$NON-NLS-1$
}
+ supportSignedBundles = getSupportSignedBundles(this);
+ CLASS_CERTIFICATE = (supportSignedBundles & SIGNED_CONTENT_VERIFY_CERTIFICATE) != 0 && //
+ Boolean.valueOf(getConfiguration(PROP_CLASS_CERTIFICATE_SUPPORT, "true")).booleanValue(); //$NON-NLS-1$
+ runtimeVerifySignedBundles = (supportSignedBundles & SIGNED_CONTENT_VERIFY_RUNTIME) != 0;
+ }
+
+ private static int getSupportSignedBundles(EquinoxConfiguration config) {
+ int supportSignedBundles = 0;
+ String[] supportOptions = ManifestElement.getArrayFromList(
+ config.getConfiguration(SIGNED_CONTENT_SUPPORT, config.getConfiguration(SIGNED_BUNDLE_SUPPORT)), ","); //$NON-NLS-1$
+ for (String supportOption : supportOptions) {
+ if (SIGNED_CONTENT_SUPPORT_CERTIFICATE.equals(supportOption)) {
+ supportSignedBundles |= SIGNED_CONTENT_VERIFY_CERTIFICATE;
+ } else if (SIGNED_CONTENT_SUPPORT_TRUST.equals(supportOption)) {
+ supportSignedBundles |= SIGNED_CONTENT_VERIFY_CERTIFICATE | SIGNED_CONTENT_VERIFY_TRUST;
+ } else if (SIGNED_CONTENT_SUPPORT_RUNTIME.equals(supportOption)) {
+ supportSignedBundles |= SIGNED_CONTENT_VERIFY_CERTIFICATE | SIGNED_CONTENT_VERIFY_RUNTIME;
+ } else if (SIGNED_CONTENT_SUPPORT_TRUE.equals(supportOption)
+ || SIGNED_CONTENT_SUPPORT_ALL.equals(supportOption)) {
+ supportSignedBundles |= SIGNED_CONTENT_VERIFY_ALL;
+ }
+ }
+ return supportSignedBundles;
}
private URL getConfigIni(EquinoxLocations locations, boolean parent) {
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/ModuleClassLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/ModuleClassLoader.java
index 31aa057b6..c09cfa8b7 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/ModuleClassLoader.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/ModuleClassLoader.java
@@ -37,7 +37,6 @@ import org.eclipse.osgi.signedcontent.SignedContent;
import org.eclipse.osgi.signedcontent.SignerInfo;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.eclipse.osgi.storage.bundlefile.BundleFile;
-import org.eclipse.osgi.storage.bundlefile.BundleFileWrapperChain;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleReference;
@@ -367,14 +366,15 @@ public abstract class ModuleClassLoader extends ClassLoader implements BundleRef
permissions = ALLPERMISSIONS;
}
Certificate[] certs = null;
- SignedContent signedContent = null;
- if (bundlefile instanceof BundleFileWrapperChain) {
- signedContent = ((BundleFileWrapperChain) bundlefile).getWrappedType(SignedContent.class);
- }
- if (getConfiguration().CLASS_CERTIFICATE && signedContent != null && signedContent.isSigned()) {
- SignerInfo[] signers = signedContent.getSignerInfos();
- if (signers.length > 0)
- certs = signers[0].getCertificateChain();
+ if (getConfiguration().CLASS_CERTIFICATE) {
+ Bundle b = getBundle();
+ SignedContent signedContent = b == null ? null : b.adapt(SignedContent.class);
+ if (signedContent != null && signedContent.isSigned()) {
+ SignerInfo[] signers = signedContent.getSignerInfos();
+ if (signers.length > 0) {
+ certs = signers[0].getCertificateChain();
+ }
+ }
}
File file = bundlefile.getBaseFile();
// Bug 477787: file will be null when the osgi.framework configuration property contains an invalid value.
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java
deleted file mode 100644
index 567c78abd..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2006, 2019 IBM Corporation and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.security.SignatureException;
-
-/**
- * This is a simple class that processes BER structures. This class
- * uses BER processing as outlined in X.690.
- */
-public class BERProcessor {
- /**
- * This is the buffer that contains the BER structures that are being interrogated.
- */
- byte buffer[];
- /**
- * The offset into <code>buffer</code> to the start of the structure being interrogated.
- * If the offset is -1 that means that we have read the last structure.
- */
- int offset;
- /**
- * The last valid offset in <code>buffer</code>.
- */
- int lastOffset;
- /**
- * The offset into <code>buffer</code> to the start of the content of the structure
- * being interrogated.
- */
- int contentOffset;
- /**
- * The length of the content of the structure being interrogated.
- */
- int contentLength;
- /**
- * The offset into <code>buffer</code> of the end of the structure being interrogated.
- */
- int endOffset;
- /**
- * The class of the tag of the current structure.
- */
- int classOfTag;
- static final int UNIVERSAL_TAGCLASS = 0;
- static final int APPLICATION_TAGCLASS = 1;
- static final int CONTEXTSPECIFIC_TAGCLASS = 2;
- static final int PRIVATE_TAGCLASS = 3;
-
- static final byte BOOLTAG = 1;
- static final byte INTTAG = 2;
- static final byte OIDTAG = 6;
- static final byte SEQTAG = 16;
- static final byte SETTAG = 17;
- static final byte NULLTAG = 5;
-
- /**
- * Tagnames used in toString()
- */
- static final String tagNames[] = {"<null>", "boolean", "int", "bitstring", "octetstring", "null", "objid", "objdesc", "external", "real", "enum", "pdv", "utf8", "relobjid", "resv", "resv", "sequence", "set", "char string"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ //$NON-NLS-13$ //$NON-NLS-14$ //$NON-NLS-15$ //$NON-NLS-16$ //$NON-NLS-17$ //$NON-NLS-18$ //$NON-NLS-19$
-
- /**
- * True if this is a structure for a constructed encoding.
- */
- public boolean constructed;
- /**
- * The tag type. Note that X.690 specifies encodings for tags with values greater than 31,
- * but currently this class does not handle these kinds of tags.
- */
- public byte tag;
-
- /**
- * Constructs a BERProcessor to operate on the passed buffer. The first structure in the
- * buffer will be processed before this method returns.
- *
- * @param buffer the buffer containing the BER structures.
- * @param offset the offset into <code>buffer</code> to the start of the first structure.
- * @param len the length of the BER structure.
- * @throws SignatureException
- */
- public BERProcessor(byte buffer[], int offset, int len) throws SignatureException {
- this.buffer = buffer;
- this.offset = offset;
- lastOffset = len + offset;
- processStructure();
- }
-
- /**
- * Parse the structure found at the current <code>offset</code> into <code>buffer</code>.
- * Most methods, constructor, and stepinto, will call this method automatically. If
- * <code>offset</code> is modified outside of those methods, this method will need to
- * be invoked.
- */
- public void processStructure() throws SignatureException {
- // Don't process if we are at the end
- if (offset == -1)
- return;
- endOffset = offset;
- // section 8.1.2.2
- classOfTag = (buffer[offset] & 0xff) >> 6;
- // section 8.1.2.5
- constructed = (buffer[offset] & 0x20) != 0;
- // section 8.1.2.3
- byte tagNumber = (byte) (buffer[offset] & 0x1f);
- if (tagNumber < 32) {
- tag = tagNumber;
- endOffset = offset + 1;
- } else {
- throw new SignatureException("Can't handle tags > 32"); //$NON-NLS-1$
- }
- if ((buffer[endOffset] & 0x80) == 0) {
- // section 8.1.3.4 (doing the short form of the length)
- contentLength = buffer[endOffset];
- endOffset++;
- } else {
- // section 8.1.3.5 (doing the long form of the length)
- int octetCount = buffer[endOffset] & 0x7f;
- if (octetCount > 3)
- throw new SignatureException("ContentLength octet count too large: " + octetCount); //$NON-NLS-1$
- contentLength = 0;
- endOffset++;
- for (int i = 0; i < octetCount; i++) {
- contentLength <<= 8;
- contentLength |= buffer[endOffset] & 0xff;
- endOffset++;
- }
- // section 8.1.3.6 (doing the indefinite form
- if (octetCount == 0)
- contentLength = -1;
- }
- contentOffset = endOffset;
- if (contentLength != -1)
- endOffset += contentLength;
- if (endOffset > lastOffset)
- throw new SignatureException("Content length too large: " + endOffset + " > " + lastOffset); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- /**
- * Returns a String representation of the current BER structure.
- * @return a String representation of the current BER structure.
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- switch (classOfTag) {
- case UNIVERSAL_TAGCLASS :
- sb.append('U');
- break;
- case APPLICATION_TAGCLASS :
- sb.append('A');
- break;
- case CONTEXTSPECIFIC_TAGCLASS :
- sb.append('C');
- break;
- case PRIVATE_TAGCLASS :
- sb.append('P');
- break;
- }
- sb.append(constructed ? 'C' : 'P');
- sb.append(" tag=" + tag); //$NON-NLS-1$
- if (tag < tagNames.length) {
- sb.append("(" + tagNames[tag] + ")"); //$NON-NLS-1$ //$NON-NLS-2$
- }
- sb.append(" len="); //$NON-NLS-1$
- sb.append(contentLength);
- switch (tag) {
- case INTTAG :
- sb.append(" value=" + getIntValue()); //$NON-NLS-1$
- break;
- case OIDTAG :
- sb.append(" value="); //$NON-NLS-1$
- int oid[] = getObjId();
- for (int i = 0; i < oid.length; i++) {
- if (i > 0)
- sb.append('.');
- sb.append(oid[i]);
- }
- }
- if (tag == 12 || (tag >= 18 && tag <= 22) || (tag >= 25 && tag <= 30)) {
- sb.append(" value="); //$NON-NLS-1$
- sb.append(getString());
- }
- return sb.toString();
- }
-
- /**
- * Returns a BERProcessor for the content of the current structure.
- * @throws SignatureException
- */
- public BERProcessor stepInto() throws SignatureException {
- return new BERProcessor(buffer, contentOffset, contentLength);
- }
-
- public void stepOver() throws SignatureException {
- offset = endOffset;
- if (endOffset >= lastOffset) {
- offset = -1;
- return;
- }
- processStructure();
- }
-
- public boolean endOfSequence() {
- return offset == -1;
- }
-
- /**
- * Gets the content from the current structure as a String.
- * @return the content from the current structure as a String.
- */
- public String getString() {
- return new String(buffer, contentOffset, contentLength, StandardCharsets.UTF_8);
- }
-
- /**
- * Gets the content from the current structure as an int.
- * @return the content from the current structure as an int.
- */
- public BigInteger getIntValue() {
- return new BigInteger(getBytes());
- }
-
- /**
- * Gets the content from the current structure as an object id (int[]).
- * @return the content from the current structure as an object id (int[]).
- */
- public int[] getObjId() {
- // First count the ids
- int count = 0;
- for (int i = 0; i < contentLength; i++) {
- // section 8.19.2
- if ((buffer[contentOffset + i] & 0x80) == 0)
- count++;
- }
- count++; // section 8.19.3
- int oid[] = new int[count];
- int index = 0;
- int currentValue = 0;
- for (int i = 0; i < contentLength; i++) {
- currentValue <<= 7;
- currentValue |= buffer[contentOffset + i] & 0x7f;
- // section 8.19.2
- if ((buffer[contentOffset + i] & 0x80) == 0) {
- if (index == 0) {
- // section 8.19.4 special processing
- oid[index++] = currentValue / 40;
- oid[index++] = currentValue % 40;
- } else {
- oid[index++] = currentValue;
- }
- currentValue = 0;
- }
- }
- return oid;
- }
-
- /**
- * Get a copy of the bytes in the content of the current structure.
- * @return a copy of the bytes in the content of the current structure.
- */
- public byte[] getBytes() {
- byte v[] = new byte[contentLength];
- System.arraycopy(buffer, contentOffset, v, 0, contentLength);
- return v;
- }
-
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/Base64.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/Base64.java
deleted file mode 100644
index 940b6b5d8..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/Base64.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2006, 2012 IBM Corporation and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
- *******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-public class Base64 {
-
- private static final byte equalSign = (byte) '=';
-
- static char digits[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', //
- 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', //
- 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
- 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
-
- /**
- * This method decodes the byte array in base 64 encoding into a char array
- * Base 64 encoding has to be according to the specification given by the
- * RFC 1521 (5.2).
- *
- * @param data the encoded byte array
- * @return the decoded byte array
- */
- public static byte[] decode(byte[] data) {
- if (data.length == 0)
- return data;
- int lastRealDataIndex = data.length - 1;
- while (data[lastRealDataIndex] == equalSign)
- lastRealDataIndex--;
- // original data digit is 8 bits long, but base64 digit is 6 bits long
- int padBytes = data.length - 1 - lastRealDataIndex;
- int byteLength = data.length * 6 / 8 - padBytes;
- byte[] result = new byte[byteLength];
- // Each 4 bytes of input (encoded) we end up with 3 bytes of output
- int dataIndex = 0;
- int resultIndex = 0;
- int allBits = 0;
- // how many result chunks we can process before getting to pad bytes
- int resultChunks = (lastRealDataIndex + 1) / 4;
- for (int i = 0; i < resultChunks; i++) {
- allBits = 0;
- // Loop 4 times gathering input bits (4 * 6 = 24)
- for (int j = 0; j < 4; j++)
- allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
- // Loop 3 times generating output bits (3 * 8 = 24)
- for (int j = resultIndex + 2; j >= resultIndex; j--) {
- result[j] = (byte) (allBits & 0xff); // Bottom 8 bits
- allBits = allBits >>> 8;
- }
- resultIndex += 3; // processed 3 result bytes
- }
- // Now we do the extra bytes in case the original (non-encoded) data
- // was not multiple of 3 bytes
- switch (padBytes) {
- case 1 :
- // 1 pad byte means 3 (4-1) extra Base64 bytes of input, 18
- // bits, of which only 16 are meaningful
- // Or: 2 bytes of result data
- allBits = 0;
- // Loop 3 times gathering input bits
- for (int j = 0; j < 3; j++)
- allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
- // NOTE - The code below ends up being equivalent to allBits =
- // allBits>>>2
- // But we code it in a non-optimized way for clarity
- // The 4th, missing 6 bits are all 0
- allBits = allBits << 6;
- // The 3rd, missing 8 bits are all 0
- allBits = allBits >>> 8;
- // Loop 2 times generating output bits
- for (int j = resultIndex + 1; j >= resultIndex; j--) {
- result[j] = (byte) (allBits & 0xff); // Bottom 8
- // bits
- allBits = allBits >>> 8;
- }
- break;
- case 2 :
- // 2 pad bytes mean 2 (4-2) extra Base64 bytes of input, 12 bits
- // of data, of which only 8 are meaningful
- // Or: 1 byte of result data
- allBits = 0;
- // Loop 2 times gathering input bits
- for (int j = 0; j < 2; j++)
- allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
- // NOTE - The code below ends up being equivalent to allBits =
- // allBits>>>4
- // But we code it in a non-optimized way for clarity
- // The 3rd and 4th, missing 6 bits are all 0
- allBits = allBits << 6;
- allBits = allBits << 6;
- // The 3rd and 4th, missing 8 bits are all 0
- allBits = allBits >>> 8;
- allBits = allBits >>> 8;
- result[resultIndex] = (byte) (allBits & 0xff); // Bottom
- // 8
- // bits
- break;
- }
- return result;
- }
-
- /**
- * This method converts a Base 64 digit to its numeric value.
- *
- * @param data digit (character) to convert
- * @return value for the digit
- */
- static int decodeDigit(byte data) {
- char charData = (char) data;
- if (charData <= 'Z' && charData >= 'A')
- return charData - 'A';
- if (charData <= 'z' && charData >= 'a')
- return charData - 'a' + 26;
- if (charData <= '9' && charData >= '0')
- return charData - '0' + 52;
- switch (charData) {
- case '+' :
- return 62;
- case '/' :
- return 63;
- default :
- throw new IllegalArgumentException("Invalid char to decode: " + data); //$NON-NLS-1$
- }
- }
-
- /**
- * This method encodes the byte array into a char array in base 64 according
- * to the specification given by the RFC 1521 (5.2).
- *
- * @param data the encoded char array
- * @return the byte array that needs to be encoded
- */
- public static byte[] encode(byte[] data) {
- int sourceChunks = data.length / 3;
- int len = ((data.length + 2) / 3) * 4;
- byte[] result = new byte[len];
- int extraBytes = data.length - (sourceChunks * 3);
- // Each 4 bytes of input (encoded) we end up with 3 bytes of output
- int dataIndex = 0;
- int resultIndex = 0;
- int allBits = 0;
- for (int i = 0; i < sourceChunks; i++) {
- allBits = 0;
- // Loop 3 times gathering input bits (3 * 8 = 24)
- for (int j = 0; j < 3; j++)
- allBits = (allBits << 8) | (data[dataIndex++] & 0xff);
- // Loop 4 times generating output bits (4 * 6 = 24)
- for (int j = resultIndex + 3; j >= resultIndex; j--) {
- result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
- // 6
- // bits
- allBits = allBits >>> 6;
- }
- resultIndex += 4; // processed 4 result bytes
- }
- // Now we do the extra bytes in case the original (non-encoded) data
- // is not multiple of 4 bytes
- switch (extraBytes) {
- case 1 :
- allBits = data[dataIndex++]; // actual byte
- allBits = allBits << 8; // 8 bits of zeroes
- allBits = allBits << 8; // 8 bits of zeroes
- // Loop 4 times generating output bits (4 * 6 = 24)
- for (int j = resultIndex + 3; j >= resultIndex; j--) {
- result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
- // 6
- // bits
- allBits = allBits >>> 6;
- }
- // 2 pad tags
- result[result.length - 1] = (byte) '=';
- result[result.length - 2] = (byte) '=';
- break;
- case 2 :
- allBits = data[dataIndex++]; // actual byte
- allBits = (allBits << 8) | (data[dataIndex++] & 0xff); // actual
- // byte
- allBits = allBits << 8; // 8 bits of zeroes
- // Loop 4 times generating output bits (4 * 6 = 24)
- for (int j = resultIndex + 3; j >= resultIndex; j--) {
- result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
- // 6
- // bits
- allBits = allBits >>> 6;
- }
- // 1 pad tag
- result[result.length - 1] = (byte) '=';
- break;
- }
- return result;
- }
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BundleToJarInputStream.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BundleToJarInputStream.java
new file mode 100644
index 000000000..cb70bdbe5
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BundleToJarInputStream.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2021 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import org.eclipse.osgi.storage.bundlefile.BundleEntry;
+import org.eclipse.osgi.storage.bundlefile.BundleFile;
+
+/*
+ * Converts a BundleFile into an input stream that is appropriate for
+ * creating a JarInputStream.
+ */
+public final class BundleToJarInputStream extends InputStream {
+ private final BundleFile bundlefile;
+ private final Iterator<String> entryPaths;
+ private final JarOutputStream jarOutput;
+ private final ByteArrayOutputStream nextEntryOutput = new ByteArrayOutputStream();
+
+ private ByteArrayInputStream nextEntryInput = null;
+
+ public BundleToJarInputStream(BundleFile bundleFile) throws IOException {
+ this.bundlefile = bundleFile;
+ List<String> entries = new ArrayList<>();
+ int signatureFileCnt = 0;
+ for (Enumeration<String> ePaths = bundleFile.getEntryPaths("", true); ePaths.hasMoreElements();) { //$NON-NLS-1$
+ String entry = ePaths.nextElement();
+ if (entry.equals(JarFile.MANIFEST_NAME)) {
+ // this is always read into the stream first and entries follow
+ entries.add(0, entry);
+ signatureFileCnt++;
+
+ } else if (isSignatureFile(entry)) {
+ // Add signature files directly after manifest.
+ entries.add(signatureFileCnt++, entry);
+ } else {
+ // everything else is just at the end in order of the enumeration
+ entries.add(entry);
+ }
+ }
+
+ entryPaths = entries.iterator();
+
+ jarOutput = new JarOutputStream(nextEntryOutput);
+ }
+
+ private boolean isSignatureFile(String entry) {
+ entry = entry.toUpperCase();
+ if (entry.startsWith("META-INF/") && entry.indexOf('/', "META-INF/".length()) == -1) { //$NON-NLS-1$ //$NON-NLS-2$
+ return entry.endsWith(".SF") //$NON-NLS-1$
+ || entry.endsWith(".DSA") //$NON-NLS-1$
+ || entry.endsWith(".RSA") //$NON-NLS-1$
+ || entry.endsWith(".EC"); //$NON-NLS-1$
+ }
+ return false;
+ }
+
+ public int read() throws IOException {
+ if (nextEntryInput != null) {
+ int result = nextEntryInput.read();
+ if (result != -1) {
+ return result;
+ }
+
+ // this entry is done force a new one to be read if there is a next
+ nextEntryInput = null;
+ return read();
+
+ }
+
+ if (entryPaths.hasNext()) {
+ readNext(entryPaths.next());
+
+ if (!entryPaths.hasNext()) {
+ jarOutput.close();
+ }
+ } else {
+ jarOutput.close();
+ return -1;
+ }
+
+ return read();
+ }
+
+ private void readNext(String path) throws IOException {
+ BundleEntry found = bundlefile.getEntry(path);
+ if (found == null) {
+ throw new IOException("No entry found: " + path); //$NON-NLS-1$
+ }
+
+ nextEntryOutput.reset();
+ JarEntry entry = new JarEntry(path);
+ jarOutput.putNextEntry(entry);
+ if (!entry.isDirectory()) {
+ try (InputStream source = found.getInputStream()) {
+ byte[] buf = new byte[8192];
+ int length;
+ while ((length = source.read(buf)) > 0) {
+ jarOutput.write(buf, 0, length);
+ }
+ }
+ }
+
+
+ jarOutput.closeEntry();
+ jarOutput.flush();
+
+ // now save off the entry we just wrote
+ nextEntryInput = new ByteArrayInputStream(nextEntryOutput.toByteArray());
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java
deleted file mode 100644
index 812bcad8c..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2006, 2012 IBM Corporation and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import org.eclipse.osgi.signedcontent.InvalidContentException;
-import org.eclipse.osgi.signedcontent.SignerInfo;
-import org.eclipse.osgi.storage.bundlefile.BundleEntry;
-import org.eclipse.osgi.storage.bundlefile.BundleFile;
-import org.eclipse.osgi.util.NLS;
-
-/**
- * This InputStream will calculate the digest of bytes as they are read. At the
- * end of the InputStream, it will calculate the digests and throw an exception
- * if the calculated digest do not match the expected digests.
- */
-class DigestedInputStream extends FilterInputStream {
- private final MessageDigest digests[];
- private final byte result[][];
- private final BundleEntry entry;
- private final BundleFile bundleFile;
- private long remaining;
-
- /**
- * Constructs an InputStream that uses another InputStream as a source and
- * calculates the digest. At the end of the stream an exception will be
- * thrown if the calculated digest doesn't match the passed digest.
- *
- * @param in the stream to use as an input source.
- * @param signerInfos the signers.
- * @param results the expected digest.
- * @throws IOException
- * @throws NoSuchAlgorithmException
- */
- DigestedInputStream(BundleEntry entry, BundleFile bundleFile, SignerInfo[] signerInfos, byte results[][], long size) throws IOException, NoSuchAlgorithmException {
- super(entry.getInputStream());
- this.entry = entry;
- this.bundleFile = bundleFile;
- this.remaining = size;
- this.digests = new MessageDigest[signerInfos.length];
- for (int i = 0; i < signerInfos.length; i++)
- this.digests[i] = MessageDigest.getInstance(signerInfos[i].getMessageDigestAlgorithm());
- this.result = results;
- }
-
- /**
- * Not supported.
- */
- @Override
- public synchronized void mark(int readlimit) {
- // Noop, we don't want to support this
- }
-
- /**
- * Always returns false.
- */
- @Override
- public boolean markSupported() {
- return false;
- }
-
- /**
- * Read a byte from the InputStream. Digests are calculated on reads. At the
- * end of the stream the calculated digests must match the expected digests.
- *
- * @return the character read or -1 at end of stream.
- * @throws IOException if there was an problem reading the byte or at the
- * end of the stream the calculated digests do not match the
- * expected digests.
- * @see java.io.InputStream#read()
- */
- @Override
- public int read() throws IOException {
- if (remaining <= 0)
- return -1;
- int c = super.read();
- if (c != -1) {
- for (MessageDigest digest : digests) {
- digest.update((byte) c);
- }
- remaining--;
- } else {
- // We hit eof so set remaining to zero
- remaining = 0;
- }
- if (remaining == 0)
- verifyDigests();
- return c;
- }
-
- private void verifyDigests() throws InvalidContentException {
- // Check the digest at end of file
- for (int i = 0; i < digests.length; i++) {
- byte rc[] = digests[i].digest();
- if (!MessageDigest.isEqual(result[i], rc))
- throw new InvalidContentException(NLS.bind(SignedContentMessages.File_In_Jar_Is_Tampered, entry.getName(), bundleFile.getBaseFile()), null);
- }
- }
-
- /**
- * Read bytes from the InputStream. Digests are calculated on reads. At the
- * end of the stream the calculated digests must match the expected digests.
- *
- * @return the number of characters read or -1 at end of stream.
- * @throws IOException if there was an problem reading or at the
- * end of the stream the calculated digests do not match the
- * expected digests.
- * @see java.io.InputStream#read()
- */
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- if (remaining <= 0)
- return -1;
- int rc = super.read(b, off, len);
- if (rc != -1) {
- for (MessageDigest digest : digests) {
- digest.update(b, off, rc);
- }
- remaining -= rc;
- } else {
- // We hit eof so set remaining to zero
- remaining = 0;
- }
- if (remaining <= 0)
- verifyDigests();
- return rc;
- }
-
- /**
- * Not supported.
- *
- * @throws IOException always thrown if this method is called since mark/reset is not supported.
- * @see java.io.InputStream#reset()
- */
- @Override
- public synchronized void reset() throws IOException {
- // Throw IOException, we don't want to support this
- throw new IOException("Reset not supported"); //$NON-NLS-1$
- }
-
- /**
- * This method is implemented as a read into a bitbucket.
- */
- @Override
- public long skip(long n) throws IOException {
- byte buffer[] = new byte[4096];
- long count = 0;
- while (n - count > 0) {
- int rc = (n - count) > buffer.length ? buffer.length : (int) (n - count);
- rc = read(buffer, 0, rc);
- if (rc == -1)
- break;
- count += rc;
- n -= rc;
- }
- return count;
- }
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java
deleted file mode 100644
index 7bb56d42b..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2006, 2012 IBM Corporation and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which accompanies this distribution,
- * and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors: IBM Corporation - initial API and implementation
- ******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.security.*;
-import java.security.cert.CertificateException;
-import java.util.*;
-
-public class PKCS7DateParser {
-
- static Date parseDate(PKCS7Processor pkcs7Processor, String signer, String file) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, NoSuchProviderException {
- Map<int[], byte[]> unsignedAttrs = pkcs7Processor.getUnsignedAttrs();
- if (unsignedAttrs != null) {
- // get the timestamp construct
- byte[] timeStampConstruct = retrieveTimeStampConstruct(unsignedAttrs);
-
- // there is a timestamp in the signer info
- if (timeStampConstruct != null) {
- PKCS7Processor timestampProcess = new PKCS7Processor(timeStampConstruct, 0, timeStampConstruct.length, signer, file);
- timestampProcess.verifyCerts();
- pkcs7Processor.setTSACertificates(timestampProcess.getCertificates());
- return timestampProcess.getSigningTime();
- }
- }
- return null;
- }
-
- private static byte[] retrieveTimeStampConstruct(Map<int[], byte[]> unsignedAttrs) {
- Set<int[]> objIDs = unsignedAttrs.keySet();
- Iterator<int[]> iter = objIDs.iterator();
- while (iter.hasNext()) {
- int[] objID = iter.next();
- if (Arrays.equals(SignedContentConstants.TIMESTAMP_OID, objID)) {
- return unsignedAttrs.get(objID);
- }
- }
- return null;
- }
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java
deleted file mode 100644
index d8db5d543..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java
+++ /dev/null
@@ -1,505 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2006, 2019 IBM Corporation and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which accompanies this distribution,
- * and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors: IBM Corporation - initial API and implementation
- ******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.io.ByteArrayInputStream;
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-import javax.security.auth.x500.X500Principal;
-import org.eclipse.osgi.util.NLS;
-
-/**
- * This class processes a PKCS7 file. See RFC 2315 for specifics.
- */
-public class PKCS7Processor implements SignedContentConstants {
-
- static CertificateFactory certFact;
-
- static {
- try {
- certFact = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
- } catch (CertificateException e) {
- // TODO this is bad and will lead to NPEs
- // Should we just throw a runtime exception to fail <clinit>?
- }
- }
-
- private final String signer;
- private final String file;
-
- private Certificate[] certificates;
- private Certificate[] tsaCertificates;
-
- // key(object id) = value(structure)
- private Map<int[], byte[]> signedAttrs;
-
- // key(object id) = value(structure)
- private Map<int[], byte[]> unsignedAttrs;
-
- // store the signature of a signerinfo
- private byte signature[];
- private String digestAlgorithm;
- private String signatureAlgorithm;
-
- private Certificate signerCert;
- private Date signingTime;
-
- private static String oid2String(int oid[]) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < oid.length; i++) {
- if (i > 0)
- sb.append('.');
- sb.append(oid[i]);
- }
- return sb.toString();
- }
-
- private static String findEncryption(int encOid[]) throws NoSuchAlgorithmException {
- if (Arrays.equals(DSA_OID, encOid)) {
- return "DSA"; //$NON-NLS-1$
- }
- if (Arrays.equals(RSA_OID, encOid)) {
- return "RSA"; //$NON-NLS-1$
- }
- throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(encOid)); //$NON-NLS-1$
- }
-
- private static String findDigest(int digestOid[]) throws NoSuchAlgorithmException {
- if (Arrays.equals(SHA1_OID, digestOid)) {
- return SHA1_STR;
- }
- if (Arrays.equals(SHA224_OID, digestOid)) {
- return SHA224_STR;
- }
- if (Arrays.equals(SHA256_OID, digestOid)) {
- return SHA256_STR;
- }
- if (Arrays.equals(SHA384_OID, digestOid)) {
- return SHA384_STR;
- }
- if (Arrays.equals(SHA512_OID, digestOid)) {
- return SHA512_STR;
- }
- if (Arrays.equals(SHA512_224_OID, digestOid)) {
- return SHA512_224_STR;
- }
- if (Arrays.equals(SHA512_256_OID, digestOid)) {
- return SHA512_256_STR;
- }
- if (Arrays.equals(MD5_OID, digestOid)) {
- return MD5_STR;
- }
- if (Arrays.equals(MD2_OID, digestOid)) {
- return MD2_STR;
- }
- throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(digestOid)); //$NON-NLS-1$
- }
-
- public PKCS7Processor(byte pkcs7[], int pkcs7Offset, int pkcs7Length, String signer, String file) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, NoSuchProviderException {
- this.signer = signer;
- this.file = file;
- // First grab the certificates
- List<Certificate> certs = null;
-
- BERProcessor bp = new BERProcessor(pkcs7, pkcs7Offset, pkcs7Length);
-
- // Just do a sanity check and make sure we are actually doing a PKCS7
- // stream
- // PKCS7: Step into the ContentType
- bp = bp.stepInto();
- if (!Arrays.equals(bp.getObjId(), SIGNEDDATA_OID)) {
- throw new SignatureException(NLS.bind(SignedContentMessages.PKCS7_Invalid_File, signer, file));
- }
-
- // PKCS7: Process the SignedData structure
- bp.stepOver(); // (**wrong comments**) skip over the oid
- bp = bp.stepInto(); // go into the Signed data
- bp = bp.stepInto(); // It is a structure;
- bp.stepOver(); // Yeah, yeah version = 1
- bp.stepOver(); // We'll see the digest stuff again; digestAlgorithms
-
- // process the encapContentInfo structure
- processEncapContentInfo(bp);
-
- bp.stepOver();
-
- // PKCS7: check if the class tag is 0
- if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 0) {
- // process the certificate elements inside the signeddata strcuture
- certs = processCertificates(bp);
- }
-
- if (certs == null || certs.size() < 1)
- throw new SignatureException("There are no certificates in the .RSA/.DSA file!"); //$NON-NLS-1$
-
- // Okay, here are our certificates.
- bp.stepOver();
- if (bp.classOfTag == BERProcessor.UNIVERSAL_TAGCLASS && bp.tag == 1) {
- bp.stepOver(); // Don't use the CRLs if present
- }
-
- processSignerInfos(bp, certs);
-
- // construct the cert path
- certs = constructCertPath(certs, signerCert);
-
- // initialize the certificates
- certificates = certs.toArray(new Certificate[certs.size()]);
- verifyCerts();
- // if this pkcs7process is tsa asn.1 block, the signingTime should already be set
- if (signingTime == null)
- signingTime = PKCS7DateParser.parseDate(this, signer, file);
- }
-
- private void processEncapContentInfo(BERProcessor bp) throws SignatureException {
- // check immediately if TSTInfo is there
- BERProcessor encapContentBERS = bp.stepInto();
- if (Arrays.equals(encapContentBERS.getObjId(), TIMESTAMP_TST_OID)) {
-
- // eContent
- encapContentBERS.stepOver();
- BERProcessor encapContentBERS1 = encapContentBERS.stepInto();
-
- // obtain eContent octet structure
- byte bytesman[] = encapContentBERS1.getBytes();
- BERProcessor eContentStructure = new BERProcessor(bytesman, 0, bytesman.length);
-
- // pointing at 'version Integer' now
- BERProcessor eContentBER = eContentStructure.stepInto();
- int tsaVersion = eContentBER.getIntValue().intValue();
-
- if (tsaVersion != 1)
- throw new SignatureException("Not a version 1 time-stamp token"); //$NON-NLS-1$
-
- // policty : TSAPolicyId
- eContentBER.stepOver();
-
- // messageImprint : MessageImprint
- eContentBER.stepOver();
-
- // serialNumber : INTEGER
- eContentBER.stepOver();
-
- // genTime : GeneralizedTime
- eContentBER.stepOver();
-
- // check time ends w/ 'Z'
- String dateString = new String(eContentBER.getBytes(), StandardCharsets.UTF_8);
- if (!dateString.endsWith("Z")) //$NON-NLS-1$
- throw new SignatureException("Wrong dateformat used in time-stamp token"); //$NON-NLS-1$
-
- // create the appropriate date time string format
- // date format could be yyyyMMddHHmmss[.s...]Z or yyyyMMddHHmmssZ
- int dotIndex = dateString.indexOf('.');
- StringBuilder dateFormatSB = new StringBuilder("yyyyMMddHHmmss"); //$NON-NLS-1$
- if (dotIndex != -1) {
- // yyyyMMddHHmmss[.s...]Z, find out number of s in the bracket
- int noS = dateString.indexOf('Z') - 1 - dotIndex;
- dateFormatSB.append('.');
-
- // append s
- for (int i = 0; i < noS; i++) {
- dateFormatSB.append('s');
- }
- }
- dateFormatSB.append("'Z'"); //$NON-NLS-1$
-
- try {
- // if the current locale is th_TH, or ja_JP_JP, then our dateFormat object will end up with
- // a calendar such as Buddhist or Japanese Imperial Calendar, and the signing time will be
- // incorrect ... so always use English as the locale for parsing the time, resulting in a
- // Gregorian calendar
- DateFormat dateFormt = new SimpleDateFormat(dateFormatSB.toString(), Locale.ENGLISH);
- dateFormt.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$
- signingTime = dateFormt.parse(dateString);
- } catch (ParseException e) {
- throw new SignatureException(SignedContentMessages.PKCS7_Parse_Signing_Time, e);
- }
- }
- }
-
- private List<Certificate> constructCertPath(List<Certificate> certs, Certificate targetCert) {
- List<Certificate> certsList = new ArrayList<>();
- certsList.add(targetCert);
-
- X509Certificate currentCert = (X509Certificate) targetCert;
- int numIteration = certs.size();
- int i = 0;
- while (i < numIteration) {
-
- X500Principal subject = currentCert.getSubjectX500Principal();
- X500Principal issuer = currentCert.getIssuerX500Principal();
-
- if (subject.equals(issuer)) {
- // the cert path has been constructed
- break;
- }
-
- currentCert = null;
- Iterator<Certificate> itr = certs.iterator();
-
- while (itr.hasNext()) {
- X509Certificate tempCert = (X509Certificate) itr.next();
-
- if (tempCert.getSubjectX500Principal().equals(issuer)) {
- certsList.add(tempCert);
- currentCert = tempCert;
- }
- }
-
- i++;
- }
-
- return certsList;
- }
-
- public void verifyCerts() throws InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
- if (certificates == null || certificates.length == 0) {
- throw new CertificateException("There are no certificates in the signature block file!"); //$NON-NLS-1$
- }
-
- int len = certificates.length;
-
- // check the certs validity and signatures
- for (int i = 0; i < len; i++) {
- X509Certificate currentX509Cert = (X509Certificate) certificates[i];
- if (i == len - 1) {
- if (currentX509Cert.getSubjectDN().equals(currentX509Cert.getIssuerDN()))
- currentX509Cert.verify(currentX509Cert.getPublicKey());
- } else {
- X509Certificate nextX509Cert = (X509Certificate) certificates[i + 1];
- currentX509Cert.verify(nextX509Cert.getPublicKey());
- }
- }
- }
-
- private Certificate processSignerInfos(BERProcessor bp, List<Certificate> certs) throws CertificateException, NoSuchAlgorithmException, SignatureException {
- // We assume there is only one SingerInfo element
-
- // PKCS7: SignerINFOS processing
- bp = bp.stepInto(); // Step into the set of signerinfos
- bp = bp.stepInto(); // Step into the signerinfo sequence
-
- // make sure the version is 1
- BigInteger signerInfoVersion = bp.getIntValue();
- if (signerInfoVersion.intValue() != 1) {
- throw new CertificateException(SignedContentMessages.PKCS7_SignerInfo_Version_Not_Supported);
- }
-
- // PKCS7: version CMSVersion
- bp.stepOver(); // Skip the version
-
- // PKCS7: sid [SignerIdentifier : issuerAndSerialNumber or subjectKeyIdentifer]
- BERProcessor issuerAndSN = bp.stepInto();
- X500Principal signerIssuer = new X500Principal(new ByteArrayInputStream(issuerAndSN.buffer, issuerAndSN.offset, issuerAndSN.endOffset - issuerAndSN.offset));
- issuerAndSN.stepOver();
- BigInteger sn = issuerAndSN.getIntValue();
-
- // initilize the newSignerCert to the issuer cert of leaf cert
- Certificate newSignerCert = null;
-
- Iterator<Certificate> itr = certs.iterator();
- // PKCS7: compuare the issuers in the issuerAndSN BER equals to the issuers in Certs generated at the beginning of this method
- // it seems like there is no neeed, cause both ways use the same set of bytes
- while (itr.hasNext()) {
- X509Certificate cert = (X509Certificate) itr.next();
- if (cert.getIssuerX500Principal().equals(signerIssuer) && cert.getSerialNumber().equals(sn)) {
- newSignerCert = cert;
- break;
- }
- }
-
- if (newSignerCert == null)
- throw new CertificateException("Signer certificate not in pkcs7block"); //$NON-NLS-1$
-
- // set the signer cert
- signerCert = newSignerCert;
-
- // PKCS7: skip over the sid [SignerIdentifier : issuerAndSerialNumber or subjectKeyIdentifer]
- bp.stepOver(); // skip the issuer name and serial number
-
- // PKCS7: digestAlgorithm DigestAlgorithmIdentifier
- BERProcessor digestAlg = bp.stepInto();
- digestAlgorithm = findDigest(digestAlg.getObjId());
-
- // PKCS7: check if the next one if context class for signedAttrs
- bp.stepOver(); // skip the digest alg
-
- // process the signed attributes if there is any
- processSignedAttributes(bp);
-
- // PKCS7: signatureAlgorithm for this SignerInfo
- BERProcessor encryptionAlg = bp.stepInto();
- signatureAlgorithm = findEncryption(encryptionAlg.getObjId());
- bp.stepOver(); // skip the encryption alg
-
- // PKCS7: signature
- signature = bp.getBytes();
-
- // PKCS7: Step into the unsignedAttrs,
- bp.stepOver();
-
- // process the unsigned attributes if there is any
- processUnsignedAttributes(bp);
-
- return newSignerCert;
- }
-
- private void processUnsignedAttributes(BERProcessor bp) throws SignatureException {
-
- if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 1) {
-
- // there are some unsignedAttrs are found!!
- unsignedAttrs = new HashMap<>();
-
- // step into a set of unsigned attributes, I believe, when steps
- // into here, the 'poiter' is pointing to the first element
- BERProcessor unsignedAttrsBERS = bp.stepInto();
- do {
- // process the unsignedAttrsBER by getting the attr type first,
- // then the strcuture for the type
- BERProcessor unsignedAttrBER = unsignedAttrsBERS.stepInto();
-
- // check if it is timestamp attribute type
- int[] objID = unsignedAttrBER.getObjId();
- // if(Arrays.equals(TIMESTAMP_OID, objID)) {
- // System.out.println("This is a timestamp type, to continue");
- // }
-
- // get the structure for the attribute type
- unsignedAttrBER.stepOver();
- byte[] structure = unsignedAttrBER.getBytes();
- unsignedAttrs.put(objID, structure);
- unsignedAttrsBERS.stepOver();
- } while (!unsignedAttrsBERS.endOfSequence());
- }
- }
-
- private void processSignedAttributes(BERProcessor bp) throws SignatureException {
- if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS) {
-
- // process the signed attributes
- signedAttrs = new HashMap<>();
-
- BERProcessor signedAttrsBERS = bp.stepInto();
- do {
- BERProcessor signedAttrBER = signedAttrsBERS.stepInto();
- int[] signedAttrObjID = signedAttrBER.getObjId();
-
- // step over to the attribute value
- signedAttrBER.stepOver();
-
- byte[] signedAttrStructure = signedAttrBER.getBytes();
-
- signedAttrs.put(signedAttrObjID, signedAttrStructure);
-
- signedAttrsBERS.stepOver();
- } while (!signedAttrsBERS.endOfSequence());
- bp.stepOver();
- }
- }
-
- public Certificate[] getCertificates() {
- return certificates == null ? new Certificate[0] : certificates;
- }
-
- public void verifySFSignature(byte data[], int dataOffset, int dataLength) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
- Signature sig = Signature.getInstance(digestAlgorithm + "with" + signatureAlgorithm); //$NON-NLS-1$
- sig.initVerify(signerCert.getPublicKey());
- sig.update(data, dataOffset, dataLength);
- if (!sig.verify(signature)) {
- throw new SignatureException(NLS.bind(SignedContentMessages.Signature_Not_Verify, signer, file));
- }
- }
-
- /**
- * Return a map of signed attributes, the key(objid) = value(PKCSBlock in bytes for the key)
- *
- * @return map if there is any signed attributes, null otherwise
- */
- public Map<int[], byte[]> getUnsignedAttrs() {
- return unsignedAttrs;
- }
-
- /**
- * Return a map of signed attributes, the key(objid) = value(PKCSBlock in bytes for the key)
- *
- * @return map if there is any signed attributes, null otherwise
- */
- public Map<int[], byte[]> getSignedAttrs() {
- return signedAttrs;
- }
-
- /**
- *
- * @param bp
- * @return a List of certificates from target cert to root cert in order
- *
- * @throws CertificateException
- * @throws SignatureException
- */
- private List<Certificate> processCertificates(BERProcessor bp) throws CertificateException, SignatureException {
- List<Certificate> rtvList = new ArrayList<>(3);
-
- // Step into the first certificate-element
- BERProcessor certsBERS = bp.stepInto();
-
- do {
- X509Certificate x509Cert = (X509Certificate) certFact.generateCertificate(new ByteArrayInputStream(certsBERS.buffer, certsBERS.offset, certsBERS.endOffset - certsBERS.offset));
-
- if (x509Cert != null) {
- rtvList.add(x509Cert);
- }
-
- // go to the next cert element
- certsBERS.stepOver();
- } while (!certsBERS.endOfSequence());
-
- // Collections.reverse(rtvList);
- return rtvList;
- }
-
- public Date getSigningTime() {
- return signingTime;
- }
-
- void setTSACertificates(Certificate[] tsaCertificates) {
- this.tsaCertificates = tsaCertificates;
- }
-
- public Certificate[] getTSACertificates() {
- return (tsaCertificates == null) ? new Certificate[0] : tsaCertificates;
- }
-
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java
deleted file mode 100644
index bef43d0db..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java
+++ /dev/null
@@ -1,514 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2019 IBM Corporation and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which accompanies this distribution,
- * and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors: IBM Corporation - initial API and implementation
- ******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import org.eclipse.osgi.framework.log.FrameworkLogEntry;
-import org.eclipse.osgi.signedcontent.SignerInfo;
-import org.eclipse.osgi.storage.bundlefile.BundleEntry;
-import org.eclipse.osgi.storage.bundlefile.BundleFile;
-import org.eclipse.osgi.util.NLS;
-
-public class SignatureBlockProcessor implements SignedContentConstants {
- private final SignedBundleFile signedBundle;
- private List<SignerInfo> signerInfos = new ArrayList<>();
- private Map<String, Object> contentMDResults = new HashMap<>();
- // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime}
- private Map<SignerInfo, Object[]> tsaSignerInfos;
- private final int supportFlags;
- private final SignedBundleHook signedBundleHook;
-
- public SignatureBlockProcessor(SignedBundleFile signedContent, int supportFlags, SignedBundleHook signedBundleHook) {
- this.signedBundle = signedContent;
- this.supportFlags = supportFlags;
- this.signedBundleHook = signedBundleHook;
- }
-
- public SignedContentImpl process() throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
- BundleFile wrappedBundleFile = signedBundle.getBundleFile();
- BundleEntry be = wrappedBundleFile.getEntry(META_INF_MANIFEST_MF);
- if (be == null)
- return createUnsignedContent();
-
- // read all the signature block file names into a list
- Enumeration<String> en = wrappedBundleFile.getEntryPaths(META_INF);
- List<String> signers = new ArrayList<>(2);
- while (en.hasMoreElements()) {
- String name = en.nextElement();
- if ((name.endsWith(DOT_DSA) || name.endsWith(DOT_RSA)) && name.indexOf('/') == name.lastIndexOf('/'))
- signers.add(name);
- }
-
- // this means the jar is not signed
- if (signers.size() == 0)
- return createUnsignedContent();
-
- byte manifestBytes[] = readIntoArray(be);
- // process the signers
-
- for (Iterator<String> iSigners = signers.iterator(); iSigners.hasNext();)
- processSigner(wrappedBundleFile, manifestBytes, iSigners.next());
-
- // done processing now create a SingedContent to return
- SignerInfo[] allSigners = signerInfos.toArray(new SignerInfo[signerInfos.size()]);
- for (Map.Entry<String, Object> entry : contentMDResults.entrySet()) {
- @SuppressWarnings("unchecked")
- List<Object>[] value = (List<Object>[]) entry.getValue();
- SignerInfo[] entrySigners = value[0].toArray(new SignerInfo[value[0].size()]);
- byte[][] entryResults = value[1].toArray(new byte[value[1].size()][]);
- entry.setValue(new Object[] {entrySigners, entryResults});
- }
- SignedContentImpl result = new SignedContentImpl(allSigners, (supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0 ? contentMDResults : null);
- result.setContent(signedBundle);
- result.setTSASignerInfos(tsaSignerInfos);
- return result;
- }
-
- private SignedContentImpl createUnsignedContent() {
- SignedContentImpl result = new SignedContentImpl(new SignerInfo[0], contentMDResults);
- result.setContent(signedBundle);
- return result;
- }
-
- private void processSigner(BundleFile bf, byte[] manifestBytes, String signer) throws IOException, SignatureException, InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
- BundleEntry be = bf.getEntry(signer);
- byte pkcs7Bytes[] = readIntoArray(be);
- int dotIndex = signer.lastIndexOf('.');
- be = bf.getEntry(signer.substring(0, dotIndex) + DOT_SF);
- byte sfBytes[] = readIntoArray(be);
-
- // Step 1, verify the .SF file is signed by the private key that corresponds to the public key
- // in the .RSA/.DSA file
- String baseFile = String.valueOf(bf.getBaseFile());
- PKCS7Processor processor = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length, signer, baseFile);
- // call the Step 1 in the Jar File Verification algorithm
- processor.verifySFSignature(sfBytes, 0, sfBytes.length);
- // algorithm used
- String digAlg = getDigAlgFromSF(sfBytes);
- if (digAlg == null)
- throw new SignatureException(NLS.bind(SignedContentMessages.SF_File_Parsing_Error, new String[] {bf.toString()}));
- // get the digest results
- // Process the Step 2 in the Jar File Verification algorithm
- // Get the manifest out of the signature file and make sure
- // it matches MANIFEST.MF
- verifyManifestAndSignatureFile(manifestBytes, sfBytes);
-
- // create a SignerInfo with the processed information
- SignerInfoImpl signerInfo = new SignerInfoImpl(processor.getCertificates(), null, digAlg);
- if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0)
- // only populate the manifests digest information for verifying content at runtime
- populateMDResults(manifestBytes, signerInfo);
- signerInfos.add(signerInfo);
- // check for tsa signers
- Certificate[] tsaCerts = processor.getTSACertificates();
- Date signingTime = processor.getSigningTime();
- if (tsaCerts != null && signingTime != null) {
- SignerInfoImpl tsaSignerInfo = new SignerInfoImpl(tsaCerts, null, digAlg);
- if (tsaSignerInfos == null)
- tsaSignerInfos = new HashMap<>(2);
- tsaSignerInfos.put(signerInfo, new Object[] {tsaSignerInfo, signingTime});
- }
- }
-
- /**
- * Verify the digest listed in each entry in the .SF file with corresponding section in the manifest
- * @throws SignatureException
- */
- private void verifyManifestAndSignatureFile(byte[] manifestBytes, byte[] sfBytes) throws SignatureException {
-
- String sf = new String(sfBytes, StandardCharsets.UTF_8);
- sf = stripContinuations(sf);
-
- // check if there -Digest-Manfiest: header in the file
- int off = sf.indexOf(digestManifestSearch);
- if (off != -1) {
- int start = sf.lastIndexOf('\n', off);
- String manifestDigest = null;
- if (start != -1) {
- // Signature-Version has to start the file, so there
- // should always be a newline at the start of
- // Digest-Manifest
- String digestName = sf.substring(start + 1, off);
- if (digestName.equalsIgnoreCase(MD5_STR))
- manifestDigest = calculateDigest(getMessageDigest(MD5_STR), manifestBytes);
- else if (digestName.equalsIgnoreCase(SHA1_STR))
- manifestDigest = calculateDigest(getMessageDigest(SHA1_STR), manifestBytes);
- else
- manifestDigest = calculateDigest(getMessageDigest(digestName), manifestBytes);
- off += digestManifestSearchLen;
-
- // find out the index of first '\n' after the -Digest-Manifest:
- int nIndex = sf.indexOf('\n', off);
- String digestValue = sf.substring(off, nIndex - 1);
-
- // check if the the computed digest value of manifest file equals to the digest value in the .sf file
- if (!digestValue.equals(manifestDigest)) {
- SignatureException se = new SignatureException(NLS.bind(SignedContentMessages.Security_File_Is_Tampered, new String[] {String.valueOf(signedBundle.getBaseFile())}));
- signedBundleHook.log(se.getMessage(), FrameworkLogEntry.ERROR, se);
- throw se;
- }
- }
- }
- }
-
- private void populateMDResults(byte mfBuf[], SignerInfo signerInfo) {
- // need to make a string from the MF file data bytes
- String mfStr = new String(mfBuf, StandardCharsets.UTF_8);
-
- // start parsing each entry in the MF String
- int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
- int length = mfStr.length();
-
- while ((entryStartOffset != -1) && (entryStartOffset < length)) {
-
- // get the start of the next 'entry', i.e. the end of this entry
- int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
- if (entryEndOffset == -1) {
- // if there is no next entry, then the end of the string
- // is the end of this entry
- entryEndOffset = mfStr.length();
- }
-
- // get the string for this entry only, since the entryStartOffset
- // points to the '\n' before the 'Name: ' we increase it by 1
- // this is guaranteed to not go past end-of-string and be less
- // then entryEndOffset.
- String entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
- entryStr = stripContinuations(entryStr);
-
- // increment the offset to the ending entry for the next iteration of the loop ...
- entryStartOffset = entryEndOffset;
-
- // entry points to the start of the next 'entry'
- String entryName = getEntryFileName(entryStr);
-
- // if we could retrieve an entry name, then we will extract
- // digest type list, and the digest value list
- if (entryName != null) {
- String aDigestLine = getDigestLine(entryStr, signerInfo.getMessageDigestAlgorithm());
-
- if (aDigestLine != null) {
- String msgDigestAlgorithm = getDigestAlgorithmFromString(aDigestLine);
- if (!msgDigestAlgorithm.equalsIgnoreCase(signerInfo.getMessageDigestAlgorithm()))
- continue; // TODO log error?
- byte digestResult[] = getDigestResultsList(aDigestLine);
-
- //
- // only insert this entry into the table if its
- // "well-formed",
- // i.e. only if we could extract its name, digest types, and
- // digest-results
- //
- // sanity check, if the 2 lists are non-null, then their
- // counts must match
- //
- // if ((msgDigestObj != null) && (digestResultsList != null)
- // && (1 != digestResultsList.length)) {
- // throw new RuntimeException(
- // "Errors occurs when parsing the manifest file stream!"); //$NON-NLS-1$
- // }
- @SuppressWarnings("unchecked")
- List<Object>[] mdResult = (List<Object>[]) contentMDResults.get(entryName);
- if (mdResult == null) {
- @SuppressWarnings("unchecked")
- List<Object>[] arrayLists = new ArrayList[2];
- mdResult = arrayLists;
- mdResult[0] = new ArrayList<>();
- mdResult[1] = new ArrayList<>();
- contentMDResults.put(entryName, mdResult);
- }
- mdResult[0].add(signerInfo);
- mdResult[1].add(digestResult);
- } // could get lines of digest entries in this MF file entry
- } // could retrieve entry name
- }
- }
-
- private static byte[] getDigestResultsList(String digestLines) {
- byte resultsList[] = null;
- if (digestLines != null) {
- // for each digest-line retrieve the digest result
- // for (int i = 0; i < digestLines.length; i++) {
- String sDigestLine = digestLines;
- int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART);
- indexDigest += MF_DIGEST_PART.length();
- // if there is no data to extract for this digest value
- // then we will fail...
- if (indexDigest >= sDigestLine.length()) {
- resultsList = null;
- // break;
- }
- // now attempt to base64 decode the result
- String sResult = sDigestLine.substring(indexDigest);
- try {
- resultsList = Base64.decode(sResult.getBytes());
- } catch (Throwable t) {
- // malformed digest result, no longer processing this entry
- resultsList = null;
- }
- }
- return resultsList;
- }
-
- private static String getDigestAlgorithmFromString(String digestLines) {
- if (digestLines != null) {
- // String sDigestLine = digestLines[i];
- int indexDigest = digestLines.indexOf(MF_DIGEST_PART);
- String sDigestAlgType = digestLines.substring(0, indexDigest);
- if (sDigestAlgType.equalsIgnoreCase(MD5_STR)) {
- // remember the "algorithm type"
- return MD5_STR;
- } else if (sDigestAlgType.equalsIgnoreCase(SHA1_STR)) {
- // remember the "algorithm type" object
- return SHA1_STR;
- } else {
- return sDigestAlgType;
- }
- }
- return null;
- }
-
- private static String getEntryFileName(String manifestEntry) {
- // get the beginning of the name
- int nameStart = manifestEntry.indexOf(MF_ENTRY_NAME);
- if (nameStart == -1) {
- return null;
- }
- // check where the name ends
- int nameEnd = manifestEntry.indexOf('\n', nameStart);
- if (nameEnd == -1) {
- return null;
- }
- // if there is a '\r' before the '\n', then we'll strip it
- if (manifestEntry.charAt(nameEnd - 1) == '\r') {
- nameEnd--;
- }
- // get to the beginning of the actual name...
- nameStart += MF_ENTRY_NAME.length();
- if (nameStart >= nameEnd) {
- return null;
- }
- return manifestEntry.substring(nameStart, nameEnd);
- }
-
- /**
- * Returns the Base64 encoded digest of the passed set of bytes.
- */
- private static String calculateDigest(MessageDigest digest, byte[] bytes) {
- return new String(Base64.encode(digest.digest(bytes)), StandardCharsets.UTF_8);
- }
-
- synchronized MessageDigest getMessageDigest(String algorithm) {
- try {
- return MessageDigest.getInstance(algorithm);
- } catch (NoSuchAlgorithmException e) {
- signedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
- }
- return null;
- }
-
- /**
- * Read the .SF file abd assuming that same digest algorithm will be used through out the whole
- * .SF file. That digest algorithm name in the last entry will be returned.
- *
- * @param SFBuf a .SF file in bytes
- * @return the digest algorithm name used in the .SF file
- */
- private static String getDigAlgFromSF(byte SFBuf[]) {
- // need to make a string from the MF file data bytes
- String mfStr = new String(SFBuf, StandardCharsets.UTF_8);
- String entryStr = null;
-
- // start parsing each entry in the MF String
- int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
- int length = mfStr.length();
-
- if ((entryStartOffset != -1) && (entryStartOffset < length)) {
-
- // get the start of the next 'entry', i.e. the end of this entry
- int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
- if (entryEndOffset == -1) {
- // if there is no next entry, then the end of the string
- // is the end of this entry
- entryEndOffset = mfStr.length();
- }
-
- // get the string for this entry only, since the entryStartOffset
- // points to the '\n' before the 'Name: ' we increase it by 1
- // this is guaranteed to not go past end-of-string and be less
- // then entryEndOffset.
- entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
- entryStr = stripContinuations(entryStr);
- }
-
- if (entryStr != null) {
- // process the entry to retrieve the digest algorith name
- String digestLine = getDigestLine(entryStr, null);
-
- // throw parsing
- return getMessageDigestName(digestLine);
- }
- return null;
- }
-
- /**
- *
- * @param manifestEntry contains a single MF file entry of the format
- * "Name: foo"
- * "MD5-Digest: [base64 encoded MD5 digest data]"
- * "SHA1-Digest: [base64 encoded SHA1 digest dat]"
- *
- * @param desireDigestAlg a string representing the desire digest value to be returned if there are
- * multiple digest lines.
- * If this value is null, return whatever digest value is in the entry.
- *
- * @return this function returns a digest line based on the desire digest algorithm value
- * (since only MD5 and SHA1 are recognized here),
- * or a 'null' will be returned if none of the digest algorithms
- * were recognized.
- */
- private static String getDigestLine(String manifestEntry, String desireDigestAlg) {
- String result = null;
-
- // find the first digest line
- int indexDigest = manifestEntry.indexOf(MF_DIGEST_PART);
- // if we didn't find any digests at all, then we are done
- if (indexDigest == -1)
- return null;
-
- // while we continue to find digest entries
- // note: in the following loop we bail if any of the lines
- // look malformed...
- while (indexDigest != -1) {
- // see where this digest line begins (look to left)
- int indexStart = manifestEntry.lastIndexOf('\n', indexDigest);
- if (indexStart == -1)
- return null;
- // see where it ends (look to right)
- int indexEnd = manifestEntry.indexOf('\n', indexDigest);
- if (indexEnd == -1)
- return null;
- // strip off ending '\r', if any
- int indexEndToUse = indexEnd;
- if (manifestEntry.charAt(indexEndToUse - 1) == '\r')
- indexEndToUse--;
- // indexStart points to the '\n' before this digest line
- int indexStartToUse = indexStart + 1;
- if (indexStartToUse >= indexEndToUse)
- return null;
-
- // now this may be a valid digest line, parse it a bit more
- // to see if this is a preferred digest algorithm
- String digestLine = manifestEntry.substring(indexStartToUse, indexEndToUse);
- String digAlg = getMessageDigestName(digestLine);
- if (desireDigestAlg != null) {
- if (desireDigestAlg.equalsIgnoreCase(digAlg))
- return digestLine;
- }
-
- // desireDigestAlg is null, always return the digestLine
- result = digestLine;
-
- // iterate to next digest line in this entry
- indexDigest = manifestEntry.indexOf(MF_DIGEST_PART, indexEnd);
- }
-
- // if we couldn't find any digest lines, then we are done
- return result;
- }
-
- /**
- * Return the Message Digest name
- *
- * @param digLine the message digest line is in the following format. That is in the
- * following format:
- * DIGEST_NAME-digest: digest value
- * @return a string representing a message digest.
- */
- private static String getMessageDigestName(String digLine) {
- String rtvValue = null;
- if (digLine != null) {
- int indexDigest = digLine.indexOf(MF_DIGEST_PART);
- if (indexDigest != -1) {
- rtvValue = digLine.substring(0, indexDigest);
- }
- }
- return rtvValue;
- }
-
- private static String stripContinuations(String entry) {
- if (entry.indexOf("\n ") < 0 && entry.indexOf("\r ") < 0) //$NON-NLS-1$//$NON-NLS-2$
- return entry;
- StringBuilder buffer = new StringBuilder(entry);
- removeAll(buffer, "\r\n "); //$NON-NLS-1$
- removeAll(buffer, "\n "); //$NON-NLS-1$
- removeAll(buffer, "\r "); //$NON-NLS-1$
- return buffer.toString();
- }
-
- private static StringBuilder removeAll(StringBuilder buffer, String toRemove) {
- int index = buffer.indexOf(toRemove);
- int length = toRemove.length();
- while (index > 0) {
- buffer.replace(index, index + length, ""); //$NON-NLS-1$
- index = buffer.indexOf(toRemove, index);
- }
- return buffer;
- }
-
- private static byte[] readIntoArray(BundleEntry be) throws IOException {
- int size = (int) be.getSize();
- InputStream is = be.getInputStream();
- try {
- byte b[] = new byte[size];
- int rc = readFully(is, b);
- if (rc != size) {
- throw new IOException("Couldn't read all of " + be.getName() + ": " + rc + " != " + size); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- }
- return b;
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- // do nothing;
- }
- }
- }
-
- private static int readFully(InputStream is, byte b[]) throws IOException {
- int count = b.length;
- int offset = 0;
- int rc;
- while ((rc = is.read(b, offset, count)) > 0) {
- count -= rc;
- offset += rc;
- }
- return offset;
- }
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java
deleted file mode 100644
index 5f3cf84c7..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2006, 2013 IBM Corporation and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SignatureException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.util.Date;
-import org.eclipse.osgi.signedcontent.SignedContent;
-import org.eclipse.osgi.signedcontent.SignedContentEntry;
-import org.eclipse.osgi.signedcontent.SignerInfo;
-import org.eclipse.osgi.storage.bundlefile.BundleEntry;
-import org.eclipse.osgi.storage.bundlefile.BundleFile;
-import org.eclipse.osgi.storage.bundlefile.BundleFileWrapper;
-import org.eclipse.osgi.util.NLS;
-
-/**
- * This class wraps a Repository of classes and resources to check and enforce
- * signatures. It requires full signing of the manifest by all signers. If no
- * signatures are found, the classes and resources are retrieved without checks.
- */
-public class SignedBundleFile extends BundleFileWrapper implements SignedContentConstants, SignedContent {
- SignedContentImpl signedContent;
- private final int supportFlags;
- private final SignedBundleHook signedBundleHook;
-
- SignedBundleFile(BundleFile bundleFile, SignedContentImpl signedContent, int supportFlags, SignedBundleHook signedBundleHook) {
- super(bundleFile);
- this.signedContent = signedContent;
- this.supportFlags = supportFlags;
- this.signedBundleHook = signedBundleHook;
- }
-
- void initializeSignedContent() throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
- if (signedContent == null) {
- SignatureBlockProcessor signatureProcessor = new SignatureBlockProcessor(this, supportFlags, signedBundleHook);
- signedContent = signatureProcessor.process();
- if (signedContent != null)
- signedBundleHook.determineTrust(signedContent, supportFlags);
- }
- }
-
- @Override
- public BundleEntry getEntry(String path) {
- // strip off leading slashes so we can ensure the path matches the one provided in the manifest.
- if (path.length() > 0 && path.charAt(0) == '/')
- path = path.substring(1);
- BundleEntry be = getBundleFile().getEntry(path);
- if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) == 0 || signedContent == null)
- return be;
- if (path.startsWith(META_INF)) {
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash == META_INF.length() - 1) {
- if (path.equals(META_INF_MANIFEST_MF) || path.endsWith(DOT_DSA) || path.endsWith(DOT_RSA) || path.endsWith(DOT_SF) || path.indexOf(SIG_DASH) == META_INF.length())
- return be;
- SignedContentEntry signedEntry = signedContent.getSignedEntry(path);
- if (signedEntry == null)
- // TODO this is to allow 1.4 signed bundles to work, it would be better if we could detect 1.4 signed bundles and only do this for them.
- return be;
- }
- }
- if (be == null) {
- // double check that no signer thinks it should exist
- SignedContentEntry signedEntry = signedContent.getSignedEntry(path);
- if (signedEntry != null)
- throw new SecurityException(NLS.bind(SignedContentMessages.file_is_removed_from_jar, path, String.valueOf(getBaseFile())));
- return null;
- }
- return new SignedBundleEntry(be);
- }
-
- class SignedBundleEntry extends BundleEntry {
- BundleEntry nestedEntry;
-
- SignedBundleEntry(BundleEntry nestedEntry) {
- this.nestedEntry = nestedEntry;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- InputStream in = signedContent.getDigestInputStream(nestedEntry);
- if (in == null)
- throw new SecurityException("Corrupted file: the digest does not exist for the file " + nestedEntry.getName()); //$NON-NLS-1$
- return in;
- }
-
- @Override
- public long getSize() {
- return nestedEntry.getSize();
- }
-
- @Override
- public String getName() {
- return nestedEntry.getName();
- }
-
- @Override
- public long getTime() {
- return nestedEntry.getTime();
- }
-
- @Override
- public URL getLocalURL() {
- return nestedEntry.getLocalURL();
- }
-
- @Override
- public URL getFileURL() {
- return nestedEntry.getFileURL();
- }
-
- }
-
- SignedContentImpl getSignedContent() {
- return signedContent;
- }
-
- @Override
- public SignedContentEntry[] getSignedEntries() {
- return signedContent == null ? null : signedContent.getSignedEntries();
- }
-
- @Override
- public SignedContentEntry getSignedEntry(String name) {
- return signedContent == null ? null : signedContent.getSignedEntry(name);
- }
-
- @Override
- public SignerInfo[] getSignerInfos() {
- return signedContent == null ? null : signedContent.getSignerInfos();
- }
-
- @Override
- public Date getSigningTime(SignerInfo signerInfo) {
- return signedContent == null ? null : signedContent.getSigningTime(signerInfo);
- }
-
- @Override
- public SignerInfo getTSASignerInfo(SignerInfo signerInfo) {
- return signedContent == null ? null : signedContent.getTSASignerInfo(signerInfo);
- }
-
- @Override
- public boolean isSigned() {
- return signedContent == null ? false : signedContent.isSigned();
- }
-
- @Override
- public void checkValidity(SignerInfo signerInfo) throws CertificateExpiredException, CertificateNotYetValidException {
- if (signedContent != null)
- signedContent.checkValidity(signerInfo);
- }
-
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java
index 780605012..4a86f0a76 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java
@@ -18,48 +18,54 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
-import java.security.*;
+import java.security.AccessController;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
-import java.util.*;
-import java.util.zip.ZipFile;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.framework.util.SecureAction;
import org.eclipse.osgi.internal.framework.EquinoxBundle;
+import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
-import org.eclipse.osgi.internal.hookregistry.*;
+import org.eclipse.osgi.internal.hookregistry.ActivatorHookFactory;
+import org.eclipse.osgi.internal.hookregistry.HookConfigurator;
+import org.eclipse.osgi.internal.hookregistry.HookRegistry;
import org.eclipse.osgi.internal.service.security.KeyStoreTrustEngine;
-import org.eclipse.osgi.internal.signedcontent.SignedStorageHook.StorageHookImpl;
+import org.eclipse.osgi.internal.signedcontent.SignedContentFromBundleFile.BaseSignerInfo;
import org.eclipse.osgi.service.security.TrustEngine;
-import org.eclipse.osgi.signedcontent.*;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.eclipse.osgi.signedcontent.SignedContentFactory;
+import org.eclipse.osgi.signedcontent.SignerInfo;
import org.eclipse.osgi.storage.BundleInfo.Generation;
-import org.eclipse.osgi.storage.bundlefile.*;
-import org.eclipse.osgi.util.ManifestElement;
-import org.eclipse.osgi.util.NLS;
-import org.osgi.framework.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* Implements signed bundle hook support for the framework
*/
-public class SignedBundleHook implements ActivatorHookFactory, BundleFileWrapperFactoryHook, HookConfigurator, SignedContentFactory {
+public class SignedBundleHook implements ActivatorHookFactory, HookConfigurator, SignedContentFactory {
static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
- static final int VERIFY_CERTIFICATE = 0x01;
- static final int VERIFY_TRUST = 0x02;
- static final int VERIFY_RUNTIME = 0x04;
- static final int VERIFY_ALL = VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_RUNTIME;
- private final static String SUPPORT_CERTIFICATE = "certificate"; //$NON-NLS-1$
- private final static String SUPPORT_TRUST = "trust"; //$NON-NLS-1$
- private final static String SUPPORT_RUNTIME = "runtime"; //$NON-NLS-1$
- private final static String SUPPORT_ALL = "all"; //$NON-NLS-1$
- private final static String SUPPORT_TRUE = "true"; //$NON-NLS-1$
//TODO: comes from configuration!;
private final static String CACERTS_PATH = System.getProperty("java.home") + File.separatorChar + "lib" + File.separatorChar + "security" + File.separatorChar + "cacerts"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$//$NON-NLS-4$
private final static String CACERTS_TYPE = "JKS"; //$NON-NLS-1$
- private final static String SIGNED_BUNDLE_SUPPORT = "osgi.support.signature.verify"; //$NON-NLS-1$
- private final static String SIGNED_CONTENT_SUPPORT = "osgi.signedcontent.support"; //$NON-NLS-1$
private final static String OSGI_KEYSTORE = "osgi.framework.keystore"; //$NON-NLS-1$
private int supportSignedBundles;
TrustEngineListener trustEngineListener;
@@ -93,7 +99,7 @@ public class SignedBundleHook implements ActivatorHookFactory, BundleFileWrapper
void frameworkStart(BundleContext bc) {
this.context = bc;
- if ((supportSignedBundles & VERIFY_TRUST) != 0)
+ if ((supportSignedBundles & EquinoxConfiguration.SIGNED_CONTENT_VERIFY_TRUST) != 0)
// initialize the trust engine listener only if trust is being established with a trust engine
trustEngineListener = new TrustEngineListener(context, this);
// always register the trust engine
@@ -152,121 +158,28 @@ public class SignedBundleHook implements ActivatorHookFactory, BundleFileWrapper
}
@Override
- public BundleFileWrapper wrapBundleFile(BundleFile bundleFile, Generation generation, boolean base) {
- try {
- if (bundleFile != null) {
- StorageHookImpl hook = generation.getStorageHook(SignedStorageHook.class);
- SignedBundleFile signedBaseFile;
- if (base && hook != null) {
- signedBaseFile = new SignedBundleFile(bundleFile, hook.signedContent, supportSignedBundles, this);
- if (hook.signedContent == null) {
- signedBaseFile.initializeSignedContent();
- SignedContentImpl signedContent = signedBaseFile.getSignedContent();
- hook.signedContent = signedContent != null && signedContent.isSigned() ? signedContent : null;
- }
- } else
- signedBaseFile = new SignedBundleFile(bundleFile, null, supportSignedBundles, this);
- signedBaseFile.initializeSignedContent();
- SignedContentImpl signedContent = signedBaseFile.getSignedContent();
- if (signedContent != null && signedContent.isSigned()) {
- // only use the signed file if there are certs
- signedContent.setContent(signedBaseFile);
- return new BundleFileWrapper(signedBaseFile);
- }
- }
- } catch (IOException | GeneralSecurityException e) {
- log("Bad bundle file: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$
- }
- return null;
- }
-
- @Override
public void addHooks(HookRegistry hookRegistry) {
container = hookRegistry.getContainer();
hookRegistry.addActivatorHookFactory(this);
- String[] supportOptions = ManifestElement.getArrayFromList(hookRegistry.getConfiguration().getConfiguration(SIGNED_CONTENT_SUPPORT, hookRegistry.getConfiguration().getConfiguration(SIGNED_BUNDLE_SUPPORT)), ","); //$NON-NLS-1$
- for (String supportOption : supportOptions) {
- if (SUPPORT_CERTIFICATE.equals(supportOption)) {
- supportSignedBundles |= VERIFY_CERTIFICATE;
- } else if (SUPPORT_TRUST.equals(supportOption)) {
- supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST;
- } else if (SUPPORT_RUNTIME.equals(supportOption)) {
- supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_RUNTIME;
- } else if (SUPPORT_TRUE.equals(supportOption) || SUPPORT_ALL.equals(supportOption)) {
- supportSignedBundles |= VERIFY_ALL;
- }
- }
+ supportSignedBundles = hookRegistry.getConfiguration().supportSignedBundles;
trustEngineNameProp = hookRegistry.getConfiguration().getConfiguration(SignedContentConstants.TRUST_ENGINE);
-
- if ((supportSignedBundles & VERIFY_CERTIFICATE) != 0) {
- hookRegistry.addStorageHookFactory(new SignedStorageHook());
- hookRegistry.addBundleFileWrapperFactoryHook(this);
- }
}
@Override
public SignedContent getSignedContent(File content) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
- if (content == null)
- throw new IllegalArgumentException("null content"); //$NON-NLS-1$
- BundleFile contentBundleFile;
- if (content.isDirectory()) {
- contentBundleFile = new DirBundleFile(content, false);
- } else {
- // Make sure we have a ZipFile first, this will throw an IOException if not valid.
- // Use SecureAction because it gives better errors about the path on exceptions
- ZipFile temp = secureAction.getZipFile(content);
- temp.close();
- contentBundleFile = new ZipBundleFile(content, null, null, container.getConfiguration().getDebug());
- }
- SignedBundleFile result = new SignedBundleFile(contentBundleFile, null, VERIFY_ALL, this);
- try {
- result.initializeSignedContent();
- } catch (InvalidKeyException e) {
- throw new InvalidKeyException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content), e);
- } catch (SignatureException e) {
- throw new SignatureException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content), e);
- } catch (CertificateException e) {
- throw new CertificateException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content), e);
- } catch (NoSuchAlgorithmException e) {
- throw new NoSuchAlgorithmException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content), e);
- } catch (NoSuchProviderException e) {
- throw (NoSuchProviderException) new NoSuchProviderException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e);
- }
- return new SignedContentFile(result.getSignedContent());
+ SignedContentFromBundleFile signedContent = new SignedContentFromBundleFile(content,
+ container.getConfiguration().getDebug());
+ determineTrust(signedContent, EquinoxConfiguration.SIGNED_CONTENT_VERIFY_TRUST);
+ return signedContent;
}
@Override
- public SignedContent getSignedContent(Bundle bundle) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, IllegalArgumentException {
- final Generation generation = (Generation) ((EquinoxBundle) bundle).getModule().getCurrentRevision().getRevisionInfo();
- StorageHookImpl hook = generation.getStorageHook(SignedStorageHook.class);
- SignedContent result = hook != null ? hook.signedContent : null;
- if (result != null)
- return result; // just reuse the signed content the storage hook
- // must create a new signed content using the raw file
- if (System.getSecurityManager() == null)
- return getSignedContent(generation.getBundleFile().getBaseFile());
- try {
- return AccessController.doPrivileged(new PrivilegedExceptionAction<SignedContent>() {
- @Override
- public SignedContent run() throws Exception {
- return getSignedContent(generation.getBundleFile().getBaseFile());
- }
- });
- } catch (PrivilegedActionException e) {
- if (e.getException() instanceof IOException)
- throw (IOException) e.getException();
- if (e.getException() instanceof InvalidKeyException)
- throw (InvalidKeyException) e.getException();
- if (e.getException() instanceof SignatureException)
- throw (SignatureException) e.getException();
- if (e.getException() instanceof CertificateException)
- throw (CertificateException) e.getException();
- if (e.getException() instanceof NoSuchAlgorithmException)
- throw (NoSuchAlgorithmException) e.getException();
- if (e.getException() instanceof NoSuchProviderException)
- throw (NoSuchProviderException) e.getException();
- throw new RuntimeException("Unknown error.", e.getException()); //$NON-NLS-1$
- }
+ public SignedContent getSignedContent(Bundle bundle) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
+ Generation generation = (Generation) ((EquinoxBundle) bundle).getModule().getCurrentRevision()
+ .getRevisionInfo();
+ SignedContentFromBundleFile signedContent = new SignedContentFromBundleFile(generation.getBundleFile());
+ determineTrust(signedContent, EquinoxConfiguration.SIGNED_CONTENT_VERIFY_TRUST);
+ return signedContent;
}
public void log(String msg, int severity, Throwable t) {
@@ -331,7 +244,7 @@ public class SignedBundleHook implements ActivatorHookFactory, BundleFileWrapper
}
- void determineTrust(SignedContentImpl trustedContent, int supportFlags) {
+ void determineTrust(SignedContentFromBundleFile trustedContent, int supportFlags) {
TrustEngine[] engines = null;
SignerInfo[] signers = trustedContent.getSignerInfos();
for (SignerInfo signer : signers) {
@@ -342,19 +255,19 @@ public class SignedBundleHook implements ActivatorHookFactory, BundleFileWrapper
engines = getTrustEngines();
// check trust of singer certs
Certificate[] signerCerts = signer.getCertificateChain();
- ((SignerInfoImpl) signer).setTrustAnchor(findTrustAnchor(signerCerts, engines, supportFlags));
+ ((BaseSignerInfo) signer).setTrustAnchor(findTrustAnchor(signerCerts, engines, supportFlags));
// if signer has a tsa check trust of tsa certs
SignerInfo tsaSignerInfo = trustedContent.getTSASignerInfo(signer);
if (tsaSignerInfo != null) {
Certificate[] tsaCerts = tsaSignerInfo.getCertificateChain();
- ((SignerInfoImpl) tsaSignerInfo).setTrustAnchor(findTrustAnchor(tsaCerts, engines, supportFlags));
+ ((BaseSignerInfo) tsaSignerInfo).setTrustAnchor(findTrustAnchor(tsaCerts, engines, supportFlags));
}
}
}
}
private Certificate findTrustAnchor(Certificate[] certs, TrustEngine[] engines, int supportFlags) {
- if ((supportFlags & SignedBundleHook.VERIFY_TRUST) == 0)
+ if ((supportFlags & EquinoxConfiguration.SIGNED_CONTENT_VERIFY_TRUST) == 0)
// we are not searching the engines; in this case we just assume the root cert is trusted
return certs != null && certs.length > 0 ? certs[certs.length - 1] : null;
for (TrustEngine engine : engines) {
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFile.java
deleted file mode 100644
index 9d3bcb3d9..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFile.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2008, 2016 IBM Corporation and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
- *******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.io.IOException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import org.eclipse.osgi.signedcontent.InvalidContentException;
-import org.eclipse.osgi.signedcontent.SignedContent;
-import org.eclipse.osgi.signedcontent.SignedContentEntry;
-import org.eclipse.osgi.signedcontent.SignerInfo;
-
-/*
- * This class is used by the SignedContentFactory to create SignedContent objects from File objects. This is needed
- * to avoid leaving the underlying ZipFiles open for the SignedContent objects returned from the
- * SignedContentFactory (bug 225090)
- */
-public class SignedContentFile implements SignedContent {
-
- private final SignedContentImpl signedContent;
- // a cache of verification exceptions
- private Map<String, Throwable> entryExceptions = null;
-
- public SignedContentFile(SignedContentImpl signedContent) {
- try {
- signedContent.content.close();
- } catch (IOException e) {
- // do nothing
- }
- this.signedContent = signedContent;
- }
-
- @Override
- public void checkValidity(SignerInfo signerInfo) throws CertificateExpiredException, CertificateNotYetValidException {
- signedContent.checkValidity(signerInfo);
- }
-
- @Override
- public synchronized SignedContentEntry[] getSignedEntries() {
- SignedContentEntry[] entries = signedContent.getSignedEntries();
- SignedContentEntry[] results = new SignedContentEntry[entries.length];
- Map<String, Throwable> exceptions = getEntryExceptions(true);
- for (int i = 0; i < entries.length; i++) {
- try {
- entries[i].verify();
- } catch (Throwable t) {
- exceptions.put(entries[i].getName(), t);
- }
- results[i] = new SignedContentFileEntry(entries[i]);
- }
- try {
- // ensure the content is closed after caching the exceptions
- signedContent.content.close();
- } catch (IOException e) {
- // do nothing
- }
- return results;
- }
-
- @Override
- public synchronized SignedContentEntry getSignedEntry(String name) {
- if (getEntryExceptions(false) == null)
- getSignedEntries(); // populate the entry exceptions
- SignedContentEntry entry = signedContent.getSignedEntry(name);
- return entry == null ? null : new SignedContentFileEntry(entry);
- }
-
- @Override
- public SignerInfo[] getSignerInfos() {
- return signedContent.getSignerInfos();
- }
-
- @Override
- public Date getSigningTime(SignerInfo signerInfo) {
- return signedContent.getSigningTime(signerInfo);
- }
-
- @Override
- public SignerInfo getTSASignerInfo(SignerInfo signerInfo) {
- return signedContent.getTSASignerInfo(signerInfo);
- }
-
- @Override
- public boolean isSigned() {
- return signedContent.isSigned();
- }
-
- synchronized Map<String, Throwable> getEntryExceptions(boolean create) {
- if (create && entryExceptions == null)
- entryExceptions = new HashMap<>(5);
- return entryExceptions;
- }
-
- public class SignedContentFileEntry implements SignedContentEntry {
- private final SignedContentEntry entry;
-
- public SignedContentFileEntry(SignedContentEntry entry) {
- this.entry = entry;
- }
-
- @Override
- public String getName() {
- return entry.getName();
- }
-
- @Override
- public SignerInfo[] getSignerInfos() {
- return entry.getSignerInfos();
- }
-
- @Override
- public boolean isSigned() {
- return entry.isSigned();
- }
-
- @Override
- public void verify() throws IOException, InvalidContentException {
- // check the entry exceptions map for the entry name
- Map<String, Throwable> exceptions = getEntryExceptions(false);
- Throwable t = exceptions == null ? null : (Throwable) exceptions.get(entry.getName());
- if (t == null)
- return;
- if (t instanceof IOException)
- throw (IOException) t;
- if (t instanceof InvalidContentException)
- throw (InvalidContentException) t;
- if (t instanceof Error)
- throw (Error) t;
- if (t instanceof RuntimeException)
- throw (RuntimeException) t;
- }
-
- }
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java
new file mode 100644
index 000000000..b0bbf338f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java
@@ -0,0 +1,315 @@
+/*******************************************************************************
+ * Copyright (c) 2021 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.security.CodeSigner;
+import java.security.Timestamp;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipFile;
+import org.eclipse.osgi.internal.debug.Debug;
+import org.eclipse.osgi.signedcontent.InvalidContentException;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.eclipse.osgi.signedcontent.SignedContentEntry;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+import org.eclipse.osgi.storage.bundlefile.BundleFile;
+import org.eclipse.osgi.storage.bundlefile.DirBundleFile;
+import org.eclipse.osgi.storage.bundlefile.ZipBundleFile;
+
+public class SignedContentFromBundleFile implements SignedContent {
+ static abstract class BaseSignerInfo implements SignerInfo {
+ private volatile Certificate trustAnchor = null;
+ @Override
+ public Certificate getTrustAnchor() {
+ return trustAnchor;
+ }
+
+ @Override
+ public boolean isTrusted() {
+ return trustAnchor != null;
+ }
+
+ @Deprecated
+ @Override
+ public String getMessageDigestAlgorithm() {
+ return "unknown"; //$NON-NLS-1$
+ }
+
+ void setTrustAnchor(Certificate anchor) {
+ this.trustAnchor = anchor;
+ }
+ }
+
+ static class TimestampSignerInfo extends BaseSignerInfo {
+ private final Timestamp timestamp;
+
+ public TimestampSignerInfo(Timestamp timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ @Override
+ public Certificate[] getCertificateChain() {
+ return timestamp.getSignerCertPath().getCertificates().toArray(new Certificate[0]);
+ }
+
+ Date getTimestamp() {
+ return timestamp.getTimestamp();
+ }
+ }
+
+ static class CodeSignerInfo extends BaseSignerInfo {
+ private final CodeSigner codeSigner;
+ private final TimestampSignerInfo timestamp;
+
+ public CodeSignerInfo(CodeSigner codeSigner) {
+ this.codeSigner = codeSigner;
+ Timestamp ts = codeSigner.getTimestamp();
+ this.timestamp = ts == null ? null : new TimestampSignerInfo(ts);
+ }
+
+ @Override
+ public Certificate[] getCertificateChain() {
+ return codeSigner.getSignerCertPath().getCertificates().toArray(new Certificate[0]);
+ }
+
+ TimestampSignerInfo getTSASignerInfo() {
+ return timestamp;
+ }
+ }
+
+ static class CodeSignerEntry implements SignedContentEntry {
+ private final String name;
+ private final List<CodeSignerInfo> signerInfos;
+
+ public CodeSignerEntry(List<CodeSignerInfo> signerInfos, String name) {
+ this.name = name;
+ this.signerInfos = signerInfos;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public SignerInfo[] getSignerInfos() {
+ return signerInfos.toArray(new SignerInfo[0]);
+ }
+
+ @Override
+ public boolean isSigned() {
+ return !signerInfos.isEmpty();
+ }
+
+ @Override
+ public void verify() throws IOException, InvalidContentException {
+ // already verified
+ }
+ }
+
+ static class CorruptEntry implements SignedContentEntry {
+ final InvalidContentException verifyError;
+ final String name;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public SignerInfo[] getSignerInfos() {
+ return new SignerInfo[0];
+ }
+
+ @Override
+ public boolean isSigned() {
+ return false;
+ }
+
+ @Override
+ public void verify() throws IOException, InvalidContentException {
+ throw verifyError;
+ }
+
+ public CorruptEntry(InvalidContentException verifyError, String name) {
+ super();
+ this.verifyError = verifyError;
+ this.name = name;
+ }
+
+ }
+
+ private final List<CodeSignerInfo> signerInfos = new ArrayList<>();
+ private final Map<String, SignedContentEntry> signedEntries;
+
+ public SignedContentFromBundleFile(BundleFile bundleFile) throws IOException {
+ signedEntries = getSignedEntries(() -> {
+ try {
+ return getJarInputStream(bundleFile);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, () -> bundleFile, signerInfos);
+ }
+
+ public SignedContentFromBundleFile(File bundleFile, Debug debug) throws IOException {
+ DirBundleFile tmpDirBundleFile = null;
+ if (bundleFile.isDirectory()) {
+ try {
+ tmpDirBundleFile = new DirBundleFile(bundleFile, false);
+ } catch (IOException e) {
+ // ignore and move on
+ }
+ }
+ DirBundleFile dirBundleFile = tmpDirBundleFile;
+ signedEntries = getSignedEntries(() -> {
+ try {
+ if (dirBundleFile != null) {
+ return getJarInputStream(dirBundleFile);
+ }
+ return new FileInputStream(bundleFile);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, () -> {
+ try {
+ if (dirBundleFile != null) {
+ return dirBundleFile;
+ }
+ // Make sure we have a ZipFile first, this will throw an IOException if not
+ // valid.
+ // Use SecureAction because it gives better errors about the path on exceptions
+ ZipFile temp = SignedBundleHook.secureAction.getZipFile(bundleFile, false);
+ temp.close();
+ return new ZipBundleFile(bundleFile, null, null, debug, false);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, signerInfos);
+ }
+
+ private static Map<String, SignedContentEntry> getSignedEntries(Supplier<InputStream> input,
+ Supplier<BundleFile> bundleFile, List<CodeSignerInfo> signerInfos) throws IOException {
+ Map<CodeSigner, CodeSignerInfo> codeSigners = new HashMap<>();
+ Map<String, SignedContentEntry> signedEntries = new LinkedHashMap<>();
+ try (JarInputStream jarInput = new JarInputStream(input.get())) {
+
+ for (JarEntry entry = jarInput.getNextJarEntry(); entry != null; entry = jarInput.getNextJarEntry()) {
+ // drain the entry so we can get the code signer
+ try {
+ for (byte[] drain = new byte[4096]; jarInput.read(drain, 0, drain.length) != -1;) {
+ // nothing
+ }
+ CodeSigner[] signers = entry.getCodeSigners();
+ if (signers != null) {
+ List<CodeSignerInfo> entryInfos = new ArrayList<>(signers.length);
+ for (CodeSigner codeSigner : signers) {
+ CodeSignerInfo info = codeSigners.computeIfAbsent(codeSigner, CodeSignerInfo::new);
+ entryInfos.add(info);
+ }
+ CodeSignerEntry signedEntry = new CodeSignerEntry(entryInfos, entry.getName());
+ signedEntries.put(entry.getName(), signedEntry);
+ }
+ } catch (SecurityException | IOException e) {
+ // assume corruption
+ signedEntries.put(entry.getName(),
+ new CorruptEntry(new InvalidContentException(entry.getName(), e), entry.getName()));
+ }
+ }
+ } catch (SecurityException e) {
+ Enumeration<String> paths = bundleFile.get().getEntryPaths("", true); //$NON-NLS-1$
+ while (paths.hasMoreElements()) {
+ String path = paths.nextElement();
+ if (!path.endsWith("/") && !signedEntries.containsKey(path)) { //$NON-NLS-1$
+ signedEntries.put(path, new CorruptEntry(new InvalidContentException(path, e), path));
+ }
+ }
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
+ }
+ signerInfos.addAll(codeSigners.values());
+ return signedEntries;
+ }
+
+ private static InputStream getJarInputStream(BundleFile bundleFile) throws IOException {
+ File f = bundleFile.getBaseFile();
+ if (f == null || f.isDirectory()) {
+ return new BundleToJarInputStream(bundleFile);
+ }
+ return new FileInputStream(f);
+ }
+
+ @Override
+ public SignedContentEntry[] getSignedEntries() {
+ return signedEntries.values().toArray(new SignedContentEntry[0]);
+ }
+
+ @Override
+ public SignedContentEntry getSignedEntry(String name) {
+ return signedEntries.get(name);
+ }
+
+ @Override
+ public SignerInfo[] getSignerInfos() {
+ return signerInfos.toArray(new SignerInfo[0]);
+ }
+
+ @Override
+ public boolean isSigned() {
+ return !signerInfos.isEmpty();
+ }
+
+ @Override
+ public Date getSigningTime(SignerInfo signerInfo) {
+ if (signerInfo instanceof CodeSignerInfo) {
+ TimestampSignerInfo tsInfo = ((CodeSignerInfo) signerInfo).getTSASignerInfo();
+ if (tsInfo != null) {
+ return tsInfo.getTimestamp();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public SignerInfo getTSASignerInfo(SignerInfo signerInfo) {
+ if (signerInfo instanceof CodeSignerInfo) {
+ return ((CodeSignerInfo) signerInfo).getTSASignerInfo();
+ }
+ return null;
+ }
+
+ @Override
+ public void checkValidity(SignerInfo signerInfo)
+ throws CertificateExpiredException, CertificateNotYetValidException {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java
deleted file mode 100644
index f8acc8de8..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2019 IBM Corporation and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which accompanies this distribution,
- * and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors: IBM Corporation - initial API and implementation
- ******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.eclipse.osgi.signedcontent.InvalidContentException;
-import org.eclipse.osgi.signedcontent.SignedContent;
-import org.eclipse.osgi.signedcontent.SignedContentEntry;
-import org.eclipse.osgi.signedcontent.SignerInfo;
-import org.eclipse.osgi.storage.bundlefile.BundleEntry;
-import org.eclipse.osgi.storage.bundlefile.BundleFile;
-import org.eclipse.osgi.util.NLS;
-
-public class SignedContentImpl implements SignedContent {
- final static int VERIFY_LIMIT = 1000 * 1024; // 1 mb; not sure what the best limit is
- final static SignerInfo[] EMPTY_SIGNERINFO = new SignerInfo[0];
- // the content which is signed
- volatile SignedBundleFile content; // TODO can this be more general?
- // the content entry md results used for entry content verification
- // keyed by entry path -> {SignerInfo[] infos, byte[][] results)}
- private final Map<String, Object> contentMDResults;
- private final SignerInfo[] signerInfos;
- // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime}
- private Map<SignerInfo, Object[]> tsaSignerInfos;
- volatile private boolean checkedValid = false;
-
- public SignedContentImpl(SignerInfo[] signerInfos, Map<String, Object> contentMDResults) {
- this.signerInfos = signerInfos == null ? EMPTY_SIGNERINFO : signerInfos;
- this.contentMDResults = contentMDResults;
- }
-
- @Override
- public SignedContentEntry[] getSignedEntries() {
- if (contentMDResults == null)
- return new SignedContentEntry[0];
- List<SignedContentEntry> results = new ArrayList<>(contentMDResults.size());
- for (Map.Entry<String, Object> entry : contentMDResults.entrySet()) {
- String entryName = entry.getKey();
- Object[] mdResult = (Object[]) entry.getValue();
- results.add(new SignedContentEntryImpl(entryName, (SignerInfo[]) mdResult[0]));
- }
- return results.toArray(new SignedContentEntry[results.size()]);
- }
-
- @Override
- public SignedContentEntry getSignedEntry(String name) {
- if (contentMDResults == null)
- return null;
- Object[] mdResult = (Object[]) contentMDResults.get(name);
- return mdResult == null ? null : new SignedContentEntryImpl(name, (SignerInfo[]) mdResult[0]);
- }
-
- @Override
- public SignerInfo[] getSignerInfos() {
- return signerInfos;
- }
-
- @Override
- public Date getSigningTime(SignerInfo signerInfo) {
- if (tsaSignerInfos == null)
- return null;
- Object[] tsaInfo = tsaSignerInfos.get(signerInfo);
- return tsaInfo == null ? null : (Date) tsaInfo[1];
- }
-
- @Override
- public SignerInfo getTSASignerInfo(SignerInfo signerInfo) {
- if (tsaSignerInfos == null)
- return null;
- Object[] tsaInfo = tsaSignerInfos.get(signerInfo);
- return tsaInfo == null ? null : (SignerInfo) tsaInfo[0];
- }
-
- @Override
- public boolean isSigned() {
- return signerInfos.length > 0;
- }
-
- @Override
- public void checkValidity(SignerInfo signer) throws CertificateExpiredException, CertificateNotYetValidException {
- Date signingTime = getSigningTime(signer);
- if (checkedValid)
- return;
- Certificate[] certs = signer.getCertificateChain();
- for (Certificate cert : certs) {
- if (!(cert instanceof X509Certificate)) {
- continue;
- }
- if (signingTime == null) {
- ((X509Certificate) cert).checkValidity();
- } else {
- ((X509Certificate) cert).checkValidity(signingTime);
- }
- }
- checkedValid = true;
- }
-
- void setContent(SignedBundleFile content) {
- this.content = content;
- }
-
- void setTSASignerInfos(Map<SignerInfo, Object[]> tsaSignerInfos) {
- this.tsaSignerInfos = tsaSignerInfos;
- }
-
- void addTSASignerInfo(SignerInfo baseInfo, SignerInfo tsaSignerInfo, Date signingTime) {
- // sanity check to make sure the baseInfo is here
- if (!containsInfo(baseInfo))
- throw new IllegalArgumentException("The baseInfo is not found"); //$NON-NLS-1$
- if (tsaSignerInfos == null)
- tsaSignerInfos = new HashMap<>(signerInfos.length);
- tsaSignerInfos.put(baseInfo, new Object[] {tsaSignerInfo, signingTime});
- }
-
- Map<String, Object> getContentMDResults() {
- return contentMDResults;
- }
-
- private boolean containsInfo(SignerInfo signerInfo) {
- for (SignerInfo si : signerInfos) {
- if (signerInfo == si) {
- return true;
- }
- }
- return false;
- }
-
- InputStream getDigestInputStream(BundleEntry nestedEntry) throws IOException {
- if (contentMDResults == null)
- return nestedEntry.getInputStream();
- Object[] mdResult = (Object[]) contentMDResults.get(nestedEntry.getName());
- if (mdResult == null)
- return null;
- try {
- return new DigestedInputStream(nestedEntry, content, (SignerInfo[]) mdResult[0], (byte[][]) mdResult[1], nestedEntry.getSize());
- } catch (NoSuchAlgorithmException e) {
- throw new IOException(e);
- }
- }
-
- public class SignedContentEntryImpl implements SignedContentEntry {
- private final String entryName;
- private final SignerInfo[] entrySigners;
-
- public SignedContentEntryImpl(String entryName, SignerInfo[] entrySigners) {
- this.entryName = entryName;
- this.entrySigners = entrySigners == null ? EMPTY_SIGNERINFO : entrySigners;
- }
-
- @Override
- public String getName() {
- return entryName;
- }
-
- @Override
- public SignerInfo[] getSignerInfos() {
- return entrySigners;
- }
-
- @Override
- public boolean isSigned() {
- return entrySigners.length > 0;
- }
-
- @Override
- public void verify() throws IOException, InvalidContentException {
- BundleFile currentContent = content;
- if (currentContent == null)
- throw new InvalidContentException("The content was not set", null); //$NON-NLS-1$
- BundleEntry entry = null;
- SecurityException exception = null;
- try {
- entry = currentContent.getEntry(entryName);
- } catch (SecurityException e) {
- exception = e;
- }
- if (entry == null)
- throw new InvalidContentException(NLS.bind(SignedContentMessages.file_is_removed_from_jar, entryName, String.valueOf(currentContent.getBaseFile())), exception);
-
- if (entry.getSize() > VERIFY_LIMIT) {
- try (InputStream in = entry.getInputStream()) {
- final byte[] buf = new byte[1024];
- while (in.read(buf) > 0) {
- // just exhausting the stream to verify
- }
- }
- } else {
- entry.getBytes();
- }
- }
- }
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java
index f128b2755..3ed871fb0 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java
@@ -18,25 +18,6 @@ import org.eclipse.osgi.util.NLS;
public class SignedContentMessages extends NLS {
- // Jar file is tampered
- public static String file_is_removed_from_jar;
- public static String File_In_Jar_Is_Tampered;
- public static String Security_File_Is_Tampered;
- public static String Signature_Not_Verify;
-
- // Jar file parsing
- public static String SF_File_Parsing_Error;
-
- // PKCS7 parsing errors
- public static String PKCS7_SignerInfo_Version_Not_Supported;
- public static String PKCS7_Invalid_File;
- public static String PKCS7_Parse_Signing_Time;
-
- // Security Exceptions
- public static String Algorithm_Not_Supported;
-
- public static String Factory_SignedContent_Error;
-
public static String Default_Trust_Keystore_Load_Failed;
public static String Default_Trust_Read_Only;
public static String Default_Trust_Cert_Not_Found;
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties
index 0dd233bd0..04b32a18a 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties
@@ -12,26 +12,6 @@
# IBM Corporation - initial API and implementation
###############################################################################
-# Jar file tampered error messages
-file_is_removed_from_jar = A file \"{0}\" has been removed from the jar: {1}
-File_In_Jar_Is_Tampered = The file \"{0}\" in the jar \"{1}\" has been tampered!
-Security_File_Is_Tampered = Either the manifest file or the signature file has been tampered in this jar: {0}
-Signature_Not_Verify = The signature cannot be verified for the signer \"{0}\" in this jar: {1}
-
-# Jar file parsing
-SF_File_Parsing_Error = Error occurs parsing the .SF file to find out the digest algorithm in this bundle: {0}
-
-# PKCS7 parsing errors
-PKCS7_SignerInfo_Version_Not_Supported = Only PKCS7 SignerInfos with a version of 1 are supported.
-PKCS7_Invalid_File = The file \"{0}\" is not a valid PKCS7 file in the jar: {1}
-PKCS7_Parse_Signing_Time = The time stamp in the pkcs7 file cannot be parsed properly!
-
-# Security Exceptions
-Algorithm_Not_Supported = {0} digest algorithm is not supported!
-
-# SignedContentFactory exception
-Factory_SignedContent_Error = An error occurred while processing the signatures for the file: {0}
-
# Default Trust Engine
Default_Trust_Keystore_Load_Failed = Failed to load the keystore from: {0}
Default_Trust_Read_Only=This trust engine is read only.
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java
deleted file mode 100644
index 6c112f7b0..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2006, 2016 IBM Corporation and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which accompanies this distribution,
- * and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors: IBM Corporation - initial API and implementation
- ******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.io.*;
-import java.security.cert.*;
-import java.util.*;
-import org.eclipse.osgi.internal.hookregistry.StorageHookFactory;
-import org.eclipse.osgi.signedcontent.SignedContent;
-import org.eclipse.osgi.signedcontent.SignerInfo;
-import org.eclipse.osgi.storage.BundleInfo.Generation;
-import org.osgi.framework.BundleException;
-
-public class SignedStorageHook extends StorageHookFactory<List<SignerInfo>, List<SignerInfo>, SignedStorageHook.StorageHookImpl> {
- private static final int STORAGE_VERSION = 4;
-
- @Override
- public int getStorageVersion() {
- return STORAGE_VERSION;
- }
-
- @Override
- public List<SignerInfo> createSaveContext() {
- return new ArrayList<>();
- }
-
- @Override
- public List<SignerInfo> createLoadContext(int version) {
- return new ArrayList<>();
- }
-
- @Override
- protected StorageHookImpl createStorageHook(Generation generation) {
- return new StorageHookImpl(generation);
- }
-
- static class StorageHookImpl extends StorageHookFactory.StorageHook<List<SignerInfo>, List<SignerInfo>> {
- SignedContentImpl signedContent;
-
- public StorageHookImpl(Generation generation) {
- super(generation, SignedStorageHook.class);
- }
-
- @Override
- public void initialize(Dictionary<String, String> manifest) throws BundleException {
- // do nothing
- }
-
- @Override
- public void load(List<SignerInfo> loadContext, DataInputStream is) throws IOException {
- boolean signed = is.readBoolean();
- if (!signed)
- return;
- int numSigners = is.readInt();
- SignerInfo[] signerInfos = new SignerInfo[numSigners];
- for (int i = 0; i < numSigners; i++)
- signerInfos[i] = readSignerInfo(is, loadContext);
-
- int resultsSize = is.readInt();
- Map<String, Object> contentMDResults = null;
- if (resultsSize > 0) {
- contentMDResults = new HashMap<>(resultsSize);
- for (int i = 0; i < resultsSize; i++) {
- String path = is.readUTF();
- int numEntrySigners = is.readInt();
- SignerInfo[] entrySigners = new SignerInfo[numEntrySigners];
- byte[][] entryResults = new byte[numEntrySigners][];
- for (int j = 0; j < numEntrySigners; j++) {
- entrySigners[j] = readSignerInfo(is, loadContext);
- int resultSize = is.readInt();
- entryResults[j] = new byte[resultSize];
- is.readFully(entryResults[j]);
- }
- contentMDResults.put(path, new Object[] {entrySigners, entryResults});
- }
- }
- SignedContentImpl result = new SignedContentImpl(signerInfos, contentMDResults);
- for (int i = 0; i < numSigners; i++) {
- boolean hasTSA = is.readBoolean();
- if (!hasTSA)
- continue;
- SignerInfo tsaSigner = readSignerInfo(is, loadContext);
- Date signingDate = new Date(is.readLong());
- result.addTSASignerInfo(signerInfos[i], tsaSigner, signingDate);
- }
- signedContent = result;
- }
-
- private SignerInfo readSignerInfo(DataInputStream is, List<SignerInfo> loadContext) throws IOException {
- int index = is.readInt();
- if (index >= 0)
- return loadContext.get(index);
- int numCerts = is.readInt();
- Certificate[] certs = new Certificate[numCerts];
- for (int i = 0; i < numCerts; i++) {
- int certSize = is.readInt();
- byte[] certBytes = new byte[certSize];
- is.readFully(certBytes);
- try {
- certs[i] = PKCS7Processor.certFact.generateCertificate(new ByteArrayInputStream(certBytes));
- } catch (CertificateException e) {
- throw new IOException(e.getMessage(), e);
- }
- }
- int anchorIdx = is.readInt();
- SignerInfoImpl result = new SignerInfoImpl(certs, anchorIdx >= 0 ? certs[anchorIdx] : null, is.readUTF());
- loadContext.add(result);
- return result;
- }
-
- @Override
- public void save(List<SignerInfo> saveContext, DataOutputStream os) throws IOException {
- os.writeBoolean(signedContent != null);
- if (signedContent == null)
- return;
- SignerInfo[] signerInfos = signedContent.getSignerInfos();
- os.writeInt(signerInfos.length);
- for (SignerInfo signerInfo : signerInfos) {
- saveSignerInfo(signerInfo, os, saveContext);
- }
-
- // keyed by entry path -> {SignerInfo[] infos, byte[][] results)}
- Map<String, Object> contentMDResults = signedContent.getContentMDResults();
- os.writeInt(contentMDResults == null ? -1 : contentMDResults.size());
- if (contentMDResults != null)
- for (Map.Entry<String, Object> entry : contentMDResults.entrySet()) {
- String path = entry.getKey();
- os.writeUTF(path);
- Object[] signerResults = (Object[]) entry.getValue();
- SignerInfo[] entrySigners = (SignerInfo[]) signerResults[0];
- byte[][] entryResults = (byte[][]) signerResults[1];
- os.writeInt(entrySigners.length);
- for (int i = 0; i < entrySigners.length; i++) {
- saveSignerInfo(entrySigners[i], os, saveContext);
- os.writeInt(entryResults[i].length);
- os.write(entryResults[i]);
- }
- }
-
- for (SignerInfo signerInfo : signerInfos) {
- SignerInfo tsaInfo = signedContent.getTSASignerInfo(signerInfo);
- os.writeBoolean(tsaInfo != null);
- if (tsaInfo == null)
- continue;
- saveSignerInfo(tsaInfo, os, saveContext);
- Date signingTime = signedContent.getSigningTime(signerInfo);
- os.writeLong(signingTime != null ? signingTime.getTime() : Long.MIN_VALUE);
- }
- }
-
- private void saveSignerInfo(SignerInfo signerInfo, DataOutputStream os, List<SignerInfo> saveContext) throws IOException {
- int cacheIdx = saveContext.indexOf(signerInfo);
- os.writeInt(cacheIdx);
- if (cacheIdx >= 0)
- return;
- Certificate[] certs = signerInfo.getCertificateChain();
- int anchorIndex = -1;
- os.writeInt(certs == null ? 0 : certs.length);
- if (certs != null)
- for (int i = 0; i < certs.length; i++) {
- if (certs[i].equals(signerInfo.getTrustAnchor()))
- anchorIndex = i;
- byte[] certBytes;
- try {
- certBytes = certs[i].getEncoded();
- } catch (CertificateEncodingException e) {
- throw new IOException(e.getMessage(), e);
- }
- os.writeInt(certBytes.length);
- os.write(certBytes);
- }
- os.writeInt(anchorIndex);
- os.writeUTF(signerInfo.getMessageDigestAlgorithm());
- saveContext.add(signerInfo);
- }
-
- public SignedContent getSignedContent() {
- return signedContent;
- }
- }
-
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java
deleted file mode 100644
index fa4162434..000000000
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2012 IBM Corporation and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which accompanies this distribution,
- * and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors: IBM Corporation - initial API and implementation
- ******************************************************************************/
-package org.eclipse.osgi.internal.signedcontent;
-
-import java.security.cert.Certificate;
-import org.eclipse.osgi.signedcontent.SignerInfo;
-
-public class SignerInfoImpl implements SignerInfo {
- private final Certificate[] chain;
- private final String mdAlgorithm;
- volatile private Certificate trustAnchor;
-
- public SignerInfoImpl(Certificate[] chain, Certificate trustAnchor, String mdAlgorithm) {
- this.chain = chain;
- this.trustAnchor = trustAnchor;
- this.mdAlgorithm = mdAlgorithm;
- }
-
- @Override
- public Certificate[] getCertificateChain() {
- return chain;
- }
-
- @Override
- public Certificate getTrustAnchor() {
- return trustAnchor;
- }
-
- @Override
- public boolean isTrusted() {
- return trustAnchor != null;
- }
-
- void setTrustAnchor(Certificate trustAnchor) {
- this.trustAnchor = trustAnchor;
- }
-
- @Override
- public String getMessageDigestAlgorithm() {
- return mdAlgorithm;
- }
-
- @Override
- public int hashCode() {
- int result = mdAlgorithm.hashCode();
- for (Certificate cert : chain) {
- result += cert.hashCode();
- }
- // Note that we do not hash based on trustAnchor;
- // this changes dynamically but we need a constant hashCode for purposes of
- // hashing in a Set.
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof SignerInfo))
- return false;
- if (obj == this)
- return true;
- SignerInfo other = (SignerInfo) obj;
- if (!mdAlgorithm.equals(other.getMessageDigestAlgorithm()))
- return false;
- Certificate[] otherCerts = other.getCertificateChain();
- if (otherCerts.length != chain.length)
- return false;
- for (int i = 0; i < chain.length; i++)
- if (!chain[i].equals(otherCerts[i]))
- return false;
- return trustAnchor == null ? other.getTrustAnchor() == null : trustAnchor.equals(other.getTrustAnchor());
- }
-}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java
index e745e8834..7d58ac375 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java
@@ -13,11 +13,13 @@
package org.eclipse.osgi.internal.signedcontent;
import java.security.cert.Certificate;
-import java.util.*;
-import org.eclipse.osgi.internal.framework.EquinoxBundle;
-import org.eclipse.osgi.internal.signedcontent.SignedStorageHook.StorageHookImpl;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
+import org.eclipse.osgi.internal.signedcontent.SignedContentFromBundleFile.BaseSignerInfo;
+import org.eclipse.osgi.signedcontent.SignedContent;
import org.eclipse.osgi.signedcontent.SignerInfo;
-import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -35,19 +37,21 @@ public class TrustEngineListener {
// re-evaluate trust and check authorization for these SignedContents
Bundle[] bundles = context.getBundles();
for (Bundle bundle : bundles) {
- SignedContentImpl signedContent = getSignedContent(bundle);
+ SignedContentFromBundleFile signedContent = getSignedContent(bundle);
if (signedContent != null && signedContent.isSigned()) {
// check the SignerInfos for this content
SignerInfo[] infos = signedContent.getSignerInfos();
for (SignerInfo info : infos) {
if (info.getTrustAnchor() == null) {
// one of the signers is not trusted
- signedBundleHook.determineTrust(signedContent, SignedBundleHook.VERIFY_TRUST);
+ signedBundleHook.determineTrust(signedContent,
+ EquinoxConfiguration.SIGNED_CONTENT_VERIFY_TRUST);
} else {
SignerInfo tsa = signedContent.getTSASignerInfo(info);
if (tsa != null && tsa.getTrustAnchor() == null)
// one of the tsa signers is not trusted
- signedBundleHook.determineTrust(signedContent, SignedBundleHook.VERIFY_TRUST);
+ signedBundleHook.determineTrust(signedContent,
+ EquinoxConfiguration.SIGNED_CONTENT_VERIFY_TRUST);
}
}
}
@@ -61,7 +65,7 @@ public class TrustEngineListener {
Set<Bundle> usingAnchor = new HashSet<>();
Set<SignerInfo> untrustedSigners = new HashSet<>();
for (Bundle bundle : bundles) {
- SignedContentImpl signedContent = getSignedContent(bundle);
+ SignedContentFromBundleFile signedContent = getSignedContent(bundle);
if (signedContent != null && signedContent.isSigned()) {
// check signer infos for this content
SignerInfo[] infos = signedContent.getSignerInfos();
@@ -82,18 +86,16 @@ public class TrustEngineListener {
}
// remove trust anchors from untrusted signers
for (Iterator<SignerInfo> untrusted = untrustedSigners.iterator(); untrusted.hasNext();)
- ((SignerInfoImpl) untrusted.next()).setTrustAnchor(null);
+ ((BaseSignerInfo) untrusted.next()).setTrustAnchor(null);
// re-establish trust
for (Bundle bundle : usingAnchor) {
- SignedContentImpl signedContent = getSignedContent(bundle);
+ SignedContentFromBundleFile signedContent = getSignedContent(bundle);
// found an signer using the anchor for this bundle re-evaluate trust
- signedBundleHook.determineTrust(signedContent, SignedBundleHook.VERIFY_TRUST);
+ signedBundleHook.determineTrust(signedContent, EquinoxConfiguration.SIGNED_CONTENT_VERIFY_TRUST);
}
}
- private SignedContentImpl getSignedContent(Bundle bundle) {
- Generation generation = (Generation) ((EquinoxBundle) bundle).getModule().getCurrentRevision().getRevisionInfo();
- StorageHookImpl hook = generation.getStorageHook(SignedStorageHook.class);
- return hook != null ? hook.signedContent : null;
+ private SignedContentFromBundleFile getSignedContent(Bundle bundle) {
+ return (SignedContentFromBundleFile) bundle.adapt(SignedContent.class);
}
}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java
index 4d62547b4..538415f75 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java
@@ -46,13 +46,13 @@ public interface SignerInfo {
public boolean isTrusted();
/**
- * Returns the <code>MessageDigest</code> algorithm used to verify content signed by this
- * signer info.
+ * Returns the <code>MessageDigest</code> algorithm used to verify content
+ * signed by this signer info.
+ *
* @return the algorithm
+ * @deprecated This information is not readily available using the JAR APIs
+ * included with Java. A value {@literal unknown} will be returned.
*/
public String getMessageDigestAlgorithm();
- // TODO need more thought here, TrustEngines could get stale since they are services, leaving off for now unless until we understand the usecase for this.
- //public TrustEngine getTrustEngine();
-
}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
index 860c4ad46..34000ee59 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
@@ -1196,10 +1196,12 @@ public class Storage {
result = equinoxContainer.getConnectModules().getConnectBundleFile(connectModule, content, generation,
mruList, getConfiguration().getDebug());
} else if (isDirectory) {
- boolean strictPath = Boolean.parseBoolean(equinoxContainer.getConfiguration().getConfiguration(EquinoxConfiguration.PROPERTY_STRICT_BUNDLE_ENTRY_PATH, Boolean.FALSE.toString()));
+ boolean strictPath = Boolean.parseBoolean(getConfiguration().getConfiguration(
+ EquinoxConfiguration.PROPERTY_STRICT_BUNDLE_ENTRY_PATH, Boolean.FALSE.toString()));
result = new DirBundleFile(content, strictPath);
} else {
- result = new ZipBundleFile(content, generation, mruList, getConfiguration().getDebug());
+ result = new ZipBundleFile(content, generation, mruList, getConfiguration().getDebug(),
+ getConfiguration().runtimeVerifySignedBundles);
}
} catch (IOException e) {
throw new RuntimeException("Could not create bundle file.", e); //$NON-NLS-1$
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java
index a425013f6..efeb53de8 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/CloseableBundleFile.java
@@ -186,6 +186,9 @@ public abstract class CloseableBundleFile<E> extends BundleFile {
@Override
public File getFile(String entry, boolean nativeCode) {
+ if (generation == null) {
+ return null;
+ }
if (!lockOpen()) {
return null;
}
@@ -556,7 +559,9 @@ public abstract class CloseableBundleFile<E> extends BundleFile {
private IOException enrichExceptionWithBaseFile(IOException e) {
File baseFile = getBaseFile();
- String extraInfo = baseFile == null ? generation.getBundleInfo().getLocation() : baseFile.toString();
+ String extraInfo = baseFile == null ? //
+ generation == null ? null : generation.getBundleInfo().getLocation() : //
+ baseFile.toString();
return new IOException(extraInfo, e);
}
}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java
index 3f668a758..63003e118 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/bundlefile/ZipBundleFile.java
@@ -32,20 +32,23 @@ import org.eclipse.osgi.util.NLS;
*/
public class ZipBundleFile extends CloseableBundleFile<ZipEntry> {
+ final boolean verify;
/**
* The zip file
*/
volatile ZipFile zipFile;
- public ZipBundleFile(File basefile, BundleInfo.Generation generation, MRUBundleFileList mruList, Debug debug) throws IOException {
+ public ZipBundleFile(File basefile, BundleInfo.Generation generation, MRUBundleFileList mruList, Debug debug,
+ boolean verify) throws IOException {
super(basefile, generation, mruList, debug);
+ this.verify = verify;
if (!BundleFile.secureAction.exists(basefile))
throw new IOException(NLS.bind(Msg.ADAPTER_FILEEXIST_EXCEPTION, basefile));
}
@Override
protected void doOpen() throws IOException {
- zipFile = BundleFile.secureAction.getZipFile(this.basefile);
+ zipFile = BundleFile.secureAction.getZipFile(this.basefile, verify);
}
/**

Back to the top