enable axolotl encryption for jingle supported file transfers

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/DownloadableFile.java          | 34 
src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java        | 25 
src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java          | 24 
src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java | 97 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java     |  2 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java       | 97 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java  | 10 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java  |  6 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java        | 61 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java        |  5 
10 files changed, 223 insertions(+), 138 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/DownloadableFile.java 🔗

@@ -24,15 +24,7 @@ public class DownloadableFile extends File {
 	}
 
 	public long getExpectedSize() {
-		if (this.aeskey != null) {
-			if (this.expectedSize == 0) {
-				return 0;
-			} else {
-				return (this.expectedSize / 16 + 1) * 16;
-			}
-		} else {
-			return this.expectedSize;
-		}
+		return this.expectedSize;
 	}
 
 	public String getMimeType() {
@@ -58,25 +50,33 @@ public class DownloadableFile extends File {
 		this.sha1sum = sum;
 	}
 
-	public void setKey(byte[] key) {
-		if (key.length == 48) {
+	public void setKeyAndIv(byte[] keyIvCombo) {
+		if (keyIvCombo.length == 48) {
 			byte[] secretKey = new byte[32];
 			byte[] iv = new byte[16];
-			System.arraycopy(key, 0, iv, 0, 16);
-			System.arraycopy(key, 16, secretKey, 0, 32);
+			System.arraycopy(keyIvCombo, 0, iv, 0, 16);
+			System.arraycopy(keyIvCombo, 16, secretKey, 0, 32);
 			this.aeskey = secretKey;
 			this.iv = iv;
-		} else if (key.length >= 32) {
+		} else if (keyIvCombo.length >= 32) {
 			byte[] secretKey = new byte[32];
-			System.arraycopy(key, 0, secretKey, 0, 32);
+			System.arraycopy(keyIvCombo, 0, secretKey, 0, 32);
 			this.aeskey = secretKey;
-		} else if (key.length >= 16) {
+		} else if (keyIvCombo.length >= 16) {
 			byte[] secretKey = new byte[16];
-			System.arraycopy(key, 0, secretKey, 0, 16);
+			System.arraycopy(keyIvCombo, 0, secretKey, 0, 16);
 			this.aeskey = secretKey;
 		}
 	}
 
+	public void setKey(byte[] key) {
+		this.aeskey = key;
+	}
+
+	public void setIv(byte[] iv) {
+		this.iv = iv;
+	}
+
 	public byte[] getKey() {
 		return this.aeskey;
 	}

src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java 🔗

@@ -4,15 +4,7 @@ import android.content.Intent;
 import android.net.Uri;
 import android.util.Log;
 
-import org.bouncycastle.crypto.engines.AESEngine;
-import org.bouncycastle.crypto.io.CipherOutputStream;
-import org.bouncycastle.crypto.modes.AEADBlockCipher;
-import org.bouncycastle.crypto.modes.GCMBlockCipher;
-import org.bouncycastle.crypto.params.AEADParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-
 import java.io.BufferedInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
@@ -28,6 +20,8 @@ import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AbstractConnectionManager;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.utils.CryptoHelper;
 
@@ -90,7 +84,7 @@ public class HttpDownloadConnection implements Transferable {
 			this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
 			String reference = mUrl.getRef();
 			if (reference != null && reference.length() == 96) {
-				this.file.setKey(CryptoHelper.hexToBytes(reference));
+				this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
 			}
 
 			if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
@@ -194,6 +188,8 @@ public class HttpDownloadConnection implements Transferable {
 
 		private boolean interactive = false;
 
+		private OutputStream os;
+
 		public FileDownloader(boolean interactive) {
 			this.interactive = interactive;
 		}
@@ -206,8 +202,10 @@ public class HttpDownloadConnection implements Transferable {
 				updateImageBounds();
 				finish();
 			} catch (SSLHandshakeException e) {
+				FileBackend.close(os);
 				changeStatus(STATUS_OFFER);
 			} catch (IOException e) {
+				FileBackend.close(os);
 				mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
 				cancel();
 			}
@@ -222,14 +220,7 @@ public class HttpDownloadConnection implements Transferable {
 			BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
 			file.getParentFile().mkdirs();
 			file.createNewFile();
-			OutputStream os;
-			if (file.getKey() != null) {
-				AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
-				cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
-				os = new CipherOutputStream(new FileOutputStream(file), cipher);
-			} else {
-				os = new FileOutputStream(file);
-			}
+			os = AbstractConnectionManager.createOutputStream(file,true);
 			long transmitted = 0;
 			long expected = file.getExpectedSize();
 			int count = -1;

src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java 🔗

@@ -4,15 +4,8 @@ import android.app.PendingIntent;
 import android.content.Intent;
 import android.net.Uri;
 import android.util.Log;
+import android.util.Pair;
 
-import org.bouncycastle.crypto.engines.AESEngine;
-import org.bouncycastle.crypto.io.CipherInputStream;
-import org.bouncycastle.crypto.modes.AEADBlockCipher;
-import org.bouncycastle.crypto.modes.GCMBlockCipher;
-import org.bouncycastle.crypto.params.AEADParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -28,6 +21,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Transferable;
 import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AbstractConnectionManager;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.ui.UiCallback;
 import eu.siacs.conversations.utils.CryptoHelper;
@@ -105,7 +99,7 @@ public class HttpUploadConnection implements Transferable {
 				|| message.getEncryption() == Message.ENCRYPTION_OTR) {
 			this.key = new byte[48];
 			mXmppConnectionService.getRNG().nextBytes(this.key);
-			this.file.setKey(this.key);
+			this.file.setKeyAndIv(this.key);
 		}
 
 		Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD);
@@ -152,15 +146,9 @@ public class HttpUploadConnection implements Transferable {
 				if (connection instanceof HttpsURLConnection) {
 					mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
 				}
-				if (file.getKey() != null) {
-					AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
-					cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
-					expected = cipher.getOutputSize((int) file.getSize());
-					is = new CipherInputStream(new FileInputStream(file), cipher);
-				} else {
-					expected = (int) file.getSize();
-					is = new FileInputStream(file);
-				}
+				Pair<InputStream,Integer> pair = AbstractConnectionManager.createInputStream(file,true);
+				is = pair.first;
+				expected = pair.second;
 				connection.setRequestMethod("PUT");
 				connection.setFixedLengthStreamingMode(expected);
 				connection.setDoOutput(true);

src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java 🔗

@@ -1,5 +1,33 @@
 package eu.siacs.conversations.services;
 
+import android.util.Log;
+import android.util.Pair;
+
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.DownloadableFile;
+
 public class AbstractConnectionManager {
 	protected XmppConnectionService mXmppConnectionService;
 
@@ -20,4 +48,73 @@ public class AbstractConnectionManager {
 			return 524288;
 		}
 	}
+
+	public static Pair<InputStream,Integer> createInputStream(DownloadableFile file, boolean gcm) {
+		FileInputStream is;
+		int size;
+		try {
+			is = new FileInputStream(file);
+			size = (int) file.getSize();
+			if (file.getKey() == null) {
+				return new Pair<InputStream,Integer>(is,size);
+			}
+		} catch (FileNotFoundException e) {
+			return null;
+		}
+		try {
+			if (gcm) {
+				AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
+				cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
+				InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher);
+				return new Pair<>(cis, cipher.getOutputSize(size));
+			} else {
+				IvParameterSpec ips = new IvParameterSpec(file.getIv());
+				Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+				cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
+				Log.d(Config.LOGTAG, "opening encrypted input stream");
+				return new Pair<InputStream,Integer>(new CipherInputStream(is, cipher),(size / 16 + 1) * 16);
+			}
+		} catch (InvalidKeyException e) {
+			return null;
+		} catch (NoSuchAlgorithmException e) {
+			return null;
+		} catch (NoSuchPaddingException e) {
+			return null;
+		} catch (InvalidAlgorithmParameterException e) {
+			return null;
+		}
+	}
+
+	public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) {
+		FileOutputStream os;
+		try {
+			os = new FileOutputStream(file);
+			if (file.getKey() == null) {
+				return os;
+			}
+		} catch (FileNotFoundException e) {
+			return null;
+		}
+		try {
+			if (gcm) {
+				AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
+				cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
+				return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher);
+			} else {
+				IvParameterSpec ips = new IvParameterSpec(file.getIv());
+				Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+				cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
+				Log.d(Config.LOGTAG, "opening encrypted output stream");
+				return new CipherOutputStream(os, cipher);
+			}
+		} catch (InvalidKeyException e) {
+			return null;
+		} catch (NoSuchAlgorithmException e) {
+			return null;
+		} catch (NoSuchPaddingException e) {
+			return null;
+		} catch (InvalidAlgorithmParameterException e) {
+			return null;
+		}
+	}
 }

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -755,6 +755,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 					}
 					break;
 				case Message.ENCRYPTION_AXOLOTL:
+					message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
 					if (message.needsUploading()) {
 						if (account.httpUploadAvailable() || message.fixCounterpart()) {
 							this.sendFileMessage(message,delay);
@@ -765,7 +766,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 						XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message);
 						if (axolotlMessage == null) {
 							account.getAxolotlService().preparePayloadMessage(message, delay);
-							message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
 						} else {
 							packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage);
 						}

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

@@ -2,9 +2,11 @@ package eu.siacs.conversations.xmpp.jingle;
 
 import android.content.Intent;
 import android.net.Uri;
-import android.os.SystemClock;
 import android.util.Log;
+import android.util.Pair;
 
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -14,13 +16,19 @@ import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.OnMessageCreatedCallback;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Transferable;
 import eu.siacs.conversations.entities.TransferablePlaceholder;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AbstractConnectionManager;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.Xmlns;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 import eu.siacs.conversations.xmpp.jid.Jid;
@@ -66,8 +74,13 @@ public class JingleConnection implements Transferable {
 
 	private boolean acceptedAutomatically = false;
 
+	private XmppAxolotlMessage mXmppAxolotlMessage;
+
 	private JingleTransport transport = null;
 
+	private OutputStream mFileOutputStream;
+	private InputStream mFileInputStream;
+
 	private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
 
 		@Override
@@ -113,6 +126,14 @@ public class JingleConnection implements Transferable {
 		}
 	};
 
+	public InputStream getFileInputStream() {
+		return this.mFileInputStream;
+	}
+
+	public OutputStream getFileOutputStream() {
+		return this.mFileOutputStream;
+	}
+
 	private OnProxyActivated onProxyActivated = new OnProxyActivated() {
 
 		@Override
@@ -194,7 +215,22 @@ public class JingleConnection implements Transferable {
 		mXmppConnectionService.sendIqPacket(account,response,null);
 	}
 
-	public void init(Message message) {
+	public void init(final Message message) {
+		if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
+			Conversation conversation = message.getConversation();
+			conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() {
+				@Override
+				public void run(XmppAxolotlMessage xmppAxolotlMessage) {
+					init(message, xmppAxolotlMessage);
+				}
+			});
+		} else {
+			init(message, null);
+		}
+	}
+
+	private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) {
+		this.mXmppAxolotlMessage = xmppAxolotlMessage;
 		this.contentCreator = "initiator";
 		this.contentName = this.mJingleConnectionManager.nextRandomId();
 		this.message = message;
@@ -238,8 +274,7 @@ public class JingleConnection implements Transferable {
 										});
 								mergeCandidate(candidate);
 							} else {
-								Log.d(Config.LOGTAG,
-										"no primary candidate of our own was found");
+								Log.d(Config.LOGTAG,"no primary candidate of our own was found");
 								sendInitRequest();
 							}
 						}
