1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-12-05 20:51:07 +01: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:
vanitasvitae 2017-06-02 12:26:37 +02:00 committed by Florian Schmaus
parent ce36fb468c
commit e86700b040
95 changed files with 11770 additions and 22 deletions

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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));
}
}