diff --git a/src/main/java/com/openfin/desktop/demo/Signer.java b/src/main/java/com/openfin/desktop/demo/Signer.java index 22a611b..4c3bd42 100644 --- a/src/main/java/com/openfin/desktop/demo/Signer.java +++ b/src/main/java/com/openfin/desktop/demo/Signer.java @@ -1,2 +1,98 @@ -package com.openfin.desktop.demo;public class Signer { +package com.openfin.desktop.demo; + +import java.io.InputStream; +import java.security.CodeSigner; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class Signer { + private final String filename; + + public Signer(String filename) { + this.filename = filename; + } + + public void verify() throws Exception { + JarFile jar = new JarFile(this.filename, true); + + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + byte[] buffer = new byte[8192]; + InputStream is = jar.getInputStream(entry); + while ((is.read(buffer, 0, buffer.length)) != -1) { + // We just read. This will throw a SecurityException + // if a signature/digest check fails. + } + is.close(); + } + + if (!checkSign(jar)) { + throw new SecurityException("not signed"); + } + + } + + private boolean checkSign(JarFile jar) throws Exception { + InputStream jis = jar.getInputStream(jar.getEntry("META-INF/MANIFEST.MF")); + Manifest man = new Manifest(jis); + jis.close(); + + HashSet signed = new HashSet<>(); + for(Map.Entry entry: man.getEntries().entrySet()) { + for(Object attrkey: entry.getValue().keySet()) { + if (attrkey instanceof Attributes.Name && attrkey.toString().contains("-Digest")) { + signed.add(entry.getKey()); + } + } + } + System.out.printf("Number of Digest from manifest %d \n", signed.size()); + + Set entries = new HashSet<>(); + for(Enumeration entry = jar.entries(); entry.hasMoreElements(); ) { + JarEntry je = entry.nextElement(); + String fileName = je.getName().toUpperCase(); + if (!je.isDirectory() + && !fileName.endsWith(".MF") + && !fileName.endsWith(".SF") + && !fileName.endsWith(".DSA") + && !fileName.endsWith(".EC") + && !fileName.endsWith(".RSA") + ) { + CodeSigner[] signers = je.getCodeSigners(); + if (signers != null && signers.length == 1) { + CodeSigner signer = signers[0]; + if (signer.getSignerCertPath().getCertificates().size() != 4) { + throw new SecurityException(String.format("invalid cert chain %s", je.getName())); + } + X509Certificate cert = (X509Certificate) signer.getSignerCertPath().getCertificates().get(0); + if (!cert.getSubjectDN().toString().contains("OpenFin Inc.")) { + throw new SecurityException(String.format("invalid signed %s", je.getName())); + } + entries.add(je.getName()); + } else { + throw new SecurityException(String.format("missing cert %s", je.getName())); + } + } + } + System.out.printf("Number of signed entries %d \n", entries.size()); + + Set unsigned = new HashSet<>(entries); + unsigned.removeAll(signed); + return unsigned.size() == 0; + } + + public static void main(String[] args) throws Exception { + Signer signer = new Signer(args[0]); + signer.verify(); + } + } diff --git a/src/test/java/com/openfin/desktop/AllTests.java b/src/test/java/com/openfin/desktop/AllTests.java index 68f0374..b34e8ee 100644 --- a/src/test/java/com/openfin/desktop/AllTests.java +++ b/src/test/java/com/openfin/desktop/AllTests.java @@ -10,6 +10,6 @@ */ @RunWith(Suite.class) -@Suite.SuiteClasses({ ApplicationTest.class, OpenFinRuntimeTest.class, WindowTest.class, SystemTest.class, InterApplicationBusTest.class, ChannelTest.class, RuntimeConfigTest.class}) +@Suite.SuiteClasses({ ApplicationTest.class, OpenFinRuntimeTest.class, WindowTest.class, SystemTest.class, InterApplicationBusTest.class, ChannelTest.class, RuntimeConfigTest.class, SnapshotTest.class}) public class AllTests { } diff --git a/src/test/java/com/openfin/desktop/SnapshotTest.java b/src/test/java/com/openfin/desktop/SnapshotTest.java new file mode 100644 index 0000000..fc8c676 --- /dev/null +++ b/src/test/java/com/openfin/desktop/SnapshotTest.java @@ -0,0 +1,124 @@ +package com.openfin.desktop; + +import com.openfin.desktop.snapshot.SnapshotSource; +import com.openfin.desktop.snapshot.SnapshotSourceProvider; +import com.sun.xml.internal.bind.v2.runtime.unmarshaller.LocatorEx; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class SnapshotTest implements SnapshotSourceProvider { + + private static Logger logger = LoggerFactory.getLogger(SnapshotTest.class.getName()); + + private static final String DESKTOP_UUID = SnapshotTest.class.getName(); + private static DesktopConnection desktopConnection; + private static OpenFinRuntime runtime; + private static final JSONObject SNAPSHOT_CONTENT = new JSONObject("{width: 123}"); + + private JSONObject randomSnapshot; + + @BeforeClass + public static void setup() throws Exception { + logger.debug("starting"); + desktopConnection = TestUtils.setupConnection(DESKTOP_UUID); + if (desktopConnection != null) { + runtime = new OpenFinRuntime(desktopConnection); + } + } + + @AfterClass + public static void teardown() throws Exception { + TestUtils.teardownDesktopConnection(desktopConnection); + } + + @Test + public void initProviderThenCreateClient() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + final String appUuid = "initProviderThenCreateClient"; + desktopConnection.getSnapshotSource().initSnapshotSourceProviderAsync(appUuid, this).thenAccept(provider -> { + logger.debug("Snapshot provider created"); + latch.countDown(); + }); + desktopConnection.getSnapshotSource().createSnapshotSourceClientAsync(appUuid).thenAccept(client -> { + logger.debug("Snapshot client created"); + latch.countDown(); + }); + + latch.await(5, TimeUnit.SECONDS); + + assertEquals("initProviderThenCreateClient timeout", latch.getCount(), 0); + } + + @Test + public void initProviderThenCreateClientThenGetSnapshot() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + final String appUuid = "initProviderThenCreateClientThenGetSnapshot"; + desktopConnection.getSnapshotSource().initSnapshotSourceProviderAsync(appUuid, this).thenAccept(provider -> { + logger.debug("Snapshot provider created"); + latch.countDown(); + }); + + desktopConnection.getSnapshotSource().createSnapshotSourceClientAsync(appUuid).thenAccept(client -> { + client.getSnapshotAsync().thenAccept(snapshot -> { + if (SNAPSHOT_CONTENT.toString().equals(snapshot.toString())) { + latch.countDown(); + } + }); + }); + + latch.await(5, TimeUnit.SECONDS); + + assertEquals("initProviderThenCreateClientThenGetSnapshot timeout", latch.getCount(), 0); + } + + @Test + public void initProviderThenCreateClientThenApplySnapshot() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + final JSONObject random = new JSONObject(String.format("{value: %f}", Math.random())); + final String appUuid = "initProviderThenCreateClientThenApplySnapshot"; + desktopConnection.getSnapshotSource().initSnapshotSourceProviderAsync(appUuid, this).thenAccept(provider -> { + latch.countDown(); + }); + + desktopConnection.getSnapshotSource().createSnapshotSourceClientAsync(appUuid).thenAccept(client -> { + client.applySnapshotAsync(random).thenAccept(ack -> { + client.getSnapshotAsync().thenAccept(snapshot -> { + if (random.toString().equals(snapshot.toString())) { + latch.countDown(); + } + }); + + }); + }); + + latch.await(5, TimeUnit.SECONDS); + + assertEquals("initProviderThenCreateClientThenGetSnapshot timeout", latch.getCount(), 0); + } + + @Override + public JSONObject getSnapshot() { + if (this.randomSnapshot != null) { + return this.randomSnapshot; + } else { + return SNAPSHOT_CONTENT; + } + } + + @Override + public void applySnapshot(JSONObject snapshot) { + this.randomSnapshot = snapshot; + } +}