use gcm for file encryption over http

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/DownloadableFile.java         | 97 
src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java       | 21 
src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java         | 32 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java |  4 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java |  4 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java       | 73 
6 files changed, 127 insertions(+), 104 deletions(-)

Detailed changes

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

@@ -1,26 +1,7 @@
 package eu.siacs.conversations.entities;
 
-import android.util.Log;
-
 import java.io.File;
-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.Key;
-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.utils.MimeUtils;
 
 public class DownloadableFile extends File {
@@ -29,8 +10,7 @@ public class DownloadableFile extends File {
 
 	private long expectedSize = 0;
 	private String sha1sum;
-	private Key aeskey;
-	private String mime;
+	private byte[] aeskey;
 
 	private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
 			0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
@@ -84,85 +64,24 @@ public class DownloadableFile extends File {
 			byte[] iv = new byte[16];
 			System.arraycopy(key, 0, iv, 0, 16);
 			System.arraycopy(key, 16, secretKey, 0, 32);
-			this.aeskey = new SecretKeySpec(secretKey, "AES");
+			this.aeskey = secretKey;
 			this.iv = iv;
 		} else if (key.length >= 32) {
 			byte[] secretKey = new byte[32];
 			System.arraycopy(key, 0, secretKey, 0, 32);
-			this.aeskey = new SecretKeySpec(secretKey, "AES");
+			this.aeskey = secretKey;
 		} else if (key.length >= 16) {
 			byte[] secretKey = new byte[16];
 			System.arraycopy(key, 0, secretKey, 0, 16);
-			this.aeskey = new SecretKeySpec(secretKey, "AES");
+			this.aeskey = secretKey;
 		}
 	}
 
-	public Key getKey() {
+	public byte[] getKey() {
 		return this.aeskey;
 	}
 
-	public InputStream createInputStream() {
-		if (this.getKey() == null) {
-			try {
-				return new FileInputStream(this);
-			} catch (FileNotFoundException e) {
-				return null;
-			}
-		} else {
-			try {
-				IvParameterSpec ips = new IvParameterSpec(iv);
-				Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-				cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips);
-				Log.d(Config.LOGTAG, "opening encrypted input stream");
-				return new CipherInputStream(new FileInputStream(this), cipher);
-			} catch (NoSuchAlgorithmException e) {
-				Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
-				return null;
-			} catch (NoSuchPaddingException e) {
-				Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
-				return null;
-			} catch (InvalidKeyException e) {
-				Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
-				return null;
-			} catch (InvalidAlgorithmParameterException e) {
-				Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
-				return null;
-			} catch (FileNotFoundException e) {
-				return null;
-			}
-		}
-	}
-
-	public OutputStream createOutputStream() {
-		if (this.getKey() == null) {
-			try {
-				return new FileOutputStream(this);
-			} catch (FileNotFoundException e) {
-				return null;
-			}
-		} else {
-			try {
-				IvParameterSpec ips = new IvParameterSpec(this.iv);
-				Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-				cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips);
-				Log.d(Config.LOGTAG, "opening encrypted output stream");
-				return new CipherOutputStream(new FileOutputStream(this),
-						cipher);
-			} catch (NoSuchAlgorithmException e) {
-				Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
-				return null;
-			} catch (NoSuchPaddingException e) {
-				Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
-				return null;
-			} catch (InvalidKeyException e) {
-				Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
-				return null;
-			} catch (InvalidAlgorithmParameterException e) {
-				Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
-				return null;
-			} catch (FileNotFoundException e) {
-				return null;
-			}
-		}
+	public byte[] getIv() {
+		return this.iv;
 	}
 }

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

@@ -2,10 +2,17 @@ package eu.siacs.conversations.http;
 
 import android.content.Intent;
 import android.net.Uri;
-import android.os.SystemClock;
 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;
@@ -206,7 +213,7 @@ public class HttpDownloadConnection implements Transferable {
 			}
 		}
 
