use gcm for file encryption over http

This commit is contained in:
Daniel Gultsch 2015-07-29 23:45:37 +02:00
parent b7c64cd19d
commit 58d80f58be
6 changed files with 126 additions and 103 deletions

View file

@ -1,26 +1,7 @@
package eu.siacs.conversations.entities; package eu.siacs.conversations.entities;
import android.util.Log;
import java.io.File; 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; import eu.siacs.conversations.utils.MimeUtils;
public class DownloadableFile extends File { public class DownloadableFile extends File {
@ -29,8 +10,7 @@ public class DownloadableFile extends File {
private long expectedSize = 0; private long expectedSize = 0;
private String sha1sum; private String sha1sum;
private Key aeskey; private byte[] aeskey;
private String mime;
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
@ -84,85 +64,24 @@ public class DownloadableFile extends File {
byte[] iv = new byte[16]; byte[] iv = new byte[16];
System.arraycopy(key, 0, iv, 0, 16); System.arraycopy(key, 0, iv, 0, 16);
System.arraycopy(key, 16, secretKey, 0, 32); System.arraycopy(key, 16, secretKey, 0, 32);
this.aeskey = new SecretKeySpec(secretKey, "AES"); this.aeskey = secretKey;
this.iv = iv; this.iv = iv;
} else if (key.length >= 32) { } else if (key.length >= 32) {
byte[] secretKey = new byte[32]; byte[] secretKey = new byte[32];
System.arraycopy(key, 0, secretKey, 0, 32); System.arraycopy(key, 0, secretKey, 0, 32);
this.aeskey = new SecretKeySpec(secretKey, "AES"); this.aeskey = secretKey;
} else if (key.length >= 16) { } else if (key.length >= 16) {
byte[] secretKey = new byte[16]; byte[] secretKey = new byte[16];
System.arraycopy(key, 0, secretKey, 0, 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; return this.aeskey;
} }
public InputStream createInputStream() { public byte[] getIv() {
if (this.getKey() == null) { return this.iv;
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;
}
}
} }
} }

View file

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

View file

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

View file

@ -93,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport {
digest.reset(); digest.reset();
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
file.createNewFile(); file.createNewFile();
this.fileOutputStream = file.createOutputStream(); this.fileOutputStream = createOutputStream(file);
if (this.fileOutputStream == null) { if (this.fileOutputStream == null) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
callback.onFileTransferAborted(); callback.onFileTransferAborted();
@ -120,7 +120,7 @@ public class JingleInbandTransport extends JingleTransport {
this.fileSize = this.remainingSize; this.fileSize = this.remainingSize;
this.digest = MessageDigest.getInstance("SHA-1"); this.digest = MessageDigest.getInstance("SHA-1");
this.digest.reset(); this.digest.reset();
fileInputStream = this.file.createInputStream(); fileInputStream = createInputStream(this.file);
if (fileInputStream == null) { if (fileInputStream == null) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
callback.onFileTransferAborted(); callback.onFileTransferAborted();

View file

@ -106,7 +106,7 @@ public class JingleSocks5Transport extends JingleTransport {
try { try {
MessageDigest digest = MessageDigest.getInstance("SHA-1"); MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset(); digest.reset();
fileInputStream = file.createInputStream(); fileInputStream = createInputStream(file); //file.createInputStream();
if (fileInputStream == null) { if (fileInputStream == null) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
callback.onFileTransferAborted(); callback.onFileTransferAborted();
@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
socket.setSoTimeout(30000); socket.setSoTimeout(30000);
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
file.createNewFile(); file.createNewFile();
fileOutputStream = file.createOutputStream(); fileOutputStream = createOutputStream(file);
if (fileOutputStream == null) { if (fileOutputStream == null) {
callback.onFileTransferAborted(); callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");

View file

@ -1,5 +1,24 @@
package eu.siacs.conversations.xmpp.jingle; 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; import eu.siacs.conversations.entities.DownloadableFile;
public abstract class JingleTransport { public abstract class JingleTransport {
@ -12,4 +31,58 @@ public abstract class JingleTransport {
final OnFileTransmissionStatusChanged callback); final OnFileTransmissionStatusChanged callback);
public abstract void disconnect(); 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;
}
}
} }