@@ -267,13 +302,16 @@ public class JingleConnection implements Transferable {
 		this.contentCreator = content.getAttribute("creator");
 		this.contentName = content.getAttribute("name");
 		this.transportId = content.getTransportId();
-		this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
-				.getChildren()));
+		this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
 		this.fileOffer = packet.getJingleContent().getFileOffer();
 
 		mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
 
 		if (fileOffer != null) {
+			Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX);
+			if (encrypted != null) {
+				this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid());
+			}
 			Element fileSize = fileOffer.findChild("size");
 			Element fileNameElement = fileOffer.findChild("name");
 			if (fileNameElement != null) {
@@ -319,10 +357,8 @@ public class JingleConnection implements Transferable {
 				message.setBody(Long.toString(size));
 				conversation.add(message);
 				mXmppConnectionService.updateConversationUi();
-				if (size < this.mJingleConnectionManager
-						.getAutoAcceptFileSize()) {
-					Log.d(Config.LOGTAG, "auto accepting file from "
-							+ packet.getFrom());
+				if (size < this.mJingleConnectionManager.getAutoAcceptFileSize()) {
+					Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
 					this.acceptedAutomatically = true;
 					this.sendAccept();
 				} else {
@@ -333,22 +369,32 @@ public class JingleConnection implements Transferable {
 									+ " allowed size:"
 									+ this.mJingleConnectionManager
 											.getAutoAcceptFileSize());
-					this.mXmppConnectionService.getNotificationService()
-							.push(message);
+					this.mXmppConnectionService.getNotificationService().push(message);
 				}
-				this.file = this.mXmppConnectionService.getFileBackend()
-						.getFile(message, false);
-				if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+				this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
+				if (mXmppAxolotlMessage != null) {
+					XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage);
+					if (transportMessage != null) {
+						message.setEncryption(Message.ENCRYPTION_AXOLOTL);
+						this.file.setKey(transportMessage.getKey());
+						this.file.setIv(transportMessage.getIv());
+						message.setAxolotlFingerprint(transportMessage.getFingerprint());
+					} else {
+						Log.d(Config.LOGTAG,"could not process KeyTransportMessage");
+					}
+				} else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 					byte[] key = conversation.getSymmetricKey();
 					if (key == null) {
 						this.sendCancel();
 						this.fail();
 						return;
 					} else {
-						this.file.setKey(key);
+						this.file.setKeyAndIv(key);
 					}
 				}
+				this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL);
 				this.file.setExpectedSize(size);
