diff options
Diffstat (limited to 'bundles/org.eclipse.osgi.tests')
14 files changed, 232 insertions, 29 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 |