Browse Source

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.
tags/4.2.1
vanitasvitae 3 years ago
committed by Florian Schmaus
parent
commit
e86700b040
95 changed files with 11770 additions and 22 deletions
  1. +1
    -0
      .gitignore
  2. +70
    -11
      build.gradle
  3. +1
    -1
      config/checkstyle.xml
  4. +20
    -0
      config/smack-omemo-signal-gplv3-license-header.txt
  5. +20
    -0
      config/smack-omemo-signal-integration-test-gplv3-license-header.txt
  6. +2
    -1
      documentation/extensions/index.md
  7. +217
    -0
      documentation/extensions/omemo.md
  8. +3
    -0
      settings.gradle
  9. +4
    -2
      smack-core/build.gradle
  10. +1
    -0
      smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml
  11. +1
    -0
      smack-integration-test/build.gradle
  12. +27
    -6
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
  13. +1
    -1
      smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java
  14. +80
    -0
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java
  15. +77
    -0
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoInitializationTest.java
  16. +155
    -0
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoIntegrationTestHelper.java
  17. +112
    -0
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoKeyTransportTest.java
  18. +184
    -0
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessageSendingTest.java
  19. +193
    -0
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoSessionRenegotiationTest.java
  20. +159
    -0
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoStoreTest.java
  21. +1
    -0
      smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/package-info.java
  22. +1
    -0
      smack-omemo-signal-integration-test/.gitignore
  23. +17
    -0
      smack-omemo-signal-integration-test/build.gradle
  24. +52
    -0
      smack-omemo-signal-integration-test/src/main/java/org/igniterealtime/smack/inttest/smack_omemo_signal/SmackOmemoSignalIntegrationTestFramework.java
  25. +25
    -0
      smack-omemo-signal-integration-test/src/main/java/org/igniterealtime/smack/inttest/smack_omemo_signal/package-info.java
  26. +677
    -0
      smack-omemo-signal/LICENSE
  27. +16
    -0
      smack-omemo-signal/build.gradle
  28. +58
    -0
      smack-omemo-signal/src/main/java/org/jivesoftware/smackx/omemo/signal/SignalFileBasedOmemoStore.java
  29. +226
    -0
      smack-omemo-signal/src/main/java/org/jivesoftware/smackx/omemo/signal/SignalOmemoKeyUtil.java
  30. +111
    -0
      smack-omemo-signal/src/main/java/org/jivesoftware/smackx/omemo/signal/SignalOmemoService.java
  31. +141
    -0
      smack-omemo-signal/src/main/java/org/jivesoftware/smackx/omemo/signal/SignalOmemoSession.java
  32. +50
    -0
      smack-omemo-signal/src/main/java/org/jivesoftware/smackx/omemo/signal/SignalOmemoStore.java
  33. +228
    -0
      smack-omemo-signal/src/main/java/org/jivesoftware/smackx/omemo/signal/SignalOmemoStoreConnector.java
  34. +26
    -0
      smack-omemo-signal/src/main/java/org/jivesoftware/smackx/omemo/signal/package-info.java
  35. +105
    -0
      smack-omemo-signal/src/test/java/org/jivesoftware/smack/omemo/OmemoManagerTest.java
  36. +88
    -0
      smack-omemo-signal/src/test/java/org/jivesoftware/smack/omemo/OmemoMessageBuilderTest.java
  37. +217
    -0
      smack-omemo-signal/src/test/java/org/jivesoftware/smack/omemo/SignalFileBasedOmemoStoreTest.java
  38. +165
    -0
      smack-omemo-signal/src/test/java/org/jivesoftware/smack/omemo/SignalOmemoKeyUtilTest.java
  39. +45
    -0
      smack-omemo-signal/src/test/java/org/jivesoftware/smack/omemo/SignalOmemoStoreConnectorTest.java
  40. +14
    -0
      smack-omemo/build.gradle
  41. +939
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/FileBasedOmemoStore.java
  42. +166
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoConfiguration.java
  43. +64
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoFingerprint.java
  44. +38
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoInitializer.java
  45. +887
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java
  46. +1379
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java
  47. +789
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoStore.java
  48. +56
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/element/OmemoBundleElement.java
  49. +207
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/element/OmemoBundleVAxolotlElement.java
  50. +81
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/element/OmemoDeviceListElement.java
  51. +39
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/element/OmemoDeviceListVAxolotlElement.java
  52. +194
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/element/OmemoElement.java
  53. +86
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/element/OmemoVAxolotlElement.java
  54. +23
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/element/package-info.java
  55. +92
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/exceptions/CannotEstablishOmemoSessionException.java
  56. +30
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/exceptions/CorruptedOmemoKeyException.java
  57. +35
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/exceptions/CryptoFailedException.java
  58. +55
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/exceptions/MultipleCryptoFailedException.java
  59. +24
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/exceptions/NoOmemoSupportException.java
  60. +31
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/exceptions/NoRawSessionException.java
  61. +54
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/exceptions/UndecidedOmemoIdentityException.java
  62. +23
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/exceptions/package-info.java
  63. +124
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/CachedDeviceList.java
  64. +84
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/CipherAndAuthTag.java
  65. +67
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/CiphertextTuple.java
  66. +63
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/ClearTextMessage.java
  67. +36
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/IdentityKeyWrapper.java
  68. +77
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/OmemoDevice.java
  69. +141
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/OmemoMessageInformation.java
  70. +264
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/OmemoSession.java
  71. +23
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/package-info.java
  72. +48
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/listener/OmemoMessageListener.java
  73. +54
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/listener/OmemoMucMessageListener.java
  74. +23
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/listener/package-info.java
  75. +26
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/package-info.java
  76. +99
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/provider/OmemoBundleVAxolotlProvider.java
  77. +66
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/provider/OmemoDeviceListVAxolotlProvider.java
  78. +95
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/provider/OmemoVAxolotlProvider.java
  79. +23
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/provider/package-info.java
  80. +63
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/util/OmemoConstants.java
  81. +450
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/util/OmemoKeyUtil.java
  82. +252
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/util/OmemoMessageBuilder.java
  83. +23
    -0
      smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/util/package-info.java
  84. +19
    -0
      smack-omemo/src/main/resources/org.jivesoftware.smackx.omemo/omemo.providers
  85. +5
    -0
      smack-omemo/src/main/resources/org.jivesoftware.smackx.omemo/omemo.xml
  86. +70
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/DeviceListTest.java
  87. +96
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoBundleVAxolotlElementTest.java
  88. +112
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoConfigurationTest.java
  89. +56
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoDeviceListVAxolotlElementTest.java
  90. +62
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoDeviceTest.java
  91. +104
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoExceptionsTest.java
  92. +38
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoFingerprintTest.java
  93. +46
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoKeyUtilTest.java
  94. +73
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/OmemoVAxolotlElementTest.java
  95. +105
    -0
      smack-omemo/src/test/java/org/jivesoftware/smack/omemo/WrapperObjectsTest.java