-		private void download() throws SSLHandshakeException, IOException {
+		private void download() throws IOException {
 			HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
 			if (connection instanceof HttpsURLConnection) {
 				mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
@@ -215,9 +222,13 @@ public class HttpDownloadConnection implements Transferable {
 			BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
 			file.getParentFile().mkdirs();
 			file.createNewFile();
-			OutputStream os = file.createOutputStream();
-			if (os == null) {
-				throw new IOException();
+			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);
 			}
 			long transmitted = 0;
 			long expected = file.getExpectedSize();

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

@@ -1,8 +1,18 @@
 package eu.siacs.conversations.http;
 
 import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
 import android.util.Log;
 
+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;
@@ -43,7 +53,7 @@ public class HttpUploadConnection implements Transferable {
 	private byte[] key = null;
 
 	private long transmitted = 0;
-	private long expected = 1;
+	private int expected = 1;
 
 	public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
 		this.mHttpConnectionManager = httpConnectionManager;
@@ -142,14 +152,21 @@ 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);
+				}
 				connection.setRequestMethod("PUT");
-				connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
+				connection.setFixedLengthStreamingMode(expected);
 				connection.setDoOutput(true);
 				connection.connect();
 				os = connection.getOutputStream();
-				is = file.createInputStream();
 				transmitted = 0;
-				expected = file.getExpectedSize();
 				int count = -1;
 				byte[] buffer = new byte[4096];
 				while (((count = is.read(buffer)) != -1) && !canceled) {
@@ -163,11 +180,13 @@ public class HttpUploadConnection implements Transferable {
 				int code = connection.getResponseCode();
 				if (code == 200 || code == 201) {
 					Log.d(Config.LOGTAG, "finished uploading file");
-					Message.FileParams params = message.getFileParams();
 					if (key != null) {
 						mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
 					}
 					mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl);
+					Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+					intent.setData(Uri.fromFile(file));
+					mXmppConnectionService.sendBroadcast(intent);
 					message.setTransferable(null);
 					message.setCounterpart(message.getConversation().getJid().toBareJid());
 					if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
@@ -188,12 +207,13 @@ public class HttpUploadConnection implements Transferable {
 							}
 						});
 					} else {
-						mXmppConnectionService.resendMessage(message,delayed);
+						mXmppConnectionService.resendMessage(message, delayed);
 					}
 				} else {
 					fail();
 				}
 			} catch (IOException e) {
+				e.printStackTrace();
 				Log.d(Config.LOGTAG,"http upload failed "+e.getMessage());
 				fail();
 			} finally {

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 = file.createOutputStream();
+			this.fileOutputStream = createOutputStream(file);
 			if (this.fileOutputStream == null) {
 				Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
 				callback.onFileTransferAborted();
@@ -120,7 +120,7 @@ public class JingleInbandTransport extends JingleTransport {
 			this.fileSize = this.remainingSize;
 			this.digest = MessageDigest.getInstance("SHA-1");
 			this.digest.reset();
-			fileInputStream = this.file.createInputStream();
+			fileInputStream = createInputStream(this.file);
 			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,7 +106,7 @@ public class JingleSocks5Transport extends JingleTransport {
 				try {
 					MessageDigest digest = MessageDigest.getInstance("SHA-1");
 					digest.reset();
-					fileInputStream = file.createInputStream();
+					fileInputStream = createInputStream(file); //file.createInputStream();
 					if (fileInputStream == null) {
 						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
 						callback.onFileTransferAborted();
@@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
 					socket.setSoTimeout(30000);
 					file.getParentFile().mkdirs();
 					file.createNewFile();
-					fileOutputStream = file.createOutputStream();
+					fileOutputStream = createOutputStream(file);
 					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,5 +1,24 @@
 package eu.siacs.conversations.xmpp.jingle;
 
+import android.util.Log;
+
+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 abstract class JingleTransport {
@@ -12,4 +31,58 @@ 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;
+		}
+	}
 }