diff options
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 Binary files differindex 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 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 Binary files differdeleted file mode 100644 index 84c087c41..000000000 --- a/bundles/org.eclipse.osgi.tests/test_files/security/bundles/multiply_signed_with_corrupt.jar +++ /dev/null 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 Binary files differindex 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 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 Binary files differnew file mode 100644 index 000000000..24cf148f5 --- /dev/null +++ b/bundles/org.eclipse.osgi.tests/test_files/security/bundles/signedJava16.jar 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 Binary files differindex 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 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 Binary files differindex 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 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 Binary files differindex 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 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 Binary files differindex 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 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 Binary files differindex 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 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 Binary files differindex 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 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); } /** |