+ 1
- 0
.gitignore View File

@@ -18,6 +18,7 @@ core/build/
debug/build/
experimental/build/
extensions/build/
out/

bin/
core/bin


+ 70
- 11
build.gradle View File

@@ -40,7 +40,11 @@ allprojects {
// build, causing unnecessary rebuilds.
builtDate = (new java.text.SimpleDateFormat("yyyy-MM-dd")).format(new Date())
oneLineDesc = 'An Open Source XMPP (Jabber) client library'
javadocAllProjects = subprojects - project(':smack-integration-test')
integrationTestProjects = [
':smack-integration-test',
':smack-omemo-signal-integration-test',
].collect{ project(it) }
javadocAllProjects = subprojects - integrationTestProjects
// A dirty hack used for Gradle's jacoco plugin, since is not
// hable to handle the case when a (sub)project has no unit
// tests. :-(
@@ -56,6 +60,7 @@ allprojects {
':smack-resolver-dnsjava',
':smack-resolver-javax',
':smack-resolver-minidns',
':smack-omemo-signal-integration-test',
].collect{ project(it) }
projectsWithUnitTests = subprojects - projectsWithoutUnitTests
androidProjects = [
@@ -67,11 +72,17 @@ allprojects {
':smack-sasl-provided',
':smack-extensions',
':smack-experimental',
':smack-omemo',
':smack-omemo-signal',
].collect{ project(it) }
androidBootClasspathProjects = [
':smack-android',
':smack-android-extensions',
].collect{ project(it) }
gplLicensedProjects = [
':smack-omemo-signal',
':smack-omemo-signal-integration-test',
].collect{ project(it) }
androidBootClasspath = getAndroidRuntimeJar()
androidJavadocOffline = getAndroidJavadocOffline()
junitVersion = '4.11'
@@ -350,14 +361,6 @@ subprojects {
developerConnection 'scm:git:https://github.com/igniterealtime/Smack.git'
}

licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution 'repo'
}
}

developers {
developer {
id 'flow'
@@ -387,8 +390,17 @@ subprojects {
}
}

// No need to ever clirr smack-integration-test
project(':smack-integration-test').clirr.enabled = false
// There is no need to ever clirr integration test projects and the
// smack-repl project.
configure(integrationTestProjects + project(':smack-repl')) {
clirr {
enabled false
}
}

// Disable clirr on omemo modules
project(':smack-omemo').clirr.enabled = false
project(':smack-omemo-signal').clirr.enabled = false

subprojects*.jar {
manifest {
@@ -396,6 +408,49 @@ subprojects*.jar {
}
}

configure(subprojects - gplLicensedProjects) {
checkstyle {
configProperties.checkstyleLicenseHeader = "header"
}
uploadArchives {
repositories {
mavenDeployer {
pom.project {
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution 'repo'
}
}
}
}
}
}
}

configure(gplLicensedProjects) {
checkstyle {
configProperties.checkstyleLicenseHeader = "${project.name}-gplv3-license-header"
}
uploadArchives {
repositories {
mavenDeployer {
pom.project {
licenses {
license {
name 'GNU General Public License, version 3 or any later version'
url 'https://www.gnu.org/licenses/gpl.txt'
distribution 'repo'
}
}
}
}
}
}

}