+				Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
 			} else {
 				this.sendCancel();
 				this.fail();
@@ -364,19 +410,30 @@ public class JingleConnection implements Transferable {
 		Content content = new Content(this.contentCreator, this.contentName);
 		if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
 			content.setTransportId(this.transportId);
-			this.file = this.mXmppConnectionService.getFileBackend().getFile(
-					message, false);
+			this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
+			Pair<InputStream,Integer> pair;
 			if (message.getEncryption() == Message.ENCRYPTION_OTR) {
 				Conversation conversation = this.message.getConversation();
 				if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
 					Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
 					cancel();
 				}
+				this.file.setKeyAndIv(conversation.getSymmetricKey());
+				pair = AbstractConnectionManager.createInputStream(this.file,false);
+				this.file.setExpectedSize(pair.second);
 				content.setFileOffer(this.file, true);
-				this.file.setKey(conversation.getSymmetricKey());
+			} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
+				this.file.setKey(mXmppAxolotlMessage.getInnerKey());
+				this.file.setIv(mXmppAxolotlMessage.getIV());
+				pair = AbstractConnectionManager.createInputStream(this.file,true);
+				this.file.setExpectedSize(pair.second);
+				content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement());
 			} else {
+				pair = AbstractConnectionManager.createInputStream(this.file,false);
+				this.file.setExpectedSize(pair.second);
 				content.setFileOffer(this.file, false);
 			}
