enable axolotl encryption for jingle supported file transfers
This commit is contained in:
parent
6059b96456
commit
60cd307f73
|
@ -24,16 +24,8 @@ public class DownloadableFile extends File {
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getExpectedSize() {
|
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() {
|
public String getMimeType() {
|
||||||
String path = this.getAbsolutePath();
|
String path = this.getAbsolutePath();
|
||||||
|
@ -58,25 +50,33 @@ public class DownloadableFile extends File {
|
||||||
this.sha1sum = sum;
|
this.sha1sum = sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKey(byte[] key) {
|
public void setKeyAndIv(byte[] keyIvCombo) {
|
||||||
if (key.length == 48) {
|
if (keyIvCombo.length == 48) {
|
||||||
byte[] secretKey = new byte[32];
|
byte[] secretKey = new byte[32];
|
||||||
byte[] iv = new byte[16];
|
byte[] iv = new byte[16];
|
||||||
System.arraycopy(key, 0, iv, 0, 16);
|
System.arraycopy(keyIvCombo, 0, iv, 0, 16);
|
||||||
System.arraycopy(key, 16, secretKey, 0, 32);
|
System.arraycopy(keyIvCombo, 16, secretKey, 0, 32);
|
||||||
this.aeskey = secretKey;
|
this.aeskey = secretKey;
|
||||||
this.iv = iv;
|
this.iv = iv;
|
||||||
} else if (key.length >= 32) {
|
} else if (keyIvCombo.length >= 32) {
|
||||||
byte[] secretKey = new byte[32];
|
byte[] secretKey = new byte[32];
|
||||||
System.arraycopy(key, 0, secretKey, 0, 32);
|
System.arraycopy(keyIvCombo, 0, secretKey, 0, 32);
|
||||||
this.aeskey = secretKey;
|
this.aeskey = secretKey;
|
||||||
} else if (key.length >= 16) {
|
} else if (keyIvCombo.length >= 16) {
|
||||||
byte[] secretKey = new byte[16];
|
byte[] secretKey = new byte[16];
|
||||||
System.arraycopy(key, 0, secretKey, 0, 16);
|
System.arraycopy(keyIvCombo, 0, secretKey, 0, 16);
|
||||||
this.aeskey = secretKey;
|
this.aeskey = secretKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setKey(byte[] key) {
|
||||||
|
this.aeskey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIv(byte[] iv) {
|
||||||
|
this.iv = iv;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getKey() {
|
public byte[] getKey() {
|
||||||
return this.aeskey;
|
return this.aeskey;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,7 @@ import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
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;
|
||||||
|
@ -28,6 +20,8 @@ import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
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.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
|
||||||
|
@ -90,7 +84,7 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
|
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||||
String reference = mUrl.getRef();
|
String reference = mUrl.getRef();
|
||||||
if (reference != null && reference.length() == 96) {
|
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
|
if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
|
||||||
|
@ -194,6 +188,8 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
|
|
||||||
private boolean interactive = false;
|
private boolean interactive = false;
|
||||||
|
|
||||||
|
private OutputStream os;
|
||||||
|
|
||||||
public FileDownloader(boolean interactive) {
|
public FileDownloader(boolean interactive) {
|
||||||
this.interactive = interactive;
|
this.interactive = interactive;
|
||||||
}
|
}
|
||||||
|
@ -206,8 +202,10 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
updateImageBounds();
|
updateImageBounds();
|
||||||
finish();
|
finish();
|
||||||
} catch (SSLHandshakeException e) {
|
} catch (SSLHandshakeException e) {
|
||||||
|
FileBackend.close(os);
|
||||||
changeStatus(STATUS_OFFER);
|
changeStatus(STATUS_OFFER);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
FileBackend.close(os);
|
||||||
mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
|
mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
|
@ -222,14 +220,7 @@ 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;
|
os = AbstractConnectionManager.createOutputStream(file,true);
|
||||||
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 transmitted = 0;
|
||||||
long expected = file.getExpectedSize();
|
long expected = file.getExpectedSize();
|
||||||
int count = -1;
|
int count = -1;
|
||||||
|
|
|
@ -4,15 +4,8 @@ import android.app.PendingIntent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
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.Message;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
|
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.ui.UiCallback;
|
import eu.siacs.conversations.ui.UiCallback;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
@ -105,7 +99,7 @@ public class HttpUploadConnection implements Transferable {
|
||||||
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
this.key = new byte[48];
|
this.key = new byte[48];
|
||||||
mXmppConnectionService.getRNG().nextBytes(this.key);
|
mXmppConnectionService.getRNG().nextBytes(this.key);
|
||||||
this.file.setKey(this.key);
|
this.file.setKeyAndIv(this.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD);
|
Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD);
|
||||||
|
@ -152,15 +146,9 @@ 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) {
|
Pair<InputStream,Integer> pair = AbstractConnectionManager.createInputStream(file,true);
|
||||||
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
|
is = pair.first;
|
||||||
cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
|
expected = pair.second;
|
||||||
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(expected);
|
connection.setFixedLengthStreamingMode(expected);
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
|
|
|
@ -1,5 +1,33 @@
|
||||||
package eu.siacs.conversations.services;
|
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 {
|
public class AbstractConnectionManager {
|
||||||
protected XmppConnectionService mXmppConnectionService;
|
protected XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
@ -20,4 +48,73 @@ public class AbstractConnectionManager {
|
||||||
return 524288;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -755,6 +755,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Message.ENCRYPTION_AXOLOTL:
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
|
message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
|
||||||
if (message.needsUploading()) {
|
if (message.needsUploading()) {
|
||||||
if (account.httpUploadAvailable() || message.fixCounterpart()) {
|
if (account.httpUploadAvailable() || message.fixCounterpart()) {
|
||||||
this.sendFileMessage(message,delay);
|
this.sendFileMessage(message,delay);
|
||||||
|
@ -765,7 +766,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message);
|
XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message);
|
||||||
if (axolotlMessage == null) {
|
if (axolotlMessage == null) {
|
||||||
account.getAxolotlService().preparePayloadMessage(message, delay);
|
account.getAxolotlService().preparePayloadMessage(message, delay);
|
||||||
message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
|
|
||||||
} else {
|
} else {
|
||||||
packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage);
|
packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@ package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
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 android.util.Pair;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -14,13 +16,19 @@ import java.util.Map.Entry;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
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.Account;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
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.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.utils.Xmlns;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
|
@ -66,8 +74,13 @@ public class JingleConnection implements Transferable {
|
||||||
|
|
||||||
private boolean acceptedAutomatically = false;
|
private boolean acceptedAutomatically = false;
|
||||||
|
|
||||||
|
private XmppAxolotlMessage mXmppAxolotlMessage;
|
||||||
|
|
||||||
private JingleTransport transport = null;
|
private JingleTransport transport = null;
|
||||||
|
|
||||||
|
private OutputStream mFileOutputStream;
|
||||||
|
private InputStream mFileInputStream;
|
||||||
|
|
||||||
private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
|
private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
|
||||||
|
|
||||||
@Override
|
@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() {
|
private OnProxyActivated onProxyActivated = new OnProxyActivated() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -194,7 +215,22 @@ public class JingleConnection implements Transferable {
|
||||||
mXmppConnectionService.sendIqPacket(account,response,null);
|
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.contentCreator = "initiator";
|
||||||
this.contentName = this.mJingleConnectionManager.nextRandomId();
|
this.contentName = this.mJingleConnectionManager.nextRandomId();
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
@ -238,8 +274,7 @@ public class JingleConnection implements Transferable {
|
||||||
});
|
});
|
||||||
mergeCandidate(candidate);
|
mergeCandidate(candidate);
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG,
|
Log.d(Config.LOGTAG,"no primary candidate of our own was found");
|
||||||
"no primary candidate of our own was found");
|
|
||||||
sendInitRequest();
|
sendInitRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,13 +302,16 @@ public class JingleConnection implements Transferable {
|
||||||
this.contentCreator = content.getAttribute("creator");
|
this.contentCreator = content.getAttribute("creator");
|
||||||
this.contentName = content.getAttribute("name");
|
this.contentName = content.getAttribute("name");
|
||||||
this.transportId = content.getTransportId();
|
this.transportId = content.getTransportId();
|
||||||
this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
|
this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
|
||||||
.getChildren()));
|
|
||||||
this.fileOffer = packet.getJingleContent().getFileOffer();
|
this.fileOffer = packet.getJingleContent().getFileOffer();
|
||||||
|
|
||||||
mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
|
mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
|
||||||
|
|
||||||
if (fileOffer != 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 fileSize = fileOffer.findChild("size");
|
||||||
Element fileNameElement = fileOffer.findChild("name");
|
Element fileNameElement = fileOffer.findChild("name");
|
||||||
if (fileNameElement != null) {
|
if (fileNameElement != null) {
|
||||||
|
@ -319,10 +357,8 @@ public class JingleConnection implements Transferable {
|
||||||
message.setBody(Long.toString(size));
|
message.setBody(Long.toString(size));
|
||||||
conversation.add(message);
|
conversation.add(message);
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
if (size < this.mJingleConnectionManager
|
if (size < this.mJingleConnectionManager.getAutoAcceptFileSize()) {
|
||||||
.getAutoAcceptFileSize()) {
|
Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
|
||||||
Log.d(Config.LOGTAG, "auto accepting file from "
|
|
||||||
+ packet.getFrom());
|
|
||||||
this.acceptedAutomatically = true;
|
this.acceptedAutomatically = true;
|
||||||
this.sendAccept();
|
this.sendAccept();
|
||||||
} else {
|
} else {
|
||||||
|
@ -333,22 +369,32 @@ public class JingleConnection implements Transferable {
|
||||||
+ " allowed size:"
|
+ " allowed size:"
|
||||||
+ this.mJingleConnectionManager
|
+ this.mJingleConnectionManager
|
||||||
.getAutoAcceptFileSize());
|
.getAutoAcceptFileSize());
|
||||||
this.mXmppConnectionService.getNotificationService()
|
this.mXmppConnectionService.getNotificationService().push(message);
|
||||||
.push(message);
|
|
||||||
}
|
}
|
||||||
this.file = this.mXmppConnectionService.getFileBackend()
|
this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||||
.getFile(message, false);
|
if (mXmppAxolotlMessage != null) {
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
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();
|
byte[] key = conversation.getSymmetricKey();
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
this.sendCancel();
|
this.sendCancel();
|
||||||
this.fail();
|
this.fail();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this.file.setKey(key);
|
this.file.setKeyAndIv(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL);
|
||||||
this.file.setExpectedSize(size);
|
this.file.setExpectedSize(size);
|
||||||
|
Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
|
||||||
} else {
|
} else {
|
||||||
this.sendCancel();
|
this.sendCancel();
|
||||||
this.fail();
|
this.fail();
|
||||||
|
@ -364,19 +410,30 @@ public class JingleConnection implements Transferable {
|
||||||
Content content = new Content(this.contentCreator, this.contentName);
|
Content content = new Content(this.contentCreator, this.contentName);
|
||||||
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||||
content.setTransportId(this.transportId);
|
content.setTransportId(this.transportId);
|
||||||
this.file = this.mXmppConnectionService.getFileBackend().getFile(
|
this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||||
message, false);
|
Pair<InputStream,Integer> pair;
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
Conversation conversation = this.message.getConversation();
|
Conversation conversation = this.message.getConversation();
|
||||||
if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
|
if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
|
||||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
|
this.file.setKeyAndIv(conversation.getSymmetricKey());
|
||||||
|
pair = AbstractConnectionManager.createInputStream(this.file,false);
|
||||||
|
this.file.setExpectedSize(pair.second);
|
||||||
content.setFileOffer(this.file, true);
|
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 {
|
} else {
|
||||||
|
pair = AbstractConnectionManager.createInputStream(this.file,false);
|
||||||
|
this.file.setExpectedSize(pair.second);
|
||||||
content.setFileOffer(this.file, false);
|
content.setFileOffer(this.file, false);
|
||||||
}
|
}
|
||||||
|
this.mFileInputStream = pair.first;
|
||||||
this.transportId = this.mJingleConnectionManager.nextRandomId();
|
this.transportId = this.mJingleConnectionManager.nextRandomId();
|
||||||
content.setTransportId(this.transportId);
|
content.setTransportId(this.transportId);
|
||||||
content.socks5transport().setChildren(getCandidatesAsElements());
|
content.socks5transport().setChildren(getCandidatesAsElements());
|
||||||
|
@ -748,6 +805,8 @@ public class JingleConnection implements Transferable {
|
||||||
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
|
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
|
||||||
this.transport.disconnect();
|
this.transport.disconnect();
|
||||||
}
|
}
|
||||||
|
FileBackend.close(mFileInputStream);
|
||||||
|
FileBackend.close(mFileOutputStream);
|
||||||
if (this.message != null) {
|
if (this.message != null) {
|
||||||
if (this.responder.equals(account.getJid())) {
|
if (this.responder.equals(account.getJid())) {
|
||||||
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
|
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
|
||||||
|
|
|
@ -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 = createOutputStream(file);
|
this.fileOutputStream = connection.getFileOutputStream();
|
||||||
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();
|
||||||
|
@ -112,15 +112,11 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
this.onFileTransmissionStatusChanged = callback;
|
this.onFileTransmissionStatusChanged = callback;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
try {
|
try {
|
||||||
if (this.file.getKey() != null) {
|
this.remainingSize = this.file.getExpectedSize();
|
||||||
this.remainingSize = (this.file.getSize() / 16 + 1) * 16;
|
|
||||||
} else {
|
|
||||||
this.remainingSize = this.file.getSize();
|
|
||||||
}
|
|
||||||
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 = createInputStream(this.file);
|
fileInputStream = connection.getFileInputStream();
|
||||||
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();
|
||||||
|
|
|
@ -106,13 +106,13 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
try {
|
try {
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
digest.reset();
|
digest.reset();
|
||||||
fileInputStream = createInputStream(file); //file.createInputStream();
|
fileInputStream = connection.getFileInputStream();
|
||||||
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();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long size = file.getSize();
|
long size = file.getExpectedSize();
|
||||||
long transmitted = 0;
|
long transmitted = 0;
|
||||||
int count;
|
int count;
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
|
@ -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 = createOutputStream(file);
|
fileOutputStream = connection.getFileOutputStream();
|
||||||
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");
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
import android.util.Log;
|
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.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
@ -31,58 +38,4 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,17 +25,18 @@ public class Content extends Element {
|
||||||
this.transportId = sid;
|
this.transportId = sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFileOffer(DownloadableFile actualFile, boolean otr) {
|
public Element setFileOffer(DownloadableFile actualFile, boolean otr) {
|
||||||
Element description = this.addChild("description",
|
Element description = this.addChild("description",
|
||||||
"urn:xmpp:jingle:apps:file-transfer:3");
|
"urn:xmpp:jingle:apps:file-transfer:3");
|
||||||
Element offer = description.addChild("offer");
|
Element offer = description.addChild("offer");
|
||||||
Element file = offer.addChild("file");
|
Element file = offer.addChild("file");
|
||||||
file.addChild("size").setContent(Long.toString(actualFile.getSize()));
|
file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize()));
|
||||||
if (otr) {
|
if (otr) {
|
||||||
file.addChild("name").setContent(actualFile.getName() + ".otr");
|
file.addChild("name").setContent(actualFile.getName() + ".otr");
|
||||||
} else {
|
} else {
|
||||||
file.addChild("name").setContent(actualFile.getName());
|
file.addChild("name").setContent(actualFile.getName());
|
||||||
}
|
}
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element getFileOffer() {
|
public Element getFileOffer() {
|
||||||
|
|
Loading…
Reference in New Issue