/** * * Copyright Paul Schaub * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx.omemo; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNotSame; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertSame; import static junit.framework.TestCase.assertTrue; import java.io.IOException; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.TreeMap; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList; import org.jivesoftware.smackx.omemo.internal.OmemoDevice; import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint; import org.junit.AfterClass; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.stringprep.XmppStringprepException; public abstract class OmemoStoreTest { protected final OmemoStore store; private final OmemoDevice alice, bob; private static final TemporaryFolder tmp = initStaticTemp(); OmemoStoreTest(OmemoStore store) throws XmppStringprepException { this.store = store; alice = new OmemoDevice(JidCreate.bareFrom("alice@wonderland.lit"), 123); bob = new OmemoDevice(JidCreate.bareFrom("bob@builder.tv"), 987); } // Tests @Test public void keyUtilNotNull() { assertNotNull(store.keyUtil()); } @Test public void generateOmemoIdentityKeyPairDoesNotReturnNull() { assertNotNull(store.generateOmemoIdentityKeyPair()); } @Test public void identityKeyFromIdentityKeyPairIsNotNull() { T_IdKeyPair pair = store.generateOmemoIdentityKeyPair(); assertNotNull(store.keyUtil().identityKeyFromPair(pair)); } @Test public void storeLoadRemoveOmemoIdentityKeyPair() throws IOException, CorruptedOmemoKeyException { T_IdKeyPair before = store.generateOmemoIdentityKeyPair(); assertNull(store.loadOmemoIdentityKeyPair(alice)); store.storeOmemoIdentityKeyPair(alice, before); T_IdKeyPair after = store.loadOmemoIdentityKeyPair(alice); assertNotNull(after); // Fingerprints equal assertEquals(store.keyUtil().getFingerprintOfIdentityKeyPair(before), store.keyUtil().getFingerprintOfIdentityKeyPair(after)); // Byte-representation equals assertTrue(Arrays.equals( store.keyUtil().identityKeyPairToBytes(before), store.keyUtil().identityKeyPairToBytes(after))); // Non-existing keypair assertNull("Must return null for non-existing key pairs.", store.loadOmemoIdentityKeyPair(bob)); // Deleting works store.removeOmemoIdentityKeyPair(alice); assertNull(store.loadOmemoIdentityKeyPair(alice)); } @Test public void storeLoadRemoveOmemoIdentityKey() throws IOException, CorruptedOmemoKeyException { // Create IdentityKeys and get bytes T_IdKey keyA1 = store.keyUtil().identityKeyFromPair(store.generateOmemoIdentityKeyPair()); T_IdKey keyB1 = store.keyUtil().identityKeyFromPair(store.generateOmemoIdentityKeyPair()); byte[] bytesA1 = store.keyUtil().identityKeyToBytes(keyA1); byte[] bytesB = store.keyUtil().identityKeyToBytes(keyB1); // Not null and not of length 0 assertNotNull("Serialized identityKey cannot be null.", bytesA1); assertNotNull("Serialized identityKey cannot be null.", bytesB); assertNotSame("Serialized identityKey must be of length > 0.", 0, bytesA1.length); assertNotSame("Serialized identityKey must be of length > 0.", 0, bytesB.length); // Keys do not equal assertFalse("Generated IdentityKeys must not be equal (ULTRA unlikely).", Arrays.equals(bytesA1, bytesB)); // Loading must return null before and not null after saving assertNull("Must return null, the store could not have this key by now.", store.loadOmemoIdentityKey(alice, bob)); store.storeOmemoIdentityKey(alice, bob, keyA1); T_IdKey keyA2 = store.loadOmemoIdentityKey(alice, bob); assertNotNull(keyA2); // Loaded key must equal stored one byte[] bytesA2 = store.keyUtil().identityKeyToBytes(keyA2); assertTrue("Serialized loaded key must equal serialized stored one.", Arrays.equals(bytesA1, bytesA2)); // Non-existing keys must return null assertNull("Non-existing keys must be returned as null.", store.loadOmemoIdentityKey(bob, alice)); // Key must vanish when deleted. store.removeOmemoIdentityKey(alice, bob); assertNull(store.loadOmemoIdentityKey(alice, bob)); } @Test public void generateOmemoPreKeys() { TreeMap keys = store.generateOmemoPreKeys(31, 49); assertNotNull("Generated data structure must not be null.", keys); byte[] lastKey = null; for (int i = 31; i <= 79; i++) { assertEquals("Key ids must be ascending order, starting at 31.", Integer.valueOf(i), keys.firstKey()); assertNotNull("Every id must match to a key.", keys.get(keys.firstKey())); byte[] bytes = store.keyUtil().preKeyToBytes(keys.get(keys.firstKey())); assertNotNull("Serialized preKey must not be null.", bytes); assertNotSame("Serialized preKey must not be of length 0.", 0, bytes.length); if (lastKey != null) { assertFalse("PreKeys MUST NOT be equal.", Arrays.equals(lastKey, bytes)); } lastKey = bytes; keys.remove(keys.firstKey()); } assertEquals("After deleting 49 keys, there must be no keys left.", 0, keys.size()); } @Test public void storeLoadRemoveOmemoPreKeys() throws IOException, InterruptedException { TreeMap before = store.generateOmemoPreKeys(1, 10); assertEquals("The store must have no prekeys before this test.", 0, store.loadOmemoPreKeys(alice).size()); store.storeOmemoPreKeys(alice, before); TreeMap after = store.loadOmemoPreKeys(alice); assertNotNull("Loaded preKeys must not be null.", after); assertEquals("Loaded preKey count must equal stored count.", before.size(), after.size()); // Non-existing key must be returned as null assertNull("Non-existing preKey must be returned as null.", store.loadOmemoPreKey(alice, 10000)); int last = after.size(); for (int i = 1; i <= last; i++) { T_PreKey bKey = before.get(i); T_PreKey aKey = after.get(i); assertTrue("Loaded keys must equal stored ones.", Arrays.equals( store.keyUtil().preKeyToBytes(bKey), store.keyUtil().preKeyToBytes(aKey))); T_PreKey rKey = store.loadOmemoPreKey(alice, i); assertNotNull("Randomly accessed preKeys must not be null.", rKey); assertTrue("Randomly accessed preKeys must equal the stored ones.", Arrays.equals( store.keyUtil().preKeyToBytes(aKey), store.keyUtil().preKeyToBytes(rKey))); store.removeOmemoPreKey(alice, i); assertNull("PreKey must be null after deletion.", store.loadOmemoPreKey(alice, i)); } TreeMap postDeletion = store.loadOmemoPreKeys(alice); assertSame("PreKey count must equal 0 after deletion of all keys.", 0, postDeletion.size()); } @Test public void storeLoadRemoveOmemoSignedPreKeys() throws IOException, CorruptedOmemoKeyException { TreeMap before = store.loadOmemoSignedPreKeys(alice); assertEquals("At this stage, there must be no signed prekeys in the store.", 0, before.size()); T_IdKeyPair idp = store.generateOmemoIdentityKeyPair(); T_SigPreKey spk = store.generateOmemoSignedPreKey(idp, 125); assertNotNull("SignedPreKey must not be null.", spk); assertEquals("ID of signedPreKey must match.", 125, store.keyUtil().signedPreKeyIdFromKey(spk)); byte[] bytes = store.keyUtil().signedPreKeyToBytes(spk); assertNotNull("Serialized signedPreKey must not be null", bytes); assertNotSame("Serialized signedPreKey must not be of length 0.", 0, bytes.length); // Stored key must equal loaded key store.storeOmemoSignedPreKey(alice, 125, spk); TreeMap after = store.loadOmemoSignedPreKeys(alice); assertEquals("We must have exactly 1 signedPreKey now.", 1, after.size()); T_SigPreKey spk2 = after.get(after.firstKey()); assertEquals("Id of the stored signedPreKey must match the one we stored.", 125, store.keyUtil().signedPreKeyIdFromKey(spk2)); assertTrue("Serialization of stored and loaded signed preKey must equal.", Arrays.equals( store.keyUtil().signedPreKeyToBytes(spk), store.keyUtil().signedPreKeyToBytes(spk2))); // Random access T_SigPreKey rspk = store.loadOmemoSignedPreKey(alice, 125); assertTrue("Serialization of stored and randomly accessed signed preKey must equal.", Arrays.equals( store.keyUtil().signedPreKeyToBytes(spk), store.keyUtil().signedPreKeyToBytes(rspk))); assertNull("Non-existing signedPreKey must be returned as null.", store.loadOmemoSignedPreKey(alice, 10000)); // Deleting store.removeOmemoSignedPreKey(alice, 125); assertNull("Deleted key must be returned as null.", store.loadOmemoSignedPreKey(alice, 125)); assertEquals(0, store.loadOmemoSignedPreKeys(alice).size()); } @Test public void loadStoreDateOfLastSignedPreKeyRenewal() throws IOException { assertNull("The date of last signed preKey renewal must be null at this stage.", store.getDateOfLastSignedPreKeyRenewal(alice)); Date before = new Date(); store.setDateOfLastSignedPreKeyRenewal(alice, before); Date after = store.getDateOfLastSignedPreKeyRenewal(alice); assertEquals("Dates must equal.", after, before); } @Test public void loadStoreDateOfLastMessageReceived() throws IOException { assertNull("The date of last message received must be null at this stage.", store.getDateOfLastReceivedMessage(alice, bob)); Date before = new Date(); store.setDateOfLastReceivedMessage(alice, bob, before); Date after = store.getDateOfLastReceivedMessage(alice, bob); assertEquals("Dates must equal.", after, before); } @Test public void loadStoreCachedDeviceList() throws IOException { Integer[] active = new Integer[] {1,5,999,10}; Integer[] inactive = new Integer[] {6,7,8}; OmemoCachedDeviceList before = new OmemoCachedDeviceList( new HashSet<>(Arrays.asList(active)), new HashSet<>(Arrays.asList(inactive))); assertNotNull("Loading a non-existent cached deviceList must return an empty list.", store.loadCachedDeviceList(alice, bob.getJid())); store.storeCachedDeviceList(alice, bob.getJid(), before); OmemoCachedDeviceList after = store.loadCachedDeviceList(alice, bob.getJid()); assertTrue("Loaded deviceList must not be empty", after.getAllDevices().size() != 0); assertEquals("Number of entries in active devices must match.", active.length, after.getActiveDevices().size()); assertEquals("Number of entries in inactive devices must match.", inactive.length, after.getInactiveDevices().size()); assertEquals("Number of total entries must match.", active.length + inactive.length, after.getAllDevices().size()); for (Integer a : active) { assertTrue(after.getActiveDevices().contains(a)); assertTrue(after.getAllDevices().contains(a)); } for (Integer i : inactive) { assertTrue(after.getInactiveDevices().contains(i)); assertTrue(after.getAllDevices().contains(i)); } store.storeCachedDeviceList(alice, bob.getJid(), new OmemoCachedDeviceList()); assertEquals("DeviceList must be empty after overwriting it with empty list.", 0, store.loadCachedDeviceList(alice, bob.getJid()).getAllDevices().size()); } @Test public void loadAllRawSessionsReturnsEmptyMapTest() { HashMap sessions = store.loadAllRawSessionsOf(alice, bob.getJid()); assertNotNull(sessions); assertEquals(0, sessions.size()); } @Test public void loadNonExistentRawSessionReturnsNullTest() { T_Sess session = store.loadRawSession(alice, bob); assertNull(session); } @Test public void getFingerprint() throws IOException, CorruptedOmemoKeyException { assertNull("Method must return null for a non-existent fingerprint.", store.getFingerprint(alice)); store.storeOmemoIdentityKeyPair(alice, store.generateOmemoIdentityKeyPair()); OmemoFingerprint fingerprint = store.getFingerprint(alice); assertNotNull("fingerprint must not be null", fingerprint); assertEquals("Fingerprint must be of length 64", 64, fingerprint.length()); store.removeOmemoIdentityKeyPair(alice); //clean up } // ############################################################## // Workaround for https://github.com/junit-team/junit4/issues/671 static TemporaryFolder initStaticTemp() { try { return new TemporaryFolder() { { before(); } }; } catch (Throwable t) { throw new RuntimeException(t); } } @AfterClass public static void cleanup() throws Exception { FileBasedOmemoStore.deleteDirectory(tmp.getRoot()); } // ############################################################## }