1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-11 01:59:38 +02:00

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
This commit is contained in:
vanitasvitae 2017-11-28 15:33:41 +01:00 committed by Paul Schaub
parent fb7a22a761
commit 36dae1ece4
53 changed files with 4072 additions and 3408 deletions

View file

@ -10,7 +10,8 @@ dependencies {
compile project(":smack-im")
compile project(":smack-extensions")
compile project(":smack-omemo")
compile 'org.whispersystems:signal-protocol-java:2.4.0'
compile 'org.whispersystems:signal-protocol-java:2.6.2'
testCompile project(path: ":smack-core", configuration: "testRuntime")
testCompile project(path: ":smack-omemo", configuration: "testRuntime")
}

View file

@ -0,0 +1,62 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smackx.omemo.signal;
import org.jivesoftware.smackx.omemo.CachingOmemoStore;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* Implementation of the CachingOmemoStore for smack-omemo-signal.
* This Store implementation can either be used as a proxy wrapping a persistent SignalOmemoStore in order to prevent
* excessive storage access, or it can be used standalone as an ephemeral store, which doesn't persist its contents.
*/
public class SignalCachingOmemoStore extends CachingOmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord,
SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
/**
* Create a new SignalCachingOmemoStore as a caching layer around a persisting OmemoStore
* (eg. a SignalFileBasedOmemoStore).
* @param wrappedStore
*/
public SignalCachingOmemoStore(OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord,
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> wrappedStore)
{
super(wrappedStore);
}
/**
* Create a new SignalCachingOmemoStore as an ephemeral standalone OmemoStore.
*/
public SignalCachingOmemoStore() {
super(new SignalOmemoKeyUtil());
}
}

View file

@ -42,7 +42,9 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord;
*/
@SuppressWarnings("unused")
public class SignalFileBasedOmemoStore
extends FileBasedOmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
extends FileBasedOmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
public SignalFileBasedOmemoStore() {
super();
@ -53,7 +55,9 @@ public class SignalFileBasedOmemoStore
}
@Override
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil() {
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil()
{
return new SignalOmemoKeyUtil();
}
}

View file