configure(androidBootClasspathProjects) {
compileJava {
options.bootClasspath = androidBootClasspath
@@ -436,6 +491,10 @@ task integrationTest {
dependsOn project(':smack-integration-test').tasks.run
}

task omemoSignalIntTest {
dependsOn project(':smack-omemo-signal-integration-test').tasks.run
}

def getGitCommit() {
def dotGit = new File("$projectDir/.git")
if (!dotGit.isDirectory()) return 'non-git build'


+ 1
- 1
config/checkstyle.xml View File

@@ -8,7 +8,7 @@
</module>
<module name="SuppressionCommentFilter"/>
<module name="Header">
<property name="headerFile" value="config/header.txt"/>
<property name="headerFile" value="config/${checkstyleLicenseHeader}.txt"/>
<property name="ignoreLines" value="3"/>
<property name="fileExtensions" value="java"/>
</module>


+ 20
- 0
config/smack-omemo-signal-gplv3-license-header.txt View File

@@ -0,0 +1,20 @@
/**
*
* Copyright 20XX John Doe
*
* 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
*/

+ 20
- 0
config/smack-omemo-signal-integration-test-gplv3-license-header.txt View File

@@ -0,0 +1,20 @@
/**
*
* Copyright 20XX John Doe
*
* This file is part of smack-omemo-signal-integration-test.
*
* smack-omemo-signal-integration-test 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
*/

+ 2
- 1
documentation/extensions/index.md View File

@@ -91,9 +91,10 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| JSON Containers | [XEP-0335](http://xmpp.org/extensions/xep-0335.html) | Encapsulation of JSON data within XMPP Stanzas. |
| [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. |
| Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. |
| [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. |
| [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. |
| HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
| [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. |
| [OMEMO End Encryption (omemo.md) | [XEP-0384](http://xmpp.org/extensions/xep-0384.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |
| Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers |




+ 217
- 0
documentation/extensions/omemo.md View File

@@ -0,0 +1,217 @@
Encrypting messages with OMEMO
==============================

[Back](index.md)

OMEMO ([XEP-0384](https://xmpp.org/extensions/xep-0384.html)) is an adaption
of the Signal protocol for XMPP. It provides an important set of
cryptographic properties including but not restricted to

* Confidentiality
* Integrity
* Authenticity
* Forward secrecy
* Future secrecy (break-in recovery)
* Plausible deniability

Contrary to OTR it is capable of multi-end-to-multi-end encryption and
message synchronization across multiple devices. It also allows the sender
to send a message while the recipient is offline.

It does NOT provide a server side message archive, so that a new device could
fetch old chat history.

Most implementations of OMEMO use the signal-protocol libraries provided by
OpenWhisperSystems. Unlike Smack, those libraries are licensed under the GPL,
which prevents a Apache licensed OMEMO implementation using those libraries (see
[licensing situation](https://github.com/igniterealtime/Smack/wiki/OMEMO-libsignal-Licensing-Situation)).
The module smack-omemo therefore contains no code related to signal-protocol.
However, almost all functionality is encapsulated in that module. If you want
to use OMEMO in a GPL client, you can use the smack-omemo-signal
Smack module, which binds the signal-protocol library to smack-omemo.
It is also possible, to port smack-omemo to other libraries implementing the
double ratchet algorithm.

Requirements
------------

In order to use OMEMO encryption, your server and the servers of your chat
partners must support PEP ([XEP-0163](http://xmpp.org/extensions/xep-0163.html))
to store and exchange key bundles.
Optionally your server should support Message Carbons ([XEP-0280](http://xmpp.org/extensions/xep-0280.html))
and Message Archive Management ([XEP-0313](http://xmpp.org/extensions/xep-0313.html))
to achieve message synchronization across all (on- and offline) devices.

Setup
-----

First you need to setup a OmemoService, for example the libsignal one:

```
SignalOmemoService.setup();
```

As a first step you have to prepare the OmemoStore.
You can either use your own implementation, or use the builtin FileBasedOmemoStore (default).
If you do not want to use your own store, the implementation uses a file based store, so you HAVE to set the default path.

```
//set path in case we want to use a file-based store (default)
OmemoConfiguration.setFileBasedOmemoStoreDefaultPath(new File("path/to/your/store"));
```

For each device you need an OmemoManager.
In this example, we use the smack-omemo-signal
implementation, so we use the SignalOmemoService as
OmemoService. The OmemoManager must be initialized with either a deviceId (of an existing
device), or null in case you want to generate a fresh device.
The OmemoManager can be used to execute OMEMO related actions like sending a
message etc. If you don't pass a deviceId, the value of defaultDeviceId will be used if present.

```
OmemoManager omemoManager = OmemoManager.getInstanceFor(connection);
```

As soon as the connection is authenticated, the module generates some keys and
announces OMEMO support.
To get updated with new OMEMO messages, you should register message listeners.

```
omemoManager.addOmemoMessageListener(new OmemoMessageListener() {
@Overwrite
public void omOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
System.out.println(decryptedBody);
}
});

omemoManager.addOmemoMucMessageListener(new OmemoMucMessageListener() {
@Overwrite
public void onOmemoMucMessageReceived(MultiUserChat muc, BareJid from, String decryptedBody, Message message,
Message wrappingMessage, OmemoMessageInformation omemoInformation) {
System.out.println(decryptedBody);
}
});
```

Usage
-----

Before you can encrypt a message for a device, you have to trust its identity.
smack-omemo will throw an UndecidedOmemoIdentityException whenever you try
to send a message to a device, which the user has not yet decided to trust or distrust.

```
omemoManager.trustOmemoIdentity(trustedDevice, trustedFingerprint);
omemoManager.distrustOmemoIdentity(untrustedDevice, untrustedFingerprint);
```

The trust decision should be made by the user based on comparing fingerprints.
You can get fingerprints of your own and contacts devices:

```
OmemoFingerprint myFingerprint = omemoManager.getFingerprint();
OmemoFingerprint otherFingerprint = omemoStore.getFingerprint(omemoManager, otherDevice);
```

To encrypt a message for a single contact or a MUC, you do as follows:

```
Message encryptedSingleMessage = omemoManager.encrypt(bobsBareJid, "Hi Bob!");

Message encryptedMucMessage = omemoManager.encrypt(multiUserChat, "Hi everybody!");
```

Note: It may happen, that smack-omemo is unable to create a session with a device.
In case we could not create a single valid session for a recipient, a
CannotCreateOmemoSessionException will be thrown. This exception contains information
about which sessions could (not) be created and why. If you want to ignore those devices,
you can encrypt the message for all remaining devices like this:

```
Message encryptedMessage = omemoManager.encryptForExistingSession(cannotEstablishSessionException, "Hi there!");
```

The resulting message can then be sent via the ChatManager/MultiUserChatManager.

You may want to generate a new identity sometime in the future. That's pretty straight
forward. No need to manually publish bundles etc.

```
omemoManager.regenerate();
```

In case your device list gets filled with old unused identities, you can clean it up.
This will remove all active devices from the device list and only publish the device
you are using right now.

```
omemoManager.purgeDevices();
```

If you want to find out, whether a server, MUC or contacts resource supports OMEMO,
you can use the following methods:

```
boolean serverCan = omemoManager.serverSupportsOmemo(serverJid);
boolean mucCan = omemoManager.multiUserChatSupportsOmemo(mucJid);
boolean resourceCan = omemoManager.resourceSupportsOmemo(contactsResourceJid);
```

It might happen, that the server you or your contact are using is not delivering devicelist updates correctly.
In such a case smack-omemo cannot fetch bundles or send messages to devices it hasn\'t seen before. To mitigate this, it
might help to explicitly request the latest device list from the server.
```
omemoManager.requestDeviceListUpdateFor(contactJid);
```

If you want to decrypt a MamQueryResult, you can do so using the following method:
````
List<ClearTextMessage> decryptedMamQuery = omemoManager.decryptMamQueryResult(mamQueryResult);
````
Note, that you cannot decrypt an OMEMO encrypted message twice for reasons of forward secrecy.
A ClearTextMessage contains the decrypted body of the message, as well as additional information like if/how the message was encrypted in the first place.
Unfortunately due to the fact that you cannot decrypt messages twice, you have to keep track of the message history locally on the device and ideally also keep track of the last received message, so you can query the server only for messages newer than that.


Configuration
-------------
smack-omemo has some configuration options that can be changed on runtime via the `OmemoConfiguration` class:

* setFileBasedOmemoStoreDefaultPath sets the default directory for the FileBasedOmemoStore implementations.
* setIgnoreStaleDevices when set to true, smack-omemo will stop encrypting messages for **own** devices that have not send a message for some period of time (configurable in setIgnoreStaleDevicesAfterHours)
* setDeleteStaleDevices when set to true, smack-omemo will remove own devices from the device list, if no messages were received from them for a period of time (configurable in setDeleteStaleDevicesAfterHours)
* setRenewOldSignedPreKeys when set to true, smack-omemo will periodically generate and publish new signed prekeys. Via setRenewOldSignedPreKeysAfterHours you can configure, after what period of time new keys are generated and setMaxNumberOfStoredSignedPreKeys allows configuration of how many signed PreKeys are kept in storage for decryption of delayed messages.
* setAddOmemoBodyHint when set to true, a plaintext body with a hint about OMEMO encryption will be added to the message. This hint will be displayed by clients that do not support OMEMO. Note that this might not be desirable when communicating with clients that do not support EME.
* setAddEmeEncryptionHint when set to true, an Explicit Message Encryption element will be added to the message. This element tells clients, that the message is encrypted with OMEMO.
* setAddMAMStorageProcessingHint when set to true, a storage hint for Message Archive Management will be added to the message. This enabled servers to store messages that contain no body.

Customization
-------------
You can integrate smack-omemo with your existing infrastructure.
It is possible to create your own OmemoStore implementations eg. using an SQL database as backend.
For this purpose, just inherit OmemoStore/SignalOmemoStore and implement the missing methods.
You can register that Store with your OmemoService by calling
```
SignalOmemoService.getInstance().setOmemoStoreBackend(myStore);
```

Features
--------
* decryption and encryption of OMEMO messages (single and multi user chat)
* provides information about trust status of incoming messages
* automatic publishing of bundle
* automatic merging of incoming deviceList updates
* ignores stale devices after period of inactivity
* removes stale devices from device list after period of inactivity
* automatic repair of broken sessions through ratchet update messages
* automatic renewal of signed preKeys
* multiple devices per connection possible

Integration Tests
-----------------
smack-omemo comes with a set of integration tests. Lets say you want to run the integration test suite for smack-omemo-signal.
You can do so by using the following gradle task:

```
gradle omemoSignalIntTest
```

+ 3
- 0
settings.gradle View File

@@ -23,4 +23,7 @@ include 'smack-core',
'smack-android-extensions',
'smack-java7',
'smack-integration-test',
'smack-omemo',
'smack-omemo-signal',
'smack-omemo-signal-integration-test',
'smack-repl'

+ 4
- 2
smack-core/build.gradle View File

@@ -13,8 +13,10 @@ dependencies {
testCompile "org.jxmpp:jxmpp-jid:$jxmppVersion:tests"
testCompile "junit:junit:$junitVersion"
testCompile 'xmlunit:xmlunit:1.5'
testCompile 'org.powermock:powermock-module-junit4:1.5.5'
testCompile 'org.powermock:powermock-api-mockito:1.5.5'
testCompile "org.powermock:powermock-module-junit4:1.6.4"
testCompile "org.powermock:powermock-module-junit4-rule:1.6.4"
testCompile "org.powermock:powermock-api-mockito:1.6.4"
testCompile "org.powermock:powermock-classloading-xstream:1.6.4"
testCompile 'com.jamesmurty.utils:java-xmlbuilder:0.6'
testCompile 'net.iharder:base64:2.3.8'
}


+ 1
- 0
smack-core/src/main/resources/org.jivesoftware.smack/smack-config.xml View File

@@ -20,5 +20,6 @@
<className>org.jivesoftware.smack.android.AndroidSmackInitializer</className>
<className>org.jivesoftware.smack.java7.Java7SmackInitializer</className>
<className>org.jivesoftware.smack.im.SmackImInitializer</className>
<className>org.jivesoftware.smackx.omemo.OmemoInitializer</className>
</optionalStartupClasses>
</smack>

+ 1
- 0
smack-integration-test/build.gradle View File

@@ -11,6 +11,7 @@ dependencies {
compile project(':smack-tcp')
compile project(':smack-extensions')
compile project(':smack-experimental')
compile project(':smack-omemo')
compile 'org.reflections:reflections:0.9.9-RC1'
compile 'eu.geekplace.javapinning:java-pinning-java7:1.1.0-alpha1'
compile "junit:junit:$junitVersion"


+ 27
- 6
smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java View File

@@ -196,10 +196,14 @@ public final class Configuration {
return addTestPackage(enabledTest.getPackage().getName());
}

public Builder addTestPackage(String testPackage) {
private void ensureTestPackagesIsSet(int length) {
if (testPackages == null) {
testPackages = new HashSet<>();
testPackages = new HashSet<>(length);
}
}

public Builder addTestPackage(String testPackage) {
ensureTestPackagesIsSet(4);
testPackages.add(testPackage);
return this;
}
@@ -260,10 +264,12 @@ public final class Configuration {
return this;
}

public Builder setTestPackages(String testPackagesString) {
public Builder addTestPackages(String testPackagesString) {
if (testPackagesString != null) {
String[] testPackagesArray = testPackagesString.split(",");
testPackages = new HashSet<>(testPackagesArray.length);

ensureTestPackagesIsSet(testPackagesArray.length);

for (String s : testPackagesArray) {
testPackages.add(s.trim());
}
@@ -271,6 +277,19 @@ public final class Configuration {
return this;
}

public Builder addTestPackages(String[] testPackagesString) {
if (testPackagesString == null) {
return this;
}

ensureTestPackagesIsSet(testPackagesString.length);

for (String testPackage : testPackagesString) {
testPackages.add(testPackage);
}
return this;
}

public Configuration build() throws KeyManagementException, NoSuchAlgorithmException {
return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername,
accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests,
@@ -280,7 +299,7 @@ public final class Configuration {

private static final String SINTTEST = "sinttest.";

public static Configuration newConfiguration()
public static Configuration newConfiguration(String[] testPackages)
throws IOException, KeyManagementException, NoSuchAlgorithmException {
Properties properties = new Properties();

@@ -330,7 +349,9 @@ public final class Configuration {
builder.setDebug(properties.getProperty("debug"));
builder.setEnabledTests(properties.getProperty("enabledTests"));
builder.setDisabledTests(properties.getProperty("disabledTests"));
builder.setTestPackages(properties.getProperty("testPackages"));

builder.addTestPackages(properties.getProperty("testPackages"));
builder.addTestPackages(testPackages);

return builder.build();
}


+ 1
- 1
smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java View File

@@ -78,7 +78,7 @@ public class SmackIntegrationTestFramework {

public static void main(String[] args) throws IOException, KeyManagementException,
NoSuchAlgorithmException, SmackException, XMPPException, InterruptedException {
Configuration config = Configuration.newConfiguration();
Configuration config = Configuration.newConfiguration(args);

SmackIntegrationTestFramework sinttest = new SmackIntegrationTestFramework(config);
TestRunResult testRunResult = sinttest.run();


+ 80
- 0
smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java View File

@@ -0,0 +1,80 @@
/**
*
* Copyright 2017 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo;

import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.junit.AfterClass;
import org.junit.BeforeClass;

import java.io.File;
import java.util.logging.Level;

/**
* Super class for OMEMO integration tests.
*/
public abstract class AbstractOmemoIntegrationTest extends AbstractSmackIntegrationTest {

static final File storePath;

static {
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");
}
}

public AbstractOmemoIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
super(environment);
if (OmemoConfiguration.getFileBasedOmemoStoreDefaultPath() == null) {
OmemoConfiguration.setFileBasedOmemoStoreDefaultPath(storePath);
}
// Test for server support
if (!OmemoManager.serverSupportsOmemo(connection, connection.getXMPPServiceDomain())) {
throw new TestNotPossibleException("Server does not support OMEMO (PubSub)");
}

//Check for OmemoService
if (!OmemoService.isServiceRegistered()) {
throw new TestNotPossibleException("No OmemoService registered.");
}
}

@BeforeClass
public void beforeTest() {
LOGGER.log(Level.INFO, "START EXECUTION");
OmemoIntegrationTestHelper.deletePath(storePath);
before();
}

@AfterClass
public void afterTest() {
after();
OmemoIntegrationTestHelper.deletePath(storePath);
LOGGER.log(Level.INFO, "END EXECUTION");
}

public abstract void before();

public abstract void after();
}

+ 77
- 0
smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoInitializationTest.java View File

@@ -0,0 +1,77 @@
/**
*
* Copyright 2017 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo;

import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pubsub.PubSubException;

import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;

public class OmemoInitializationTest extends AbstractOmemoIntegrationTest {

private OmemoManager alice;
private OmemoStore<?,?,?,?,?,?,?,?,?> store;

@Override
public void before() {
alice = OmemoManager.getInstanceFor(conOne, 666);
store = OmemoService.getInstance().getOmemoStoreBackend();
}

public OmemoInitializationTest(SmackIntegrationTestEnvironment environment) throws TestNotPossibleException, XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
super(environment);
}

/**
* Tests, if the initialization is done properly.
*/
@SmackIntegrationTest
public void initializationTest() throws XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException, CorruptedOmemoKeyException {
//test keys.
setUpOmemoManager(alice);
assertNotNull("IdentityKey must not be null after initialization.", store.loadOmemoIdentityKeyPair(alice));
assertTrue("We must have " + OmemoConstants.TARGET_PRE_KEY_COUNT + " preKeys.",
store.loadOmemoPreKeys(alice).size() == OmemoConstants.TARGET_PRE_KEY_COUNT);
assertNotNull("Our signedPreKey must not be null.", store.loadCurrentSignedPreKeyId(alice));

//Is deviceId published?
assertTrue("Published deviceList must contain our deviceId.",
OmemoService.fetchDeviceList(alice, alice.getOwnJid())
.getDeviceIds().contains(alice.getDeviceId()));

assertTrue("Our fingerprint must be of correct length.",
OmemoService.getInstance().getOmemoStoreBackend().getFingerprint(alice).length() == 64);
}

@Override
public void after() {
alice.shutdown();
cleanServerSideTraces(alice);
}
}

+ 155
- 0
smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoIntegrationTestHelper.java View File

@@ -0,0 +1,155 @@
/**
*
* Copyright 2017 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo;

import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smack.roster.RosterEntry;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pubsub.PubSubAssertionError;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubManager;

import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;

/**
* Class containing some helper methods for OmemoIntegrationTests.
*/
final class OmemoIntegrationTestHelper {

private static final Logger LOGGER = Logger.getLogger(OmemoIntegrationTestHelper.class.getSimpleName());

static void cleanServerSideTraces(OmemoManager omemoManager) {
cleanUpPubSub(omemoManager);
cleanUpRoster(omemoManager);
}

static void deletePath(File storePath) {
FileBasedOmemoStore.deleteDirectory(storePath);
}

static void deletePath(OmemoManager omemoManager) {
OmemoService.getInstance().getOmemoStoreBackend().purgeOwnDeviceKeys(omemoManager);
}

static void cleanUpPubSub(OmemoManager omemoManager) {
PubSubManager pm = PubSubManager.getInstance(omemoManager.getConnection(),omemoManager.getOwnJid());
try {
omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid());
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException e) {
//ignore
}

CachedDeviceList deviceList = OmemoService.getInstance().getOmemoStoreBackend().loadCachedDeviceList(omemoManager, omemoManager.getOwnJid());
for (int id : deviceList.getAllDevices()) {
try {
pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems();
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | PubSubAssertionError.DiscoInfoNodeAssertionError e) {
//Silent
}

try {
pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id));
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | PubSubAssertionError e) {
//Silent
}
}

try {
pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems();
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | PubSubAssertionError.DiscoInfoNodeAssertionError e) {
//Silent
}

try {
pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST);
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | PubSubAssertionError e) {
//Silent
}
}

static void cleanUpRoster(OmemoManager omemoManager) {
Roster roster = Roster.getInstanceFor(omemoManager.getConnection());
for (RosterEntry r : roster.getEntries()) {
try {
roster.removeEntry(r);
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | SmackException.NotLoggedInException e) {
//Silent
}
}
}

/**
* Let Alice subscribe to Bob.
* @param alice
* @param bob
* @throws SmackException.NotLoggedInException
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
static void subscribe(OmemoManager alice, OmemoManager bob, String nick)
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {

Roster aliceRoster = Roster.getInstanceFor(alice.getConnection());
Roster bobsRoster = Roster.getInstanceFor(bob.getConnection());
bobsRoster.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
aliceRoster.createEntry(bob.getOwnJid(), nick, null);
}


static void unidirectionalTrust(OmemoManager alice, OmemoManager bob) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException {
//Fetch deviceList
alice.requestDeviceListUpdateFor(bob.getOwnJid());
LOGGER.log(Level.INFO, "Current deviceList state: " + alice.getOwnDevice() + " knows " + bob.getOwnDevice() + ": "
+ OmemoService.getInstance().getOmemoStoreBackend().loadCachedDeviceList(alice, bob.getOwnJid()));
assertTrue("Trusting party must know the others device at this point.",
alice.getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(alice, bob.getOwnJid())
.getActiveDevices().contains(bob.getDeviceId()));

//Create sessions
alice.buildSessionsWith(bob.getOwnJid());
assertTrue("Trusting party must have a session with the other end at this point.",
!alice.getOmemoService().getOmemoStoreBackend().loadAllRawSessionsOf(alice, bob.getOwnJid()).isEmpty());

//Trust the other party
alice.getOmemoService().getOmemoStoreBackend().trustOmemoIdentity(alice, bob.getOwnDevice(),
alice.getOmemoService().getOmemoStoreBackend().getFingerprint(alice, bob.getOwnDevice()));

}

static void setUpOmemoManager(OmemoManager omemoManager) throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException {
omemoManager.initialize();
OmemoBundleElement bundle = OmemoService.fetchBundle(omemoManager, omemoManager.getOwnDevice());
assertNotNull("Bundle must not be null.", bundle);
assertEquals("Published Bundle must equal our local bundle.", bundle, omemoManager.getOmemoService().getOmemoStoreBackend().packOmemoBundle(omemoManager));
}
}

+ 112
- 0
smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoKeyTransportTest.java View File

@@ -0,0 +1,112 @@
/**
*
* Copyright 2017 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo;

import junit.framework.TestCase;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;

import java.util.Arrays;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;

import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.subscribe;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.unidirectionalTrust;
import static org.junit.Assert.assertTrue;

/**
* Test keyTransportMessages.
*/
public class OmemoKeyTransportTest extends AbstractOmemoIntegrationTest {

private OmemoManager alice, bob;

public OmemoKeyTransportTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
super(environment);
}

@Override
public void before() {
alice = OmemoManager.getInstanceFor(conOne, 11111);
bob = OmemoManager.getInstanceFor(conTwo, 222222);
}

@SmackIntegrationTest
public void keyTransportTest() throws Exception {
final SimpleResultSyncPoint syncPoint = new SimpleResultSyncPoint();

subscribe(alice, bob, "Bob");
subscribe(bob, alice, "Alice");

setUpOmemoManager(alice);
setUpOmemoManager(bob);

unidirectionalTrust(alice, bob);
unidirectionalTrust(bob, alice);

final byte[] key = OmemoMessageBuilder.generateKey();
final byte[] iv = OmemoMessageBuilder.generateIv();

bob.addOmemoMessageListener(new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
//Don't care
}

@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
LOGGER.log(Level.INFO, "Received a keyTransportMessage.");
assertTrue("Key must match the one we sent.", Arrays.equals(key, cipherAndAuthTag.getKey()));
assertTrue("IV must match the one we sent.", Arrays.equals(iv, cipherAndAuthTag.getIv()));
syncPoint.signal();
}
});

OmemoElement keyTransportElement = alice.createKeyTransportElement(key, iv, bob.getOwnDevice());
Message message = new Message(bob.getOwnJid());
message.addExtension(keyTransportElement);
ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
.send(message);

try {
syncPoint.waitForResult(10 * 1000);
} catch (TimeoutException e) {
TestCase.fail("We MUST have received the keyTransportMessage within 10 seconds.");
}
}

@Override
public void after() {
alice.shutdown();
bob.shutdown();
cleanServerSideTraces(alice);
cleanServerSideTraces(bob);
}
}

+ 184
- 0
smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessageSendingTest.java View File

@@ -0,0 +1,184 @@
/**
*
* Copyright 2017 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo;

import junit.framework.TestCase;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.jivesoftware.smackx.pubsub.PubSubException;

import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotSame;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.subscribe;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.unidirectionalTrust;

/**
* Test message sending.
*/
public class OmemoMessageSendingTest extends AbstractOmemoIntegrationTest {

private OmemoManager alice, bob;
private OmemoStore<?,?,?,?,?,?,?,?,?> store;

public OmemoMessageSendingTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
super(environment);
}

@Override
public void before() {
alice = OmemoManager.getInstanceFor(conOne, 123);
bob = OmemoManager.getInstanceFor(conTwo, 345);
store = OmemoService.getInstance().getOmemoStoreBackend();
}

/**
* This Test tests sending and receiving messages.
* Alice and Bob create fresh devices, then they add another to their rosters.
* Next they build sessions with one another and Alice sends a message to Bob.
* After receiving and successfully decrypting the message, its tested, if Bob
* publishes a new Bundle. After that Bob replies to the message and its tested,
* whether Alice can decrypt the message and if she does NOT publish a new Bundle.
*
* @throws CorruptedOmemoKeyException
* @throws InterruptedException
* @throws SmackException.NoResponseException
* @throws SmackException.NotConnectedException
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotLoggedInException
* @throws PubSubException.NotALeafNodeException
* @throws CannotEstablishOmemoSessionException
* @throws UndecidedOmemoIdentityException
* @throws NoSuchAlgorithmException
* @throws CryptoFailedException
*/
@SmackIntegrationTest
public void messageSendingTest() throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, CannotEstablishOmemoSessionException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException {
final String alicesSecret = "Hey Bob! I love you!";
final String bobsSecret = "I love you too, Alice."; //aww <3

final SimpleResultSyncPoint messageOneSyncPoint = new SimpleResultSyncPoint();
final SimpleResultSyncPoint messageTwoSyncPoint = new SimpleResultSyncPoint();

//Subscribe to one another
subscribe(alice, bob, "Bob");
subscribe(bob, alice,"Alice");

//initialize OmemoManagers
setUpOmemoManager(alice);
setUpOmemoManager(bob);

//Save initial bundles
OmemoBundleElement aliceBundle = store.packOmemoBundle(alice);
OmemoBundleElement bobsBundle = store.packOmemoBundle(bob);

//Trust
unidirectionalTrust(alice, bob);
unidirectionalTrust(bob, alice);

//Register messageListeners
bob.addOmemoMessageListener(new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
LOGGER.log(Level.INFO,"Bob received message: " + decryptedBody);
if (decryptedBody.trim().equals(alicesSecret.trim())) {
messageOneSyncPoint.signal();
} else {
messageOneSyncPoint.signal(new Exception("Received message must equal sent message."));
}
}

@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
}
});

alice.addOmemoMessageListener(new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
LOGGER.log(Level.INFO, "Alice received message: " + decryptedBody);
if (decryptedBody.trim().equals(bobsSecret.trim())) {
messageTwoSyncPoint.signal();
} else {
messageTwoSyncPoint.signal(new Exception("Received message must equal sent message."));
}
}

@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {

}
});

//Prepare Alice message for Bob
Message encryptedA = alice.encrypt(bob.getOwnJid(), alicesSecret);
ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
.send(encryptedA);

try {
messageOneSyncPoint.waitForResult(10 * 1000);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Exception while waiting for message: " + e, e);
TestCase.fail("Bob must have received Alice message.");
}

//Check if Bob published a new Bundle
assertNotSame("Bob must have published another bundle at this point, since we used a PreKeyMessage.",
bobsBundle, OmemoService.fetchBundle(alice, bob.getOwnDevice()));

//Prepare Bobs response
Message encryptedB = bob.encrypt(alice.getOwnJid(), bobsSecret);
ChatManager.getInstanceFor(bob.getConnection()).chatWith(alice.getOwnJid().asEntityBareJidIfPossible())
.send(encryptedB);

try {
messageTwoSyncPoint.waitForResult(10 * 1000);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Exception while waiting for response: " + e, e);
TestCase.fail("Alice must have received a response from Bob.");
}

assertEquals("Alice must not have published a new bundle, since we built the session using Bobs bundle.",
aliceBundle, OmemoService.fetchBundle(bob, alice.getOwnDevice()));
}

@Override
public void after() {
alice.shutdown();
bob.shutdown();
cleanServerSideTraces(alice);
cleanServerSideTraces(bob);
}
}

+ 193
- 0
smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoSessionRenegotiationTest.java View File

@@ -0,0 +1,193 @@
/**
*
* Copyright 2017 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo;

import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;

import java.util.logging.Level;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.fail;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.subscribe;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.unidirectionalTrust;

/**
* Test session renegotiation.
*/
public class OmemoSessionRenegotiationTest extends AbstractOmemoIntegrationTest {

private OmemoManager alice, bob;
private OmemoStore<?,?,?,?,?,?,?,?,?> store;

public OmemoSessionRenegotiationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
super(environment);
}

@Override
public void before() {
alice = OmemoManager.getInstanceFor(conOne, 1337);
bob = OmemoManager.getInstanceFor(conTwo, 1009);
store = OmemoService.getInstance().getOmemoStoreBackend();
}

@SmackIntegrationTest
public void sessionRenegotiationTest() throws Exception {

final boolean[] phaseTwo = new boolean[1];
final SimpleResultSyncPoint sp1 = new SimpleResultSyncPoint();
final SimpleResultSyncPoint sp2 = new SimpleResultSyncPoint();
final SimpleResultSyncPoint sp3 = new SimpleResultSyncPoint();
final SimpleResultSyncPoint sp4 = new SimpleResultSyncPoint();

final String m1 = "1: Alice says hello to bob.";
final String m2 = "2: Bob replies to Alice.";
final String m3 = "3. This message will arrive but Bob cannot decrypt it.";
final String m4 = "4. This message is readable by Bob again.";

subscribe(alice, bob, "Bob");
subscribe(bob, alice, "Alice");

setUpOmemoManager(alice);
setUpOmemoManager(bob);

unidirectionalTrust(alice, bob);
unidirectionalTrust(bob, alice);

OmemoMessageListener first = new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
LOGGER.log(Level.INFO, "Bob received OMEMO message: " + decryptedBody);
assertEquals("Received message MUST match the one we sent.", decryptedBody, m1);
sp1.signal();
}

@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {

}
};
bob.addOmemoMessageListener(first);

ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
.send(alice.encrypt(bob.getOwnJid(), m1));

sp1.waitForResult(10 * 1000);

bob.removeOmemoMessageListener(first);

OmemoMessageListener second = new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
LOGGER.log(Level.INFO, "Alice received OMEMO message: " + decryptedBody);
assertEquals("Reply must match the messagewe sent.", decryptedBody, m2);
sp2.signal();
}

@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {

}
};
alice.addOmemoMessageListener(second);

ChatManager.getInstanceFor(bob.getConnection()).chatWith(alice.getOwnJid().asEntityBareJidIfPossible())
.send(bob.encrypt(alice.getOwnJid(), m2));

sp2.waitForResult(10 * 1000);

alice.removeOmemoMessageListener(second);

store.forgetOmemoSessions(bob);
store.removeAllRawSessionsOf(bob, alice.getOwnJid());

OmemoMessageListener third = new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
fail("Bob should not have received a decipherable message: " + decryptedBody);
}

@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {

}
};
bob.addOmemoMessageListener(third);

OmemoMessageListener fourth = new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {

}

@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
LOGGER.log(Level.INFO, "Alice received preKeyMessage.");
sp3.signal();
}
};
alice.addOmemoMessageListener(fourth);

ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
.send(alice.encrypt(bob.getOwnJid(), m3));

sp3.waitForResult(10 * 1000);

bob.removeOmemoMessageListener(third);
alice.removeOmemoMessageListener(fourth);

OmemoMessageListener fifth = new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
LOGGER.log(Level.INFO, "Bob received an OMEMO message: " + decryptedBody);
assertEquals("The received message must match the one we sent.",
decryptedBody, m4);
sp4.signal();
}

@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {

}
};
bob.addOmemoMessageListener(fifth);

ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
.send(alice.encrypt(bob.getOwnJid(), m4));

sp4.waitForResult(10 * 1000);
}

@Override
public void after() {
alice.shutdown();
bob.shutdown();
cleanServerSideTraces(alice);
cleanServerSideTraces(bob);
}
}

+ 159
- 0
smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoStoreTest.java View File

@@ -0,0 +1,159 @@
/**
*
* Copyright 2017 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo;

import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;

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.assertNotSame;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.deletePath;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;

/**
* Test the OmemoStore.
*/
public class OmemoStoreTest extends AbstractOmemoIntegrationTest {

private OmemoManager alice;
private OmemoManager bob;

public OmemoStoreTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
super(environment);
}

@Override
public void before() {
alice = OmemoManager.getInstanceFor(conOne);
bob = OmemoManager.getInstanceFor(conOne);
}

@SmackIntegrationTest
public void storeTest() throws Exception {

//########### PRE-INITIALIZATION ############

assertEquals("Creating an OmemoManager without MUST have set the default deviceId.", alice.getDeviceId(), OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(alice.getOwnJid()));
assertEquals("OmemoManager must be equal, since both got created without giving a deviceId.", alice, bob);
OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(alice.getOwnJid(), -1); //Reset default deviceId

alice.shutdown();

alice = OmemoManager.getInstanceFor(conOne);
assertNotSame("Instantiating OmemoManager without deviceId MUST assign random deviceId.", alice.getDeviceId(), bob.getDeviceId());

OmemoStore<?,?,?,?,?,?,?,?,?> store = OmemoService.getInstance().getOmemoStoreBackend();
OmemoFingerprint finger = new OmemoFingerprint("FINGER");
//DefaultDeviceId
store.setDefaultDeviceId(alice.getOwnJid(), 777);
assertEquals("defaultDeviceId setting/getting must equal.", 777, store.getDefaultDeviceId(alice.getOwnJid()));

//Trust/Distrust/Decide
bob.shutdown();
bob = OmemoManager.getInstanceFor(conTwo, 998);
assertFalse("Bobs device MUST be undecided at this point",
store.isDecidedOmemoIdentity(alice, bob.getOwnDevice(), finger));
assertFalse("Bobs device MUST not be trusted at this point",
store.isTrustedOmemoIdentity(alice, bob.getOwnDevice(), finger));
store.trustOmemoIdentity(alice, bob.getOwnDevice(), finger);
assertTrue("Bobs device MUST be trusted at this point.",
store.isTrustedOmemoIdentity(alice, bob.getOwnDevice(), finger));
assertTrue("Bobs device MUST be decided at this point.",
store.isDecidedOmemoIdentity(alice, bob.getOwnDevice(), finger));
store.distrustOmemoIdentity(alice, bob.getOwnDevice(), finger);
assertFalse("Bobs device MUST be untrusted at this point.",
store.isTrustedOmemoIdentity(alice, bob.getOwnDevice(), finger));

//Dates
assertNull("Date of last received message must be null when no message was received ever.",
store.getDateOfLastReceivedMessage(alice, bob.getOwnDevice()));
Date now = new Date();
store.setDateOfLastReceivedMessage(alice, bob.getOwnDevice(), now);
assertEquals("Date of last reveived message must match the one we set.",
now, store.getDateOfLastReceivedMessage(alice, bob.getOwnDevice()));
assertNull("Date of last signed prekey renewal must be null.",
store.getDateOfLastSignedPreKeyRenewal(alice));
store.setDateOfLastSignedPreKeyRenewal(alice, now);
assertEquals("Date of last signed prekey renewal must match our date.",
now, store.getDateOfLastSignedPreKeyRenewal(alice));

//Keys
assertNull("IdentityKeyPair must be null at this point.",
store.loadOmemoIdentityKeyPair(alice));
assertNull("IdentityKey of contact must be null at this point.",
store.loadOmemoIdentityKey(alice, bob.getOwnDevice()));
assertEquals("PreKeys list must be of length 0 at this point.",
0, store.loadOmemoPreKeys(alice).size());
assertEquals("SignedPreKeys list must be of length 0 at this point.",
0, store.loadOmemoSignedPreKeys(alice).size());

assertNotNull("Generated IdentityKeyPair must not be null.",
store.generateOmemoIdentityKeyPair());
assertEquals("Generated PreKey list must be of correct length.",
100, store.generateOmemoPreKeys(1, 100).size());


//LastPreKeyId
assertEquals("LastPreKeyId must be 0 at this point.",
0, store.loadLastPreKeyId(alice));
store.storeLastPreKeyId(alice, 1234);
Thread.sleep(100);
assertEquals("LastPreKeyId set/get must equal.", 1234, store.loadLastPreKeyId(alice));
store.storeLastPreKeyId(alice, 0);

//CurrentSignedPreKeyId
assertEquals("CurrentSignedPreKeyId must be 0 at this point.",
0, store.loadCurrentSignedPreKeyId(alice));
store.storeCurrentSignedPreKeyId(alice, 554);
Thread.sleep(100);
assertEquals("CurrentSignedPreKeyId must match the value we set.",
554, store.loadCurrentSignedPreKeyId(alice));
store.storeCurrentSignedPreKeyId(alice, 0);

deletePath(alice);

//################# POST-INITIALIZATION #################
setUpOmemoManager(alice);

//Keys
assertNotNull("IdentityKeyPair must not be null after initialization",
store.loadOmemoIdentityKeyPair(alice));
assertNotSame("LastPreKeyId must not be 0 after initialization.",
0, store.loadLastPreKeyId(alice));
assertNotSame("currentSignedPreKeyId must not be 0 after initialization.",
0, store.loadCurrentSignedPreKeyId(alice));
assertNotNull("The last PreKey must not be null.",
store.loadOmemoPreKey(alice, store.loadLastPreKeyId(alice) - 1));
assertNotNull("The current signedPreKey must not be null.",
store.loadOmemoSignedPreKey(alice, store.loadCurrentSignedPreKeyId(alice)));
}

@Override
public void after() {
alice.shutdown();
bob.shutdown();
}
}

+ 1
- 0
smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/package-info.java View File

@@ -0,0 +1 @@
../../../../../../../../smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/package-info.java

+ 1
- 0
smack-omemo-signal-integration-test/.gitignore View File

@@ -0,0 +1 @@
int_test_omemo_store/

+ 17
- 0
smack-omemo-signal-integration-test/build.gradle View File

@@ -0,0 +1,17 @@
apply plugin: 'application'

description = """\
Smack integration tests for OMEMO using libsignal."""

mainClassName = 'org.igniterealtime.smack.inttest.smack_omemo_signal.SmackOmemoSignalIntegrationTestFramework'
applicationDefaultJvmArgs = ["-enableassertions"]

dependencies {
compile project(':smack-integration-test')
compile project(':smack-omemo-signal')
}

run {
// Pass all system properties down to the "application" run
systemProperties System.getProperties()
}

+ 52
- 0
smack-omemo-signal-integration-test/src/main/java/org/igniterealtime/smack/inttest/smack_omemo_signal/SmackOmemoSignalIntegrationTestFramework.java View File

@@ -0,0 +1,52 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal-integration-test.
*
* smack-omemo-signal-integration-test 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.igniterealtime.smack.inttest.smack_omemo_signal;

import org.igniterealtime.smack.inttest.SmackIntegrationTestFramework;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.signal.SignalOmemoService;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

public class SmackOmemoSignalIntegrationTestFramework {

public static void main(String[] args) throws InvalidKeyException, NoSuchPaddingException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, SmackException,
InterruptedException, CorruptedOmemoKeyException, KeyManagementException, IOException, XMPPException {
SignalOmemoService.acknowledgeLicense();
SignalOmemoService.setup();

final String[] smackOmemoPackages = new String[] { "org.jivesoftware.smackx.omemo" };
SmackIntegrationTestFramework.main(smackOmemoPackages);
}

}

+ 25
- 0
smack-omemo-signal-integration-test/src/main/java/org/igniterealtime/smack/inttest/smack_omemo_signal/package-info.java View File

@@ -0,0 +1,25 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal-integration-test.
*
* smack-omemo-signal-integration-test 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
*/

/**
* The Smack Integration Test Framework for smack-omemo-signal.
*/
package org.igniterealtime.smack.inttest.smack_omemo_signal;

+ 677
- 0
smack-omemo-signal/LICENSE View File

@@ -0,0 +1,677 @@


GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

Preamble

The GNU General Public License is a free, copyleft license for
software and other kinds of works.

The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.

When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.

Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

The precise terms and conditions for copying, distribution and
modification follow.

TERMS AND CONDITIONS

0. Definitions.

"This License" refers to version 3 of the GNU General Public License.

"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.

To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

A "covered work" means either the unmodified Program or a work based
on the Program.

To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

1. Source Code.

The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.

A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

The Corresponding Source for a work in source code form is that
same work.

2. Basic Permissions.

All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.

3. Protecting Users' Legal Rights From Anti-Circumvention Law.

No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

4. Conveying Verbatim Copies.

You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

5. Conveying Modified Source Versions.

You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.

b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".

c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.

d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.

A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

6. Conveying Non-Source Forms.

You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.

b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.

c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.

d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.

e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.

A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial