mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-09-10 02:39:42 +02:00
Add OMEMO support
This commit adds the modules smack-omemo and smack-omemo-signal. smack-omemo is licensed under the Apache license like the rest of the smack project. smack-omemo-signal on the other hand is licensed under the GPLv3. Due to the fact, that smack-omemo is not of much use without smack-omemo-signal, the OMEMO feature can currently only be used by GPLv3 compatible software. This may change in the future, when a more permissively licensed module becomes available. Fixes SMACK-743.
This commit is contained in:
parent
ce36fb468c
commit
e86700b040
95 changed files with 11770 additions and 22 deletions
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
*
|
||||
* 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.FileBasedOmemoStore;
|
||||
import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
|
||||
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;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Implementation of a FileBasedOmemoStore for the smack-omemo-signal module.
|
||||
*
|
||||
* @author Paul Schaub
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class SignalFileBasedOmemoStore
|
||||
extends FileBasedOmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
|
||||
|
||||
public SignalFileBasedOmemoStore() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SignalFileBasedOmemoStore(File base) {
|
||||
super(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil() {
|
||||
return new SignalOmemoKeyUtil();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/**
|
||||
*
|
||||
* 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.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;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.SessionCipher;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.ecc.Curve;
|
||||
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;
|
||||
import org.whispersystems.libsignal.util.KeyHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Concrete implementation of the KeyUtil for an implementation using the Signal library.
|
||||
*
|
||||
* @author Paul Schaub
|
||||
*/
|
||||
public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord,
|
||||
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair generateOmemoIdentityKeyPair() {
|
||||
return KeyHelper.generateIdentityKeyPair();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, PreKeyRecord> generateOmemoPreKeys(int currentPreKeyId, int count) {
|
||||
List<PreKeyRecord> preKeyRecords = KeyHelper.generatePreKeys(currentPreKeyId, count);
|
||||
HashMap<Integer, PreKeyRecord> hashMap = new HashMap<>();
|
||||
for (PreKeyRecord p : preKeyRecords) {
|
||||
hashMap.put(p.getId(), p);
|
||||
}
|
||||
return hashMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignedPreKeyRecord generateOmemoSignedPreKey(IdentityKeyPair identityKeyPair, int currentPreKeyId) throws CorruptedOmemoKeyException {
|
||||
try {
|
||||
return KeyHelper.generateSignedPreKey(identityKeyPair, currentPreKeyId);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new CorruptedOmemoKeyException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignedPreKeyRecord signedPreKeyFromBytes(byte[] data) throws IOException {
|
||||
return new SignedPreKeyRecord(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] signedPreKeyToBytes(SignedPreKeyRecord signedPreKeyRecord) {
|
||||
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 {
|
||||
return new SessionRecord(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] rawSessionToBytes(SessionRecord session) {
|
||||
return session.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair identityKeyPairFromBytes(byte[] data) throws CorruptedOmemoKeyException {
|
||||
try {
|
||||
return new IdentityKeyPair(data);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new CorruptedOmemoKeyException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey identityKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException {
|
||||
try {
|
||||
return new IdentityKey(data, 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new CorruptedOmemoKeyException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECPublicKey ellipticCurvePublicKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException {
|
||||
try {
|
||||
return Curve.decodePoint(data, 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new CorruptedOmemoKeyException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] preKeyToBytes(PreKeyRecord preKeyRecord) {
|
||||
return preKeyRecord.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreKeyRecord preKeyFromBytes(byte[] bytes) throws IOException {
|
||||
return new PreKeyRecord(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreKeyBundle bundleFromOmemoBundle(OmemoBundleVAxolotlElement bundle, OmemoDevice contact, int preKeyId) throws CorruptedOmemoKeyException {
|
||||
return new PreKeyBundle(0,
|
||||
contact.getDeviceId(),
|
||||
preKeyId,
|
||||
BUNDLE.preKeyPublic(bundle, preKeyId),
|
||||
BUNDLE.signedPreKeyId(bundle),
|
||||
BUNDLE.signedPreKeyPublic(bundle),
|
||||
BUNDLE.signedPreKeySignature(bundle),
|
||||
BUNDLE.identityKey(bundle));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] signedPreKeySignatureFromKey(SignedPreKeyRecord signedPreKey) {
|
||||
return signedPreKey.getSignature();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int signedPreKeyIdFromKey(SignedPreKeyRecord signedPreKey) {
|
||||
return signedPreKey.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] identityKeyPairToBytes(IdentityKeyPair identityKeyPair) {
|
||||
return identityKeyPair.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey identityKeyFromPair(IdentityKeyPair identityKeyPair) {
|
||||
return identityKeyPair.getPublicKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] identityKeyForBundle(IdentityKey identityKey) {
|
||||
return identityKey.getPublicKey().serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] identityKeyToBytes(IdentityKey identityKey) {
|
||||
return identityKey.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] preKeyPublicKeyForBundle(ECPublicKey preKey) {
|
||||
return preKey.serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] preKeyForBundle(PreKeyRecord preKeyRecord) {
|
||||
return preKeyRecord.getKeyPair().getPublicKey().serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] signedPreKeyPublicForBundle(SignedPreKeyRecord signedPreKey) {
|
||||
return signedPreKey.getKeyPair().getPublicKey().serialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OmemoFingerprint getFingerprint(IdentityKey identityKey) {
|
||||
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);
|
||||
return new OmemoFingerprint(fp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignalProtocolAddress omemoDeviceAsAddress(OmemoDevice contact) {
|
||||
return new SignalProtocolAddress(contact.getJid().asBareJid().toString(), contact.getDeviceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OmemoDevice addressAsOmemoDevice(SignalProtocolAddress address) throws XmppStringprepException {
|
||||
return new OmemoDevice(JidCreate.bareFrom(address.getName()), address.getDeviceId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
*
|
||||
* 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.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;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.SessionBuilder;
|
||||
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.state.PreKeyBundle;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Concrete implementation of the OmemoService using the Signal library.
|
||||
*
|
||||
* @author Paul Schaub
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
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 {
|
||||
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 " +
|
||||
"SignalOmemoService.acknowledgeLicense() prior to the setup() method in order to prevent " +
|
||||
"this exception.");
|
||||
}
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new SignalOmemoService();
|
||||
}
|
||||
setInstance(INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> createDefaultOmemoStoreBackend() {
|
||||
return new SignalFileBasedOmemoStore();
|
||||
}
|
||||
|
||||
private SignalOmemoService()
|
||||
throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException,
|
||||
NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException,
|
||||
IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||
java.security.InvalidKeyException {
|
||||
super();
|
||||
}
|
||||
|
||||
public static void acknowledgeLicense() {
|
||||
LICENSE_ACKNOWLEDGED = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processBundle(OmemoManager omemoManager, PreKeyBundle preKeyBundle, OmemoDevice contact) throws CorruptedOmemoKeyException {
|
||||
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(omemoManager, getOmemoStoreBackend());
|
||||
SessionBuilder builder = new SessionBuilder(connector, connector, connector, connector,
|
||||
getOmemoStoreBackend().keyUtil().omemoDeviceAsAddress(contact));
|
||||
try {
|
||||
builder.process(preKeyBundle);
|
||||
LOGGER.log(Level.INFO, "Session built with " + contact);
|
||||
getOmemoStoreBackend().getOmemoSessionOf(omemoManager, contact); //method puts session in session map.
|
||||
} catch (org.whispersystems.libsignal.InvalidKeyException e) {
|
||||
throw new CorruptedOmemoKeyException(e.getMessage());
|
||||
} catch (UntrustedIdentityException e) {
|
||||
// This should never happen.
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
*
|
||||
* 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.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;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
*
|
||||
* 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.OmemoStore;
|
||||
import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
|
||||
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 OmemoStore using the Signal library.
|
||||
*
|
||||
* @author Paul Schaub
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class SignalOmemoStore
|
||||
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() {
|
||||
return signalKeyUtil;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
*
|
||||
* 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.OmemoManager;
|
||||
import org.jivesoftware.smackx.omemo.OmemoStore;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||
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.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.SessionCipher;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
||||
import org.whispersystems.libsignal.state.IdentityKeyStore;
|
||||
import org.whispersystems.libsignal.state.PreKeyBundle;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.PreKeyStore;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Class that adapts libsignal-protocol-java's Store classes to the OmemoStore class.
|
||||
*
|
||||
* @author Paul Schaub
|
||||
*/
|
||||
public class SignalOmemoStoreConnector
|
||||
implements IdentityKeyStore, SessionStore, PreKeyStore, SignedPreKeyStore {
|
||||
|
||||
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>
|
||||
omemoStore;
|
||||
|
||||
public SignalOmemoStoreConnector(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> store) {
|
||||
this.omemoManager = omemoManager;
|
||||
this.omemoStore = store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
try {
|
||||
return omemoStore.loadOmemoIdentityKeyPair(omemoManager);
|
||||
} catch (CorruptedOmemoKeyException e) {
|
||||
LOGGER.log(Level.SEVERE, "getIdentityKeyPair has failed: " + e, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't use this.
|
||||
* @return dummy
|
||||
*/
|
||||
@Override
|
||||
public int getLocalRegistrationId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
|
||||
try {
|
||||
omemoStore.storeOmemoIdentityKey(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress), identityKey);
|
||||
} catch (XmppStringprepException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
|
||||
//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!");
|
||||
}
|
||||
return pr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePreKey(int i, PreKeyRecord preKeyRecord) {
|
||||
omemoStore.storeOmemoPreKey(omemoManager, i, preKeyRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPreKey(int i) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionRecord loadSession(SignalProtocolAddress signalProtocolAddress) {
|
||||
try {
|
||||
SessionRecord s = omemoStore.loadRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
|
||||
return (s != null ? s : new SessionRecord());
|
||||
} catch (XmppStringprepException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getSubDeviceSessions(String s) {
|
||||
HashMap<Integer, SessionRecord> contactsSessions;
|
||||
try {
|
||||
contactsSessions = omemoStore.loadAllRawSessionsOf(omemoManager, JidCreate.bareFrom(s));
|
||||
} catch (XmppStringprepException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
if (contactsSessions != null) {
|
||||
return new ArrayList<>(contactsSessions.keySet());
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSession(SignalProtocolAddress signalProtocolAddress, SessionRecord sessionRecord) {
|
||||
try {
|
||||
omemoStore.storeRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress), sessionRecord);
|
||||
} catch (XmppStringprepException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSession(SignalProtocolAddress signalProtocolAddress) {
|
||||
try {
|
||||
return omemoStore.containsRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
|
||||
} catch (XmppStringprepException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSession(SignalProtocolAddress signalProtocolAddress) {
|
||||
try {
|
||||
omemoStore.removeRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
|
||||
} catch (XmppStringprepException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllSessions(String s) {
|
||||
try {
|
||||
omemoStore.removeAllRawSessionsOf(omemoManager, JidCreate.bareFrom(s));
|
||||
} catch (XmppStringprepException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||
HashMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap = omemoStore.loadOmemoSignedPreKeys(omemoManager);
|
||||
List<SignedPreKeyRecord> signedPreKeyRecordList = new ArrayList<>();
|
||||
signedPreKeyRecordList.addAll(signedPreKeyRecordHashMap.values());
|
||||
return signedPreKeyRecordList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSignedPreKey(int i, SignedPreKeyRecord signedPreKeyRecord) {
|
||||
omemoStore.storeOmemoSignedPreKey(omemoManager, i, signedPreKeyRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSignedPreKey(int i) {
|
||||
try {
|
||||
return loadSignedPreKey(i) != null;
|
||||
} catch (InvalidKeyIdException e) {
|
||||
LOGGER.log(Level.WARNING, "containsSignedPreKey has failed: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSignedPreKey(int i) {
|
||||
omemoStore.removeOmemoSignedPreKey(omemoManager, i);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Concrete implementation of OMEMO for smack using the signal-protocol-java library.
|
||||
* @author Paul Schaub
|
||||
*/
|
||||
package org.jivesoftware.smackx.omemo.signal;
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
*
|
||||
* 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 org.jivesoftware.smack.DummyConnection;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.test.util.SmackTestSuite;
|
||||
import org.jivesoftware.smack.test.util.TestUtils;
|
||||
import org.jivesoftware.smackx.omemo.OmemoManager;
|
||||
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||
import org.jivesoftware.smackx.omemo.provider.OmemoVAxolotlProvider;
|
||||
import org.jivesoftware.smackx.omemo.signal.SignalOmemoService;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
||||
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.assertTrue;
|
||||
|
||||
/**
|
||||
* Test OmemoManager functionality.
|
||||
*/
|
||||
public class OmemoManagerTest extends SmackTestSuite {
|
||||
|
||||
@Test
|
||||
public void instantiationTest() throws CorruptedOmemoKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IllegalBlockSizeException, SmackException {
|
||||
SignalOmemoService.acknowledgeLicense();
|
||||
SignalOmemoService.setup();
|
||||
|
||||
DummyConnection dummy = new DummyConnection();
|
||||
DummyConnection silly = new DummyConnection();
|
||||
OmemoManager a = OmemoManager.getInstanceFor(dummy, 123);
|
||||
OmemoManager b = OmemoManager.getInstanceFor(dummy, 234);
|
||||
OmemoManager c = OmemoManager.getInstanceFor(silly, 123);
|
||||
OmemoManager d = OmemoManager.getInstanceFor(dummy, 123);
|
||||
|
||||
assertNotNull(a);
|
||||
assertNotNull(b);
|
||||
assertNotNull(c);
|
||||
assertNotNull(d);
|
||||
|
||||
assertEquals(123, a.getDeviceId());
|
||||
assertEquals(234, b.getDeviceId());
|
||||
|
||||
assertFalse(a == b);
|
||||
assertFalse(a == c);
|
||||
assertFalse(b == c);
|
||||
assertTrue(a == d);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void randomDeviceIdTest() {
|
||||
int a = OmemoManager.randomDeviceId();
|
||||
int b = OmemoManager.randomDeviceId();
|
||||
|
||||
assertNotSame(a, b); // This is highly unlikely
|
||||
|
||||
assertTrue(a > 0);
|
||||
assertTrue(b > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stanzaRecognitionTest() throws Exception {
|
||||
String omemoXML = "<encrypted xmlns='eu.siacs.conversations.axolotl'><header sid='1009'><key rid='1337'>MwohBfRqBm2atj3fT0/KUDg59Cnvfpgoe/PLNIu1xgSXujEZEAAYACIwKh6TTC7VBQZcCcKnQlO+6s1GQ9DIRKH4JU7XrJ+JJnkPUwJ4VLSeOEQD7HmFbhQPTLZO0u/qlng=</key><iv>sN0amy4e2NBrlb4G/OjNIQ==</iv></header><payload>4xVUAeg4M0Mhk+5n3YG1x12Dw/cYTc0Z</payload></encrypted>";
|
||||
OmemoElement omemoElement = new OmemoVAxolotlProvider().parse(TestUtils.getParser(omemoXML));
|
||||
Message m = new Message();
|
||||
m.addExtension(omemoElement);
|
||||
Message n = new Message();
|
||||
|
||||
assertTrue(OmemoManager.stanzaContainsOmemoElement(m));
|
||||
assertFalse(OmemoManager.stanzaContainsOmemoElement(n));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
*
|
||||
* 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 org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.jivesoftware.smack.test.util.SmackTestSuite;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
|
||||
import org.junit.Test;
|
||||
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;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Security;
|
||||
|
||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
|
||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.PROVIDER;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
/**
|
||||
* Test the OmemoMessageBuilder.
|
||||
*/
|
||||
public class OmemoMessageBuilderTest extends SmackTestSuite {
|
||||
|
||||
@Test
|
||||
public void setTextTest() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException, InvalidKeyException {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
String message = "Hello World!";
|
||||
byte[] key = OmemoMessageBuilder.generateKey();
|
||||
byte[] iv = OmemoMessageBuilder.generateIv();
|
||||
|
||||
SecretKey secretKey = new SecretKeySpec(key, KEYTYPE);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
|
||||
|
||||
OmemoMessageBuilder<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
|
||||
mb = new OmemoMessageBuilder<>(null, null, key, iv);
|
||||
mb.setMessage(message);
|
||||
|
||||
byte[] expected = cipher.doFinal(message.getBytes(StringUtils.UTF8));
|
||||
byte[] messageKey = new byte[16];
|
||||
System.arraycopy(mb.getMessageKey(),0, messageKey, 0, 16);
|
||||
byte[] messagePlusTag = new byte[mb.getCiphertextMessage().length + 16];
|
||||
System.arraycopy(mb.getCiphertextMessage(),0,messagePlusTag,0,mb.getCiphertextMessage().length);
|
||||
System.arraycopy(mb.getMessageKey(), 16, messagePlusTag, mb.getCiphertextMessage().length, 16);
|
||||
|
||||
assertArrayEquals(key, messageKey);
|
||||
assertArrayEquals(expected, messagePlusTag);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/**
|
||||
*
|
||||
* 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 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
*
|
||||
* 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 org.jivesoftware.smackx.omemo.signal.SignalOmemoStoreConnector;
|
||||
import org.junit.Test;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
/**
|
||||
* Test some functionality of the SignalOmemoStoreConnector.
|
||||
*/
|
||||
public class SignalOmemoStoreConnectorTest {
|
||||
|
||||
@Test
|
||||
public void getLocalRegistrationIdTest() {
|
||||
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(null, null);
|
||||
assertEquals("RegistrationId must always be 0.", 0, connector.getLocalRegistrationId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTrustedIdentityTest() {
|
||||
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(null, null);
|
||||
assertTrue("All identities must be trusted by default.", connector.isTrustedIdentity(null, null));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue