1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-06-14 23:54:49 +02:00
Smack/smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoStoreTest.java
vanitasvitae 36dae1ece4 Move trust management from OmemoStore to TrustCallback
Refactor various method names to more precisely reflect
what happens

Introduce CachingOmemoStore, SignalCachingOmemoStore,
which can be either used standalone as ephemeral OmemoStore
implementations, or as wrappers around other implementations
to add a cache layer for reduced storage access.

Get rid of "isFreshInstallation". Keys are now - given
that they don't exist - generated on startup.

Bump libsignal-protocol-java to 2.6.2

Prevent offline access to some functions which require
authenticated connection

Create more advanced unit tests and integration tests

Add async initialization function for OmemoManager

Remove session handling from smack-omemo.
This is now handled - in case of smack-omemo-signal -
solely by libsignal-protocol-java
2018-06-09 13:53:04 +02:00

347 lines
15 KiB
Java

/**
*
* 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.smack.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.FileBasedOmemoStore;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
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<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store;
private final OmemoDevice alice, bob;
private static final TemporaryFolder tmp = initStaticTemp();
OmemoStoreTest(OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 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<Integer, T_PreKey> 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<Integer, T_PreKey> 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<Integer, T_PreKey> 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<Integer, T_PreKey> 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<Integer, T_SigPreKey> 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<Integer, T_SigPreKey> 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};
CachedDeviceList before = new CachedDeviceList(
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);
CachedDeviceList 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 CachedDeviceList());
assertEquals("DeviceList must be empty after overwriting it with empty list.", 0,
store.loadCachedDeviceList(alice, bob.getJid()).getAllDevices().size());
}
@Test
public void loadAllRawSessionsReturnsEmptyMapTest() {
HashMap<Integer, T_Sess> 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());
}
// ##############################################################
}