@ -21,16 +21,13 @@
package org.jivesoftware.smackx.omemo.signal;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoBundleVAxolotlElement;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoSession;
import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
import org.jxmpp.jid.impl.JidCreate;
@ -54,25 +51,27 @@ import org.whispersystems.libsignal.util.KeyHelper;
* @author Paul Schaub
*/
public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord,
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
@Override
public IdentityKeyPair generateOmemoIdentityKeyPair() {
return KeyHelper.generateIdentityKeyPair();
}
@Override
public HashMap<Integer, PreKeyRecord> generateOmemoPreKeys(int currentPreKeyId, int count) {
public TreeMap<Integer, PreKeyRecord> generateOmemoPreKeys(int currentPreKeyId, int count) {
List<PreKeyRecord> preKeyRecords = KeyHelper.generatePreKeys(currentPreKeyId, count);
HashMap<Integer, PreKeyRecord> hashMap = new HashMap<>();
TreeMap<Integer, PreKeyRecord> map = new TreeMap<>();
for (PreKeyRecord p : preKeyRecords) {
hashMap.put(p.getId(), p);
map.put(p.getId(), p);
}
return hashMap;
return map;
}
@Override
public SignedPreKeyRecord generateOmemoSignedPreKey(IdentityKeyPair identityKeyPair, int currentPreKeyId) throws CorruptedOmemoKeyException {
public SignedPreKeyRecord generateOmemoSignedPreKey(IdentityKeyPair identityKeyPair, int currentPreKeyId)
throws CorruptedOmemoKeyException
{
try {
return KeyHelper.generateSignedPreKey(identityKeyPair, currentPreKeyId);
} catch (InvalidKeyException e) {
@ -82,6 +81,7 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public SignedPreKeyRecord signedPreKeyFromBytes(byte[] data) throws IOException {
if (data == null) return null;
return new SignedPreKeyRecord(data);
}
@ -90,21 +90,9 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
return signedPreKeyRecord.serialize();
}
@Override
public OmemoSession<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
createOmemoSession(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> omemoStore,
OmemoDevice contact, IdentityKey identityKey) {
return new SignalOmemoSession(omemoManager, omemoStore, contact, identityKey);
}
@Override
public OmemoSession<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
createOmemoSession(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> omemoStore, OmemoDevice from) {
return new SignalOmemoSession(omemoManager, omemoStore, from);
}
@Override
public SessionRecord rawSessionFromBytes(byte[] data) throws IOException {
if (data == null) return null;
return new SessionRecord(data);
}
@ -115,6 +103,7 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public IdentityKeyPair identityKeyPairFromBytes(byte[] data) throws CorruptedOmemoKeyException {
if (data == null) return null;
try {
return new IdentityKeyPair(data);
} catch (InvalidKeyException e) {
@ -124,6 +113,7 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public IdentityKey identityKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException {
if (data == null) return null;
try {
return new IdentityKey(data, 0);
} catch (InvalidKeyException e) {
@ -133,6 +123,7 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public ECPublicKey ellipticCurvePublicKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException {
if (data == null) return null;
try {
return Curve.decodePoint(data, 0);
} catch (InvalidKeyException e) {
@ -147,11 +138,14 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public PreKeyRecord preKeyFromBytes(byte[] bytes) throws IOException {
if (bytes == null) return null;
return new PreKeyRecord(bytes);
}
@Override
public PreKeyBundle bundleFromOmemoBundle(OmemoBundleVAxolotlElement bundle, OmemoDevice contact, int preKeyId) throws CorruptedOmemoKeyException {
public PreKeyBundle bundleFromOmemoBundle(OmemoBundleVAxolotlElement bundle, OmemoDevice contact, int preKeyId)
throws CorruptedOmemoKeyException
{
return new PreKeyBundle(0,
contact.getDeviceId(),
preKeyId,
@ -208,20 +202,35 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
}
@Override
public OmemoFingerprint getFingerprint(IdentityKey identityKey) {
public OmemoFingerprint getFingerprintOfIdentityKey(IdentityKey identityKey) {
if (identityKey == null) {
return null;
}
String fp = identityKey.getFingerprint();
// Cut "(byte)0x" prefixes, remove spaces and commas, cut first two digits.
fp = fp.replace("(byte)0x", "").replace(",", "").replace(" ", "").substring(2);
fp = fp.replace("(byte)0x", "").replace(",", "")
.replace(" ", "").substring(2);
return new OmemoFingerprint(fp);
}
@Override
public OmemoFingerprint getFingerprintOfIdentityKeyPair(IdentityKeyPair identityKeyPair) {
if (identityKeyPair == null) {
return null;
}
return getFingerprintOfIdentityKey(identityKeyPair.getPublicKey());
}
@Override
public SignalProtocolAddress omemoDeviceAsAddress(OmemoDevice contact) {
return new SignalProtocolAddress(contact.getJid().asBareJid().toString(), contact.getDeviceId());
}
@Override
public OmemoDevice addressAsOmemoDevice(SignalProtocolAddress address) throws XmppStringprepException {
public OmemoDevice addressAsOmemoDevice(SignalProtocolAddress address)
throws XmppStringprepException
{
return new OmemoDevice(JidCreate.bareFrom(address.getName()), address.getDeviceId());
}
}

View file

@ -26,7 +26,6 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.logging.Level;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
@ -34,7 +33,6 @@ import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoService;
import org.jivesoftware.smackx.omemo.OmemoStore;
@ -59,16 +57,31 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord;
* @author Paul Schaub
*/
@SuppressWarnings("unused")
public final class SignalOmemoService extends OmemoService<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
public final class SignalOmemoService
extends OmemoService<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
private static SignalOmemoService INSTANCE;
private static boolean LICENSE_ACKNOWLEDGED = false;
public static void setup() throws InvalidKeyException, XMPPErrorException, NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, SmackException, InterruptedException, CorruptedOmemoKeyException {
@Override
protected SignalOmemoSessionManager createOmemoSessionManager(
OmemoManager.KnownBareJidGuard manager,
OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> store)
{
return new SignalOmemoSessionManager(manager, getOmemoStoreBackend());
}
public static void setup()
throws InvalidKeyException, XMPPErrorException, NoSuchPaddingException, InvalidAlgorithmParameterException,
UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchProviderException, SmackException, InterruptedException, CorruptedOmemoKeyException
{
if (!LICENSE_ACKNOWLEDGED) {
throw new IllegalStateException("smack-omemo-signal is licensed under the terms of the GPLv3. Please be aware that you " +
"can only use this library within the terms of the GPLv3. See for example " +
"https://www.gnu.org/licenses/quick-guide-gplv3 for more details. Please call " +
throw new IllegalStateException("smack-omemo-signal is licensed under the terms of the GPLv3. " +
"Please be aware that you can only use this library within the terms of the GPLv3. " +
"See for example https://www.gnu.org/licenses/quick-guide-gplv3 for more details. Please call " +
"SignalOmemoService.acknowledgeLicense() prior to the setup() method in order to prevent " +
"this exception.");
}
@ -79,8 +92,10 @@ public final class SignalOmemoService extends OmemoService<IdentityKeyPair, Iden
}
@Override
public OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> createDefaultOmemoStoreBackend() {
return new SignalFileBasedOmemoStore();
public OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
createDefaultOmemoStoreBackend() {
return new SignalCachingOmemoStore();
}
private SignalOmemoService()
@ -96,14 +111,17 @@ public final class SignalOmemoService extends OmemoService<IdentityKeyPair, Iden
}
@Override
protected void processBundle(OmemoManager omemoManager, PreKeyBundle preKeyBundle, OmemoDevice contact) throws CorruptedOmemoKeyException {
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(omemoManager, getOmemoStoreBackend());
protected void processBundle(OmemoManager.KnownBareJidGuard managerGuard,
PreKeyBundle preKeyBundle,
OmemoDevice device)
throws CorruptedOmemoKeyException
{
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(managerGuard, getOmemoStoreBackend());
SessionBuilder builder = new SessionBuilder(connector, connector, connector, connector,
getOmemoStoreBackend().keyUtil().omemoDeviceAsAddress(contact));
getOmemoStoreBackend().keyUtil().omemoDeviceAsAddress(device));
try {
builder.process(preKeyBundle);
LOGGER.log(Level.INFO, "Session built with " + contact);
getOmemoStoreBackend().getOmemoSessionOf(omemoManager, contact); //method puts session in session map.
LOGGER.log(Level.FINE, "Session built with " + device);
} catch (org.whispersystems.libsignal.InvalidKeyException e) {
throw new CorruptedOmemoKeyException(e.getMessage());
} catch (UntrustedIdentityException e) {

View file

@ -1,142 +0,0 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smackx.omemo.signal;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoSession;
import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.libsignal.LegacyMessageException;
import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
import org.whispersystems.libsignal.protocol.SignalMessage;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* Concrete implementation of the OmemoSession using the Signal library.
*
* @author Paul Schaub
*/
public class SignalOmemoSession extends OmemoSession<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
private static final Logger LOGGER = Logger.getLogger(SignalOmemoSession.class.getName());
/**
* Constructor used when the remote user initialized the session using a PreKeyOmemoMessage.
*
* @param omemoManager omemoManager
* @param omemoStore omemoStoreConnector that can be used to get information from
* @param remoteContact omemoDevice of the remote contact
* @param identityKey identityKey of the remote contact
*/
SignalOmemoSession(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> omemoStore,
OmemoDevice remoteContact, IdentityKey identityKey) {
super(omemoManager, omemoStore, remoteContact, identityKey);
}
/**
* Constructor used when we initiate a new Session with the remote user.
*
* @param omemoManager omemoManager
* @param omemoStore omemoStore used to get information from
* @param remoteContact omemoDevice of the remote contact
*/
SignalOmemoSession(OmemoManager omemoManager,
OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> omemoStore,
OmemoDevice remoteContact) {
super(omemoManager, omemoStore, remoteContact);
}
@Override
public SessionCipher createCipher(OmemoDevice contact) {
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(omemoManager, omemoStore);
return new SessionCipher(connector, connector, connector, connector,
omemoStore.keyUtil().omemoDeviceAsAddress(contact));
}
@Override
public CiphertextTuple encryptMessageKey(byte[] messageKey) {
CiphertextMessage ciphertextMessage;
ciphertextMessage = cipher.encrypt(messageKey);
int type = (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE ?
OmemoElement.TYPE_OMEMO_PREKEY_MESSAGE : OmemoElement.TYPE_OMEMO_MESSAGE);
return new CiphertextTuple(ciphertextMessage.serialize(), type);
}
@Override
public byte[] decryptMessageKey(byte[] encryptedKey) throws NoRawSessionException {
byte[] decryptedKey = null;
try {
try {
PreKeySignalMessage message = new PreKeySignalMessage(encryptedKey);
if (!message.getPreKeyId().isPresent()) {
LOGGER.log(Level.WARNING, "PreKeySignalMessage did not contain a PreKeyId");
return null;
}
LOGGER.log(Level.INFO, "PreKeySignalMessage received, new session ID: " + message.getSignedPreKeyId() + "/" + message.getPreKeyId().get());
IdentityKey messageIdentityKey = message.getIdentityKey();
if (this.identityKey != null && !this.identityKey.equals(messageIdentityKey)) {
LOGGER.log(Level.INFO, "Had session with fingerprint " + getFingerprint() +
", received message with different fingerprint " + omemoStore.keyUtil().getFingerprint(messageIdentityKey) +
". Silently drop the message.");
} else {
this.identityKey = messageIdentityKey;
decryptedKey = cipher.decrypt(message);
this.preKeyId = message.getPreKeyId().get();
}
} catch (InvalidMessageException | InvalidVersionException e) {
SignalMessage message = new SignalMessage(encryptedKey);
decryptedKey = cipher.decrypt(message);
} catch (InvalidKeyIdException e) {
throw new NoRawSessionException(e);
}
catch (InvalidKeyException | UntrustedIdentityException e) {
LOGGER.log(Level.SEVERE, "Error decrypting message header, " + e.getClass().getName() + ": " + e.getMessage());
}
} catch (InvalidMessageException | NoSessionException e) {
throw new NoRawSessionException(e);
} catch (LegacyMessageException | DuplicateMessageException e) {
LOGGER.log(Level.SEVERE, "Error decrypting message header, " + e.getClass().getName() + ": " + e.getMessage());
}
return decryptedKey;
}
}

View file

@ -0,0 +1,164 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smackx.omemo.signal;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoSessionManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.libsignal.LegacyMessageException;
import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
import org.whispersystems.libsignal.protocol.SignalMessage;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
public class SignalOmemoSessionManager
extends OmemoSessionManager<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
private static final Logger LOGGER = Logger.getLogger(OmemoSessionManager.class.getName());
private final SignalOmemoStoreConnector storeConnector;
public SignalOmemoSessionManager(OmemoManager.KnownBareJidGuard managerGuard,
OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord,
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle,
SessionCipher> store)
{
super(managerGuard, store);
this.storeConnector = new SignalOmemoStoreConnector(managerGuard, store);
}
@Override
public byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey)
throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException,
UntrustedOmemoIdentityException
{
SessionCipher cipher = getCipher(sender);
byte[] decryptedKey;
// Try to handle the message as a PreKeySignalMessage...
try {
PreKeySignalMessage preKeyMessage = new PreKeySignalMessage(encryptedKey);
if (!preKeyMessage.getPreKeyId().isPresent()) {
throw new CryptoFailedException("PreKeyMessage did not contain a preKeyId.");
}
IdentityKey messageIdentityKey = preKeyMessage.getIdentityKey();
IdentityKey previousIdentityKey = store.loadOmemoIdentityKey(storeConnector.getOurDevice(), sender);
if (previousIdentityKey != null &&
!previousIdentityKey.getFingerprint().equals(messageIdentityKey.getFingerprint())) {
throw new UntrustedOmemoIdentityException(sender,
store.keyUtil().getFingerprintOfIdentityKey(previousIdentityKey),
store.keyUtil().getFingerprintOfIdentityKey(messageIdentityKey));
}
try {
decryptedKey = cipher.decrypt(preKeyMessage);
}
catch (UntrustedIdentityException e) {
throw new AssertionError("Signals trust management MUST be disabled.");
}
catch (LegacyMessageException | InvalidKeyException e) {
throw new CryptoFailedException(e);
}
catch (InvalidKeyIdException e) {
throw new NoRawSessionException(e);
}
catch (DuplicateMessageException e) {
LOGGER.log(Level.INFO, "Decryption of PreKeyMessage from " + sender +
" failed, since the message has been decrypted before.");
return null;
}
} catch (InvalidVersionException | InvalidMessageException noPreKeyMessage) {
// ...if that fails, handle it as a SignalMessage
try {
SignalMessage message = new SignalMessage(encryptedKey);
decryptedKey = getCipher(sender).decrypt(message);
}
catch (UntrustedIdentityException e) {
throw new AssertionError("Signals trust management MUST be disabled.");
}
catch (InvalidMessageException | NoSessionException e) {
throw new NoRawSessionException(e);
}
catch (LegacyMessageException e) {
throw new CryptoFailedException(e);
}
catch (DuplicateMessageException e1) {
LOGGER.log(Level.INFO, "Decryption of SignalMessage from " + sender +
" failed, since the message has been decrypted before.");
return null;
}
}
return decryptedKey;
}
@Override
public CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey) {
CiphertextMessage ciphertextMessage;
try {
ciphertextMessage = getCipher(recipient).encrypt(messageKey);
} catch (UntrustedIdentityException e) {
throw new AssertionError("Signals trust management MUST be disabled.");
}
// TODO: Figure out, if this is enough...
int type = (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE ?
OmemoElement.TYPE_OMEMO_PREKEY_MESSAGE : OmemoElement.TYPE_OMEMO_MESSAGE);
return new CiphertextTuple(ciphertextMessage.serialize(), type);
}
private SessionCipher getCipher(OmemoDevice device) {
SignalOmemoKeyUtil keyUtil = (SignalOmemoKeyUtil) store.keyUtil();
SignalProtocolAddress address = keyUtil.omemoDeviceAsAddress(device);
return new SessionCipher(storeConnector, storeConnector, storeConnector, storeConnector, address);
}
}

View file

@ -40,12 +40,14 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord;
*/
@SuppressWarnings("unused")
public abstract class SignalOmemoStore
extends OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
extends OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
private final SignalOmemoKeyUtil signalKeyUtil = new SignalOmemoKeyUtil();
@Override
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil() {
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil() {
return signalKeyUtil;
}
}

View file

@ -21,15 +21,17 @@
package org.jivesoftware.smackx.omemo.signal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.whispersystems.libsignal.IdentityKey;
@ -57,21 +59,28 @@ public class SignalOmemoStoreConnector
private static final Logger LOGGER = Logger.getLogger(SignalOmemoStoreConnector.class.getName());
private final OmemoManager omemoManager;
private final OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
private final OmemoManager.KnownBareJidGuard managerGuard;
private final OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
omemoStore;
public SignalOmemoStoreConnector(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> store) {
this.omemoManager = omemoManager;
public SignalOmemoStoreConnector(OmemoManager.KnownBareJidGuard managerGuard, OmemoStore<IdentityKeyPair,
IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey,
PreKeyBundle, SessionCipher> store) {
this.managerGuard = managerGuard;
this.omemoStore = store;
}
OmemoDevice getOurDevice() {
return managerGuard.get().getOwnDevice();
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
try {
return omemoStore.loadOmemoIdentityKeyPair(omemoManager);
return omemoStore.loadOmemoIdentityKeyPair(getOurDevice());
} catch (CorruptedOmemoKeyException e) {
LOGGER.log(Level.SEVERE, "getIdentityKeyPair has failed: " + e, e);
LOGGER.log(Level.SEVERE, "IdentityKeyPair seems to be invalid.", e);
return null;
}
}
@ -86,33 +95,41 @@ public class SignalOmemoStoreConnector
}
@Override
public void saveIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
public boolean saveIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
OmemoDevice device;
try {
omemoStore.storeOmemoIdentityKey(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress), identityKey);
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
omemoStore.storeOmemoIdentityKey(getOurDevice(), device, identityKey);
return true;
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
// Disable internal trust management. Instead we use OmemoStore.isTrustedOmemoIdentity() before encrypting for a
// recipient.
public boolean isTrustedIdentity(SignalProtocolAddress signalProtocolAddress,
IdentityKey identityKey,
Direction direction) {
// Disable internal trust management. Instead we use OmemoStore.isTrustedOmemoIdentity() before encrypting
// for a recipient.
return true;
}
@Override
public PreKeyRecord loadPreKey(int i) throws InvalidKeyIdException {
PreKeyRecord pr = omemoStore.loadOmemoPreKey(omemoManager, i);
if (pr == null) {
throw new InvalidKeyIdException("No PreKey with Id " + i + " found!");
PreKeyRecord preKey = omemoStore.loadOmemoPreKey(getOurDevice(), i);
if (preKey == null) {
throw new InvalidKeyIdException("No PreKey with Id " + i + " found.");
}
return pr;
return preKey;
}
@Override
public void storePreKey(int i, PreKeyRecord preKeyRecord) {
omemoStore.storeOmemoPreKey(omemoManager, i, preKeyRecord);
omemoStore.storeOmemoPreKey(getOurDevice(), i, preKeyRecord);
}
@Override
@ -120,96 +137,112 @@ public class SignalOmemoStoreConnector
try {
return (loadPreKey(i) != null);
} catch (InvalidKeyIdException e) {
LOGGER.log(Level.WARNING, "containsPreKey has failed: " + e.getMessage());
return false;
}
}
@Override
public void removePreKey(int i) {
omemoStore.removeOmemoPreKey(omemoManager, i);
omemoStore.removeOmemoPreKey(getOurDevice(), i);
}
@Override
public SessionRecord loadSession(SignalProtocolAddress signalProtocolAddress) {
OmemoDevice device;
try {
SessionRecord s = omemoStore.loadRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
return (s != null ? s : new SessionRecord());
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
SessionRecord record = omemoStore.loadRawSession(getOurDevice(), device);
if (record != null) {
return record;
} else {
return new SessionRecord();
}
}
@Override
public List<Integer> getSubDeviceSessions(String s) {
HashMap<Integer, SessionRecord> contactsSessions;
BareJid jid;
try {
contactsSessions = omemoStore.loadAllRawSessionsOf(omemoManager, JidCreate.bareFrom(s));
jid = JidCreate.bareFrom(s);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
if (contactsSessions != null) {
return new ArrayList<>(contactsSessions.keySet());
}
return new ArrayList<>();
return new ArrayList<>(omemoStore.loadAllRawSessionsOf(getOurDevice(), jid).keySet());
}
@Override
public void storeSession(SignalProtocolAddress signalProtocolAddress, SessionRecord sessionRecord) {
OmemoDevice device;
try {
omemoStore.storeRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress), sessionRecord);
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
omemoStore.storeRawSession(getOurDevice(), device, sessionRecord);
}
@Override
public boolean containsSession(SignalProtocolAddress signalProtocolAddress) {
OmemoDevice device;
try {
return omemoStore.containsRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
return omemoStore.containsRawSession(getOurDevice(), device);
}
@Override
public void deleteSession(SignalProtocolAddress signalProtocolAddress) {
OmemoDevice device;
try {
omemoStore.removeRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
omemoStore.removeRawSession(getOurDevice(), device);
}
@Override
public void deleteAllSessions(String s) {
BareJid jid;
try {
omemoStore.removeAllRawSessionsOf(omemoManager, JidCreate.bareFrom(s));
jid = JidCreate.bareFrom(s);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
omemoStore.removeAllRawSessionsOf(getOurDevice(), jid);
}
@Override
public SignedPreKeyRecord loadSignedPreKey(int i) throws InvalidKeyIdException {
SignedPreKeyRecord spkr = omemoStore.loadOmemoSignedPreKey(omemoManager, i);
if (spkr == null) {
throw new InvalidKeyIdException("No SignedPreKey with Id " + i + " found!");
}
return spkr;
return omemoStore.loadOmemoSignedPreKey(getOurDevice(), i);
}
@Override
public List<SignedPreKeyRecord> loadSignedPreKeys() {
HashMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap = omemoStore.loadOmemoSignedPreKeys(omemoManager);
List<SignedPreKeyRecord> signedPreKeyRecordList = new ArrayList<>();
TreeMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap =
omemoStore.loadOmemoSignedPreKeys(getOurDevice());
signedPreKeyRecordList.addAll(signedPreKeyRecordHashMap.values());
return signedPreKeyRecordList;
}
@Override
public void storeSignedPreKey(int i, SignedPreKeyRecord signedPreKeyRecord) {
omemoStore.storeOmemoSignedPreKey(omemoManager, i, signedPreKeyRecord);
omemoStore.storeOmemoSignedPreKey(getOurDevice(), i, signedPreKeyRecord);
}
@Override
@ -224,6 +257,6 @@ public class SignalOmemoStoreConnector
@Override
public void removeSignedPreKey(int i) {
omemoStore.removeOmemoSignedPreKey(omemoManager, i);
omemoStore.removeOmemoSignedPreKey(getOurDevice(), i);
}
}

View file

@ -0,0 +1,122 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smack.omemo;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNotSame;
import static junit.framework.TestCase.assertTrue;
import static junit.framework.TestCase.fail;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.signal.SignalOmemoKeyUtil;
import org.junit.Test;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* Test SignalOmemoKeyUtil methods.
*
* @author Paul Schaub
*/
public class LegacySignalOmemoKeyUtilTest extends SmackTestSuite {
private final SignalOmemoKeyUtil keyUtil = new SignalOmemoKeyUtil();
@Test
public void omemoIdentityKeyPairSerializationTest() {
IdentityKeyPair ikp = keyUtil.generateOmemoIdentityKeyPair();
byte[] bytes = keyUtil.identityKeyPairToBytes(ikp);
assertNotNull("serialized identityKeyPair must not be null.",
bytes);
assertNotSame("serialized identityKeyPair must not be of length 0.",
0, bytes.length);
try {
IdentityKeyPair ikp2 = keyUtil.identityKeyPairFromBytes(bytes);
assertTrue("Deserialized IdentityKeyPairs PublicKey must equal the originals one.",
ikp.getPublicKey().equals(ikp2.getPublicKey()));
} catch (CorruptedOmemoKeyException e) {
fail("Caught exception while deserializing IdentityKeyPair.");
}
}
@Test
public void omemoIdentityKeySerializationTest() {
IdentityKey k = keyUtil.generateOmemoIdentityKeyPair().getPublicKey();
try {
assertEquals("Deserialized IdentityKey must equal the original one.",
k, keyUtil.identityKeyFromBytes(keyUtil.identityKeyToBytes(k)));
} catch (CorruptedOmemoKeyException e) {
fail("Caught exception while serializing and deserializing identityKey (" + e + "): " + e.getMessage());
}
}
@Test
public void generateOmemoSignedPreKeyTest() {
IdentityKeyPair ikp = keyUtil.generateOmemoIdentityKeyPair();
try {
SignedPreKeyRecord spk = keyUtil.generateOmemoSignedPreKey(ikp, 1);
assertNotNull("SignedPreKey must not be null.", spk);
assertEquals("SignedPreKeyId must match.", 1, spk.getId());
assertEquals("singedPreKeyId must match here also.", 1, keyUtil.signedPreKeyIdFromKey(spk));
} catch (CorruptedOmemoKeyException e) {
fail("Caught an exception while generating signedPreKey (" + e + "): " + e.getMessage());
}
}
@Test
public void getFingerprintTest() {
IdentityKeyPair ikp = keyUtil.generateOmemoIdentityKeyPair();
IdentityKey ik = ikp.getPublicKey();
assertTrue("Length of fingerprint must be 64.",
keyUtil.getFingerprintOfIdentityKey(ik).length() == 64);
}
@Test
public void addressToDeviceTest() {
SignalProtocolAddress address = new SignalProtocolAddress("test@server.tld",1337);
try {
OmemoDevice device = keyUtil.addressAsOmemoDevice(address);
assertEquals(device, new OmemoDevice(JidCreate.bareFrom("test@server.tld"), 1337));
} catch (XmppStringprepException e) {
fail("Could not convert address to device: " + e + " " + e.getMessage());
}
}
@Test
public void deviceToAddressTest() {
try {
OmemoDevice device = new OmemoDevice(JidCreate.bareFrom("test@server.tld"), 1337);
SignalProtocolAddress address = keyUtil.omemoDeviceAsAddress(device);
assertEquals(address, new SignalProtocolAddress("test@server.tld", 1337));
} catch (XmppStringprepException e) {
fail("Could not convert device to address: " + e + " " + e.getMessage());
}
}
}

View file

@ -57,7 +57,12 @@ import org.junit.Test;
public class OmemoManagerTest extends SmackTestSuite {
@Test
public void instantiationTest() throws CorruptedOmemoKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IllegalBlockSizeException, SmackException {
public void instantiationTest()
throws CorruptedOmemoKeyException, NoSuchAlgorithmException, UnsupportedEncodingException,
InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IllegalBlockSizeException,
SmackException
{
SignalOmemoService.acknowledgeLicense();
SignalOmemoService.setup();
@ -73,8 +78,8 @@ public class OmemoManagerTest extends SmackTestSuite {
assertNotNull(c);
assertNotNull(d);
assertEquals(123, a.getDeviceId());
assertEquals(234, b.getDeviceId());
assertEquals(Integer.valueOf(123), a.getDeviceId());
assertEquals(Integer.valueOf(234), b.getDeviceId());
assertFalse(a == b);
assertFalse(a == c);

View file

@ -1,218 +0,0 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
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.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertArrayEquals;
import static org.powermock.api.mockito.PowerMockito.when;
import java.io.File;
import java.util.Date;
import org.jivesoftware.smackx.omemo.FileBasedOmemoStore;
import org.jivesoftware.smackx.omemo.OmemoConfiguration;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.signal.SignalFileBasedOmemoStore;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* Test the file-based signalOmemoStore.
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({OmemoManager.class})
public class SignalFileBasedOmemoStoreTest {
private static File storePath;
private static SignalFileBasedOmemoStore omemoStore;
private static OmemoManager omemoManager;
private void deleteStore() {
FileBasedOmemoStore.deleteDirectory(storePath);
}
@BeforeClass
public static void setup() throws XmppStringprepException {
String userHome = System.getProperty("user.home");
if (userHome != null) {
File f = new File(userHome);
storePath = new File(f, ".config/smack-integration-test/store");
} else {
storePath = new File("int_test_omemo_store");
}
OmemoConfiguration.setFileBasedOmemoStoreDefaultPath(storePath);
omemoStore = new SignalFileBasedOmemoStore();
OmemoDevice device = new OmemoDevice(JidCreate.bareFrom("storeTest@server.tld"), 55155);
omemoManager = PowerMockito.mock(OmemoManager.class);
when(omemoManager.getDeviceId()).thenReturn(device.getDeviceId());
when(omemoManager.getOwnJid()).thenReturn(device.getJid());
when(omemoManager.getOwnDevice()).thenReturn(device);
}
@Before
public void before() {
deleteStore();
}
@After
public void after() {
deleteStore();
}
@Test
public void isFreshInstallationTest() {
assertTrue(omemoStore.isFreshInstallation(omemoManager));
omemoStore.storeOmemoIdentityKeyPair(omemoManager, omemoStore.generateOmemoIdentityKeyPair());
assertFalse(omemoStore.isFreshInstallation(omemoManager));
omemoStore.purgeOwnDeviceKeys(omemoManager);
assertTrue(omemoStore.isFreshInstallation(omemoManager));
}
@Test
public void defaultDeviceIdTest() throws XmppStringprepException {
assertEquals(-1, omemoStore.getDefaultDeviceId(omemoManager.getOwnJid()));
omemoStore.setDefaultDeviceId(omemoManager.getOwnJid(), 55);
assertEquals(55, omemoStore.getDefaultDeviceId(omemoManager.getOwnJid()));
assertEquals(-1, omemoStore.getDefaultDeviceId(JidCreate.bareFrom("randomGuy@server.tld")));
}
@Test
public void cachedDeviceListTest() throws XmppStringprepException {
OmemoDevice bob = new OmemoDevice(JidCreate.bareFrom("bob@builder.tv"), 666);
OmemoDevice craig = new OmemoDevice(JidCreate.bareFrom("craig@southpark.tv"), 3333333);
CachedDeviceList bobsList = new CachedDeviceList();
assertEquals(0, bobsList.getAllDevices().size());
bobsList.getActiveDevices().add(bob.getDeviceId());
bobsList.getActiveDevices().add(777);
bobsList.getInactiveDevices().add(888);
CachedDeviceList craigsList = new CachedDeviceList();
craigsList.addDevice(craig.getDeviceId());
assertEquals(3, bobsList.getAllDevices().size());
assertEquals(2, bobsList.getActiveDevices().size());
assertTrue(bobsList.getInactiveDevices().contains(888));
assertTrue(bobsList.getActiveDevices().contains(777));
assertTrue(bobsList.getAllDevices().contains(888));
assertEquals(0, craigsList.getInactiveDevices().size());
assertEquals(1, craigsList.getActiveDevices().size());
assertEquals(1, craigsList.getAllDevices().size());
assertEquals(craig.getDeviceId(), craigsList.getActiveDevices().iterator().next().intValue());
}
@Test
public void omemoIdentityKeyPairTest() throws CorruptedOmemoKeyException {
assertNull(omemoStore.loadOmemoIdentityKeyPair(omemoManager));
omemoStore.storeOmemoIdentityKeyPair(omemoManager, omemoStore.generateOmemoIdentityKeyPair());
IdentityKeyPair ikp = omemoStore.loadOmemoIdentityKeyPair(omemoManager);
assertNotNull(ikp);
assertTrue(omemoStore.keyUtil().getFingerprint(ikp.getPublicKey()).equals(omemoStore.getFingerprint(omemoManager)));
}
@Test
public void signedPreKeyTest() throws CorruptedOmemoKeyException {
assertEquals(0, omemoStore.loadOmemoSignedPreKeys(omemoManager).size());
IdentityKeyPair ikp = omemoStore.generateOmemoIdentityKeyPair();
SignedPreKeyRecord spk = omemoStore.generateOmemoSignedPreKey(ikp, 14);
omemoStore.storeOmemoSignedPreKey(omemoManager, 14, spk);
assertEquals(1, omemoStore.loadOmemoSignedPreKeys(omemoManager).size());
assertNotNull(omemoStore.loadOmemoSignedPreKey(omemoManager, 14));
assertArrayEquals(spk.serialize(), omemoStore.loadOmemoSignedPreKey(omemoManager, 14).serialize());
assertNull(omemoStore.loadOmemoSignedPreKey(omemoManager, 13));
assertEquals(0, omemoStore.loadCurrentSignedPreKeyId(omemoManager));
omemoStore.storeCurrentSignedPreKeyId(omemoManager, 15);
assertEquals(15, omemoStore.loadCurrentSignedPreKeyId(omemoManager));
omemoStore.removeOmemoSignedPreKey(omemoManager, 14);
assertNull(omemoStore.loadOmemoSignedPreKey(omemoManager, 14));
assertNull(omemoStore.getDateOfLastSignedPreKeyRenewal(omemoManager));
Date now = new Date();
omemoStore.setDateOfLastSignedPreKeyRenewal(omemoManager, now);
assertEquals(now, omemoStore.getDateOfLastSignedPreKeyRenewal(omemoManager));
}
@Test
public void preKeyTest() {
assertEquals(0, omemoStore.loadOmemoPreKeys(omemoManager).size());
assertNull(omemoStore.loadOmemoPreKey(omemoManager, 12));
omemoStore.storeOmemoPreKeys(omemoManager,
omemoStore.generateOmemoPreKeys(1, 20));
assertNotNull(omemoStore.loadOmemoPreKey(omemoManager, 12));
assertEquals(20, omemoStore.loadOmemoPreKeys(omemoManager).size());
omemoStore.removeOmemoPreKey(omemoManager, 12);
assertNull(omemoStore.loadOmemoPreKey(omemoManager, 12));
assertEquals(19, omemoStore.loadOmemoPreKeys(omemoManager).size());
assertEquals(0, omemoStore.loadLastPreKeyId(omemoManager));
omemoStore.storeLastPreKeyId(omemoManager, 35);
assertEquals(35, omemoStore.loadLastPreKeyId(omemoManager));
}
@Test
public void trustingTest() throws XmppStringprepException, CorruptedOmemoKeyException {
OmemoDevice bob = new OmemoDevice(JidCreate.bareFrom("bob@builder.tv"), 555);
IdentityKey bobsKey = omemoStore.generateOmemoIdentityKeyPair().getPublicKey();
assertFalse(omemoStore.isDecidedOmemoIdentity(omemoManager, bob, bobsKey));
assertFalse(omemoStore.isTrustedOmemoIdentity(omemoManager, bob, bobsKey));
omemoStore.trustOmemoIdentity(omemoManager, bob, bobsKey);
assertTrue(omemoStore.isDecidedOmemoIdentity(omemoManager, bob, omemoStore.keyUtil().getFingerprint(bobsKey)));
assertTrue(omemoStore.isTrustedOmemoIdentity(omemoManager, bob, omemoStore.keyUtil().getFingerprint(bobsKey)));
assertNull(omemoStore.loadOmemoIdentityKey(omemoManager, bob));
omemoStore.storeOmemoIdentityKey(omemoManager, bob, bobsKey);
assertNotNull(omemoStore.loadOmemoIdentityKey(omemoManager, bob));
IdentityKey bobsOtherKey = omemoStore.generateOmemoIdentityKeyPair().getPublicKey();
assertFalse(omemoStore.isTrustedOmemoIdentity(omemoManager, bob, bobsOtherKey));
assertFalse(omemoStore.isDecidedOmemoIdentity(omemoManager, bob, bobsOtherKey));
omemoStore.distrustOmemoIdentity(omemoManager, bob, omemoStore.keyUtil().getFingerprint(bobsKey));
assertTrue(omemoStore.isDecidedOmemoIdentity(omemoManager, bob, bobsKey));
assertFalse(omemoStore.isTrustedOmemoIdentity(omemoManager, bob, bobsKey));
assertNull(omemoStore.getDateOfLastReceivedMessage(omemoManager, bob));
Date now = new Date();
omemoStore.setDateOfLastReceivedMessage(omemoManager, bob, now);
assertEquals(now, omemoStore.getDateOfLastReceivedMessage(omemoManager, bob));
}
}

File diff suppressed because one or more lines are too long

View file

@ -41,6 +41,6 @@ public class SignalOmemoStoreConnectorTest {
@Test
public void isTrustedIdentityTest() {
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(null, null);
assertTrue("All identities must be trusted by default.", connector.isTrustedIdentity(null, null));
assertTrue("All identities must be trusted by default.", connector.isTrustedIdentity(null, null, null));
}
}

View file

@ -0,0 +1,84 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smack.omemo;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.signal.SignalCachingOmemoStore;
import org.jivesoftware.smackx.omemo.signal.SignalFileBasedOmemoStore;
import org.jivesoftware.smackx.omemo.signal.SignalOmemoKeyUtil;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.jxmpp.stringprep.XmppStringprepException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* smack-omemo-signal implementation of {@link OmemoStoreTest}.
* This class executes tests of its super class with available implementations of {@link OmemoStore}.
* So far this includes {@link SignalFileBasedOmemoStore}, {@link SignalCachingOmemoStore}.
*/
@RunWith(value = Parameterized.class)
public class SignalOmemoStoreTest extends OmemoStoreTest<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
public SignalOmemoStoreTest(OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> store)
throws XmppStringprepException {
super(store);
}
/**
* We are running this Test with multiple available OmemoStore implementations.
* @return
* @throws IOException
*/
@Parameterized.Parameters
public static Collection<Object[]> getParameters() throws IOException {
TemporaryFolder temp = initStaticTemp();
return Arrays.asList(new Object[][] {
// Simple file based store
{ new SignalFileBasedOmemoStore(temp.newFolder("sigFileBased"))},
// Ephemeral caching store
{ new SignalCachingOmemoStore()},
// Caching file based store
{ new SignalCachingOmemoStore(new SignalFileBasedOmemoStore(temp.newFolder("cachingSigFileBased")))}
});
}
@Test
public void keyUtilTest() {
assertTrue(store.keyUtil() instanceof SignalOmemoKeyUtil);
}
}