implemented support for for jingle encrypted transports (XEP-0396)

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/Config.java                            |  4 
src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java       |  2 
src/main/java/eu/siacs/conversations/xml/Namespace.java                     |  2 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java      | 38 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java |  9 
src/main/res/values-hu/strings.xml                                          |  1 
6 files changed, 45 insertions(+), 11 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/Config.java 🔗

@@ -101,8 +101,8 @@ public final class Config {
 
 
     public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
-    public static final boolean USE_DIRECT_JINGLE_CANDIDATES = false;
-    public static final boolean DISABLE_HTTP_UPLOAD = false;
+    public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true;
+    public static final boolean DISABLE_HTTP_UPLOAD = true;
     public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts
     public static final boolean BACKGROUND_STANZA_LOGGING = false; //log all stanzas that were received while the app is in background
     public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption

src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java 🔗

@@ -29,6 +29,8 @@ public abstract class AbstractGenerator {
 			Content.Version.FT_5.getNamespace(),
 			Namespace.JINGLE_TRANSPORTS_S5B,
 			Namespace.JINGLE_TRANSPORTS_IBB,
+			Namespace.JINGLE_ENCRYPTED_TRANSPORT,
+			Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO,
 			"http://jabber.org/protocol/muc",
 			"jabber:x:conference",
 			Namespace.OOB,

src/main/java/eu/siacs/conversations/xml/Namespace.java 🔗

@@ -30,4 +30,6 @@ public final class Namespace {
 	public static final String PING = "urn:xmpp:ping";
 	public static final String PUSH = "urn:xmpp:push:0";
 	public static final String COMMANDS = "http://jabber.org/protocol/commands";
+	public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
+	public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
 }

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java 🔗

@@ -43,6 +43,8 @@ import rocks.xmpp.addr.Jid;
 
 public class JingleConnection implements Transferable {
 
+    private static final String JET_OMEMO_CIPHER = "urn:xmpp:ciphers:aes-128-gcm-nopadding";
+
     private static final int JINGLE_STATUS_INITIATED = 0;
     private static final int JINGLE_STATUS_ACCEPTED = 1;
     private static final int JINGLE_STATUS_FINISHED = 4;
@@ -72,6 +74,7 @@ public class JingleConnection implements Transferable {
     private String contentName;
     private String contentCreator;
     private Transport initialTransport;
+    private boolean remoteSupportsOmemoJet;
 
     private int mProgress = 0;
 
@@ -295,8 +298,10 @@ public class JingleConnection implements Transferable {
         this.contentName = this.mJingleConnectionManager.nextRandomId();
         this.message = message;
         this.account = message.getConversation().getAccount();
-        upgradeNamespace();
-        this.initialTransport = getRemoteFeatures().contains(Namespace.JINGLE_TRANSPORTS_S5B) ? Transport.SOCKS : Transport.IBB;
+        final List<String> remoteFeatures = getRemoteFeatures();
+        upgradeNamespace(remoteFeatures);
+        this.initialTransport = remoteFeatures.contains(Namespace.JINGLE_TRANSPORTS_S5B) ? Transport.SOCKS : Transport.IBB;
+        this.remoteSupportsOmemoJet = remoteFeatures.contains(Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO);
         this.message.setTransferable(this);
         this.mStatus = Transferable.STATUS_UPLOADING;
         this.initiator = this.account.getJid();
@@ -356,11 +361,10 @@ public class JingleConnection implements Transferable {
         }
     }
 
-    private void upgradeNamespace() {
-        List<String> features = getRemoteFeatures();
-        if (features.contains(Content.Version.FT_5.getNamespace())) {
+    private void upgradeNamespace(List<String> remoteFeatures) {
+        if (remoteFeatures.contains(Content.Version.FT_5.getNamespace())) {
             this.ftVersion = Content.Version.FT_5;
-        } else if (features.contains(Content.Version.FT_4.getNamespace())) {
+        } else if (remoteFeatures.contains(Content.Version.FT_4.getNamespace())) {
             this.ftVersion = Content.Version.FT_4;
         }
     }
@@ -430,6 +434,13 @@ public class JingleConnection implements Transferable {
 
         if (fileOffer != null) {
             Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX);
+            if (encrypted == null) {
+                final Element security = content.findChild("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
+                if (security != null && AxolotlService.PEP_PREFIX.equals(security.getAttribute("type"))) {
+                    Log.d(Config.LOGTAG, account.getJid().asBareJid()+": received jingle file offer with JET");
+                    encrypted = security.findChild("encrypted", AxolotlService.PEP_PREFIX);
+                }
+            }
             if (encrypted != null) {
                 this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().asBareJid());
             }
@@ -520,7 +531,18 @@ public class JingleConnection implements Transferable {
                 this.file.setKey(mXmppAxolotlMessage.getInnerKey());
                 this.file.setIv(mXmppAxolotlMessage.getIV());
                 this.file.setExpectedSize(file.getSize() + 16);
-                content.setFileOffer(this.file, false, this.ftVersion).addChild(mXmppAxolotlMessage.toElement());
+                final Element file = content.setFileOffer(this.file, false, this.ftVersion);
+                if (remoteSupportsOmemoJet) {
+                    Log.d(Config.LOGTAG, account.getJid().asBareJid()+": remote announced support for JET");
+                    final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
+                    security.setAttribute("name", this.contentName);
+                    security.setAttribute("cipher", JET_OMEMO_CIPHER);
+                    security.setAttribute("type", AxolotlService.PEP_PREFIX);
+                    security.addChild(mXmppAxolotlMessage.toElement());
+                    content.addChild(security);
+                } else {
+                    file.addChild(mXmppAxolotlMessage.toElement());
+                }
             } else {
                 this.file.setExpectedSize(file.getSize());
                 content.setFileOffer(this.file, false, this.ftVersion);
@@ -754,6 +776,8 @@ public class JingleConnection implements Transferable {
                 this.sendFallbackToIbb();
             }
         } else {
+            final JingleCandidate candidate = connection.getCandidate();
+            Log.d(Config.LOGTAG, account.getJid().asBareJid()+": elected candidate "+candidate.getHost()+":"+candidate.getPort());
             this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
             if (connection.needsActivation()) {
                 if (connection.getCandidate().isOurs()) {

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java 🔗

@@ -16,6 +16,7 @@ import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.services.AbstractConnectionManager;
@@ -134,11 +135,14 @@ public class JingleSocks5Transport extends JingleTransport {
             outputStream.write(response.array());
             outputStream.flush();
             if (success) {
+                Log.d(Config.LOGTAG,connection.getAccount().getJid().asBareJid()+": successfully processed connection to candidate "+candidate.getHost()+":"+candidate.getPort());
                 this.socket = socket;
                 this.inputStream = inputStream;
                 this.outputStream = outputStream;
                 this.isEstablished = true;
                 FileBackend.close(serverSocket);
+            } else {
+                this.socket.close();
             }
         } else {
             socket.close();
@@ -174,6 +178,7 @@ public class JingleSocks5Transport extends JingleTransport {
         new Thread(() -> {
             InputStream fileInputStream = null;
             final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_" + connection.getSessionId());
+            long transmitted = 0;
             try {
                 wakeLock.acquire();
                 MessageDigest digest = MessageDigest.getInstance("SHA-1");
@@ -186,7 +191,6 @@ public class JingleSocks5Transport extends JingleTransport {
                 }
                 final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
                 long size = file.getExpectedSize();
-                long transmitted = 0;
                 int count;
                 byte[] buffer = new byte[8192];
                 while ((count = innerInputStream.read(buffer)) > 0) {
@@ -201,7 +205,8 @@ public class JingleSocks5Transport extends JingleTransport {
                     callback.onFileTransmitted(file);
                 }
             } catch (Exception e) {
-                Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage());
+                final Account account = connection.getAccount();
+                Log.d(Config.LOGTAG, account.getJid().asBareJid()+": failed sending file after "+transmitted+"/"+file.getExpectedSize()+" ("+ socket.getInetAddress()+":"+socket.getPort()+")", e);
                 callback.onFileTransferAborted();
             } finally {
                 FileBackend.close(fileInputStream);

src/main/res/values-hu/strings.xml 🔗

@@ -873,4 +873,5 @@
   <string name="not_a_backup_file">A kiválasztott fájl nem a Conversations biztonsági mentése</string>
   <string name="account_already_setup">Ez a fiók már be lett állítva</string>
   <string name="please_enter_password">Kérem, adja meg a fiókhoz tartozó jelszót</string>
+  <string name="unable_to_perform_this_action">Nem sikerült ezt a cselekvést elvégezni</string>
 </resources>