Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Watson2021-04-29 18:26:56 +0000
committerThomas Watson2021-05-04 21:02:22 +0000
commita1b0ac2cf7034e29511cc2092e1f44e9b1137e9a (patch)
treea0aceed4a6e7193507d7de7a2d54e47f1986f14a
parent62bb51bbb663e4f16fee4938f2d56c9c3c1d89ea (diff)
downloadrt.equinox.framework-a1b0ac2cf7034e29511cc2092e1f44e9b1137e9a.tar.gz
rt.equinox.framework-a1b0ac2cf7034e29511cc2092e1f44e9b1137e9a.tar.xz
rt.equinox.framework-a1b0ac2cf7034e29511cc2092e1f44e9b1137e9a.zip
The SignedContent implementation has had its own implementation to handle parsing of RSA/DSA/SF files related to JAR signing. As new algorithms are added to Java this code has to keep up and understand the new stuff. This commit removes the home grown implementation for reading JAR signers and instead uses the built int JarFile and JarInputStream implementations of Java. This has some limitations 1) It is not easy to determine the SignerInfo message digest algorithm. The built in JAR APIs do not expose this detail. As such the method SignerInfo.getMessageDigestAlgorthm now always returns "unknown" and the method has been deprecated 2) Supporting runtime verification of signed bundles that are installed as directory (exploded) bundles is no longer available. Doing so will not perform well as we need to create a JarInputStream to do the verification over the complete directory structure and that cannot happen lazily as entries are read from the bundle. JAR'ed bundles now just use JarFile under the covers to do runtime verification if configured. ZipFile is still used if no runtime verification is configured (the default). This is to avoid always parsing the manifest on restarts. 3) On demand verification of a SignedContentEntry is not possible. With the Java JAR APIs you cannot even tell if a JAR entry is signed until the complete entry itself is read and verified up front. Now a SignedContentEntry is either signed and its content has been validated up front or it is not signed or it is signed but invalid (corrupted). When invalid the SignedContentEntry.verify() will throw an InvalidContentException, but in this case the entry will still be unsigned because the JAR APIs will not give us the entry certificates in this case to indicate the entry is signed. 4) Old deprecated message digest algorithms that may have been used to sign JARs may no longer be supported by the JAR APIs. In this case the JAR will simply be reported as not signed by SignedContent API. This fact forced me to have to re-sign many of the test JARs. Change-Id: I66279a333c22f852948415bddeda355535413106 Signed-off-by: Thomas Watson <tjwatson@us.ibm.com> Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.framework/+/180074
-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