+			this.mFileInputStream = pair.first;
 			this.transportId = this.mJingleConnectionManager.nextRandomId();
 			content.setTransportId(this.transportId);
 			content.socks5transport().setChildren(getCandidatesAsElements());
@@ -748,6 +805,8 @@ public class JingleConnection implements Transferable {
 		if (this.transport != null && this.transport instanceof JingleInbandTransport) {
 			this.transport.disconnect();
 		}
+		FileBackend.close(mFileInputStream);
+		FileBackend.close(mFileOutputStream);
 		if (this.message != null) {
 			if (this.responder.equals(account.getJid())) {
 				this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));

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

@@ -93,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport {
 			digest.reset();
 			file.getParentFile().mkdirs();
 			file.createNewFile();
-			this.fileOutputStream = createOutputStream(file);
+			this.fileOutputStream = connection.getFileOutputStream();
 			if (this.fileOutputStream == null) {
 				Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
 				callback.onFileTransferAborted();
@@ -112,15 +112,11 @@ public class JingleInbandTransport extends JingleTransport {
 		this.onFileTransmissionStatusChanged = callback;
 		this.file = file;
 		try {
-			if (this.file.getKey() != null) {
-				this.remainingSize = (this.file.getSize() / 16 + 1) * 16;
-			} else {
-				this.remainingSize = this.file.getSize();
-			}
+			this.remainingSize = this.file.getExpectedSize();
 			this.fileSize = this.remainingSize;
 			this.digest = MessageDigest.getInstance("SHA-1");
 			this.digest.reset();
-			fileInputStream = createInputStream(this.file);
+			fileInputStream = connection.getFileInputStream();
 			if (fileInputStream == null) {
 				Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
 				callback.onFileTransferAborted();

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

@@ -106,13 +106,13 @@ public class JingleSocks5Transport extends JingleTransport {
 				try {
 					MessageDigest digest = MessageDigest.getInstance("SHA-1");
 					digest.reset();
-					fileInputStream = createInputStream(file); //file.createInputStream();
+					fileInputStream = connection.getFileInputStream();
 					if (fileInputStream == null) {
 						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
 						callback.onFileTransferAborted();
 						return;
 					}
-					long size = file.getSize();
+					long size = file.getExpectedSize();
 					long transmitted = 0;
 					int count;
 					byte[] buffer = new byte[8192];
@@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
 					socket.setSoTimeout(30000);
 					file.getParentFile().mkdirs();
 					file.createNewFile();
-					fileOutputStream = createOutputStream(file);
+					fileOutputStream = connection.getFileOutputStream();
 					if (fileOutputStream == null) {
 						callback.onFileTransferAborted();
 						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");

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

@@ -1,6 +1,13 @@
 package eu.siacs.conversations.xmpp.jingle;
 
 import android.util.Log;
+import android.util.Pair;
+
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -31,58 +38,4 @@ public abstract class JingleTransport {
 			final OnFileTransmissionStatusChanged callback);
 
 	public abstract void disconnect();
-
-	protected InputStream createInputStream(DownloadableFile file) {
-		FileInputStream is;
-		try {
-			is = new FileInputStream(file);
-			if (file.getKey() == null) {
-				return is;
-			}
-		} catch (FileNotFoundException e) {
-			return null;
-		}
-		try {
-			IvParameterSpec ips = new IvParameterSpec(file.getIv());
-			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-			cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
-			Log.d(Config.LOGTAG, "opening encrypted input stream");
-			return new CipherInputStream(is, cipher);
-		} catch (InvalidKeyException e) {
-			return null;
-		} catch (NoSuchAlgorithmException e) {
-			return null;
-		} catch (NoSuchPaddingException e) {
-			return null;
-		} catch (InvalidAlgorithmParameterException e) {
-			return null;
-		}
-	}
-
-	protected OutputStream createOutputStream(DownloadableFile file) {
-		FileOutputStream os;
-		try {
-			os = new FileOutputStream(file);
-			if (file.getKey() == null) {
-				return os;
-			}
-		} catch (FileNotFoundException e) {
-			return null;
-		}
-		try {
-			IvParameterSpec ips = new IvParameterSpec(file.getIv());
-			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-			cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
-			Log.d(Config.LOGTAG, "opening encrypted output stream");
-			return new CipherOutputStream(os, cipher);
-		} catch (InvalidKeyException e) {
-			return null;
-		} catch (NoSuchAlgorithmException e) {
-			return null;
-		} catch (NoSuchPaddingException e) {
-			return null;
-		} catch (InvalidAlgorithmParameterException e) {
-			return null;
-		}
-	}
 }

src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java 🔗

@@ -25,17 +25,18 @@ public class Content extends Element {
 		this.transportId = sid;
 	}
 
-	public void setFileOffer(DownloadableFile actualFile, boolean otr) {
+	public Element setFileOffer(DownloadableFile actualFile, boolean otr) {
 		Element description = this.addChild("description",
 				"urn:xmpp:jingle:apps:file-transfer:3");
 		Element offer = description.addChild("offer");
 		Element file = offer.addChild("file");
-		file.addChild("size").setContent(Long.toString(actualFile.getSize()));
+		file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize()));
 		if (otr) {
 			file.addChild("name").setContent(actualFile.getName() + ".otr");
 		} else {
 			file.addChild("name").setContent(actualFile.getName());
 		}
+		return file;
 	}
 
 	public Element getFileOffer() {