enable axolotl encryption for jingle supported file transfers

This commit is contained in:
Daniel Gultsch 2015-08-01 01:19:16 +02:00
parent 6059b96456
commit 60cd307f73
10 changed files with 223 additions and 138 deletions

View file

@ -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;
} }

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
}
} }

View file

@ -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);
} }

View file

@ -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));

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 = 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();

View file

@ -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");

View file

@ -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;
}
}
} }

View file

@ -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() {