not working version of otr file transfer
This commit is contained in:
parent
beafb06b6a
commit
1cf055d2fd
|
@ -62,6 +62,8 @@ public class Conversation extends AbstractEntity {
|
||||||
|
|
||||||
private transient String latestMarkableMessageId;
|
private transient String latestMarkableMessageId;
|
||||||
|
|
||||||
|
private byte[] symmetricKey;
|
||||||
|
|
||||||
public Conversation(String name, Account account, String contactJid,
|
public Conversation(String name, Account account, String contactJid,
|
||||||
int mode) {
|
int mode) {
|
||||||
this(java.util.UUID.randomUUID().toString(), name, null, account
|
this(java.util.UUID.randomUUID().toString(), name, null, account
|
||||||
|
@ -353,4 +355,12 @@ public class Conversation extends AbstractEntity {
|
||||||
this.latestMarkableMessageId = id;
|
this.latestMarkableMessageId = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSymmetricKey(byte[] key) {
|
||||||
|
this.symmetricKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSymmetricKey() {
|
||||||
|
return this.symmetricKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package eu.siacs.conversations.parser;
|
package eu.siacs.conversations.parser;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import net.java.otr4j.session.Session;
|
import net.java.otr4j.session.Session;
|
||||||
import net.java.otr4j.session.SessionStatus;
|
import net.java.otr4j.session.SessionStatus;
|
||||||
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.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||||
|
|
||||||
|
@ -72,11 +74,15 @@ public class MessageParser extends AbstractParser {
|
||||||
} else if ((before != after) && (after == SessionStatus.FINISHED)) {
|
} else if ((before != after) && (after == SessionStatus.FINISHED)) {
|
||||||
conversation.resetOtrSession();
|
conversation.resetOtrSession();
|
||||||
}
|
}
|
||||||
// isEmpty is a work around for some weird clients which send emtpty
|
|
||||||
// strings over otr
|
|
||||||
if ((body == null) || (body.isEmpty())) {
|
if ((body == null) || (body.isEmpty())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (body.startsWith(CryptoHelper.FILETRANSFER)) {
|
||||||
|
String key = body.substring(CryptoHelper.FILETRANSFER.length());
|
||||||
|
conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
|
||||||
|
Log.d("xmppService","new symmetric key: "+CryptoHelper.bytesToHex(conversation.getSymmetricKey()));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
|
conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
|
||||||
Message finishedMessage = new Message(conversation, packet.getFrom(), body,
|
Message finishedMessage = new Message(conversation, packet.getFrom(), body,
|
||||||
Message.ENCRYPTION_OTR, Message.STATUS_RECIEVED);
|
Message.ENCRYPTION_OTR, Message.STATUS_RECIEVED);
|
||||||
|
|
|
@ -56,7 +56,11 @@ public class FileBackend {
|
||||||
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
|
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
|
||||||
filename = message.getUuid() + ".webp";
|
filename = message.getUuid() + ".webp";
|
||||||
} else {
|
} else {
|
||||||
filename = message.getUuid() + ".webp.pgp";
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
|
filename = message.getUuid() + ".webp";
|
||||||
|
} else {
|
||||||
|
filename = message.getUuid() + ".webp.pgp";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new JingleFile(path + "/" + filename);
|
return new JingleFile(path + "/" + filename);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import org.openintents.openpgp.util.OpenPgpApi;
|
import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||||
|
@ -28,8 +28,10 @@ import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.ui.OnAccountListChangedListener;
|
import eu.siacs.conversations.ui.OnAccountListChangedListener;
|
||||||
import eu.siacs.conversations.ui.OnConversationListChangedListener;
|
import eu.siacs.conversations.ui.OnConversationListChangedListener;
|
||||||
import eu.siacs.conversations.ui.UiCallback;
|
import eu.siacs.conversations.ui.UiCallback;
|
||||||
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||||
import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
|
import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
|
||||||
|
import eu.siacs.conversations.utils.PRNGFixes;
|
||||||
import eu.siacs.conversations.utils.PhoneHelper;
|
import eu.siacs.conversations.utils.PhoneHelper;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
@ -47,6 +49,7 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
@ -114,7 +117,7 @@ public class XmppConnectionService extends Service {
|
||||||
tlsException = listener;
|
tlsException = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Random mRandom = new Random(System.currentTimeMillis());
|
private SecureRandom mRandom;
|
||||||
|
|
||||||
private long lastCarbonMessageReceived = -CARBON_GRACE_PERIOD;
|
private long lastCarbonMessageReceived = -CARBON_GRACE_PERIOD;
|
||||||
|
|
||||||
|
@ -367,7 +370,7 @@ public class XmppConnectionService extends Service {
|
||||||
message = new Message(conversation, "",
|
message = new Message(conversation, "",
|
||||||
Message.ENCRYPTION_DECRYPTED);
|
Message.ENCRYPTION_DECRYPTED);
|
||||||
} else {
|
} else {
|
||||||
message = new Message(conversation, "", Message.ENCRYPTION_NONE);
|
message = new Message(conversation, "", conversation.getNextEncryption());
|
||||||
}
|
}
|
||||||
message.setPresence(conversation.getNextPresence());
|
message.setPresence(conversation.getNextPresence());
|
||||||
message.setType(Message.TYPE_IMAGE);
|
message.setType(Message.TYPE_IMAGE);
|
||||||
|
@ -509,9 +512,12 @@ public class XmppConnectionService extends Service {
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("TrulyRandom")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
ExceptionHelper.init(getApplicationContext());
|
ExceptionHelper.init(getApplicationContext());
|
||||||
|
PRNGFixes.apply();
|
||||||
|
this.mRandom = new SecureRandom();
|
||||||
this.databaseBackend = DatabaseBackend
|
this.databaseBackend = DatabaseBackend
|
||||||
.getInstance(getApplicationContext());
|
.getInstance(getApplicationContext());
|
||||||
this.fileBackend = new FileBackend(getApplicationContext());
|
this.fileBackend = new FileBackend(getApplicationContext());
|
||||||
|
@ -604,7 +610,7 @@ public class XmppConnectionService extends Service {
|
||||||
SharedPreferences sharedPref = getPreferences();
|
SharedPreferences sharedPref = getPreferences();
|
||||||
account.setResource(sharedPref.getString("resource", "mobile")
|
account.setResource(sharedPref.getString("resource", "mobile")
|
||||||
.toLowerCase(Locale.getDefault()));
|
.toLowerCase(Locale.getDefault()));
|
||||||
XmppConnection connection = new XmppConnection(account, this.pm);
|
XmppConnection connection = new XmppConnection(account, this);
|
||||||
connection.setOnMessagePacketReceivedListener(this.messageListener);
|
connection.setOnMessagePacketReceivedListener(this.messageListener);
|
||||||
connection.setOnStatusChangedListener(this.statusListener);
|
connection.setOnStatusChangedListener(this.statusListener);
|
||||||
connection.setOnPresencePacketReceivedListener(this.presenceListener);
|
connection.setOnPresencePacketReceivedListener(this.presenceListener);
|
||||||
|
@ -1240,6 +1246,31 @@ public class XmppConnectionService extends Service {
|
||||||
updateUi(conversation, false);
|
updateUi(conversation, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean renewSymmetricKey(Conversation conversation) {
|
||||||
|
Account account = conversation.getAccount();
|
||||||
|
byte[] symmetricKey = new byte[32];
|
||||||
|
this.mRandom.nextBytes(symmetricKey);
|
||||||
|
Session otrSession = conversation.getOtrSession();
|
||||||
|
if (otrSession!=null) {
|
||||||
|
MessagePacket packet = new MessagePacket();
|
||||||
|
packet.setType(MessagePacket.TYPE_CHAT);
|
||||||
|
packet.setFrom(account.getFullJid());
|
||||||
|
packet.addChild("private", "urn:xmpp:carbons:2");
|
||||||
|
packet.addChild("no-copy", "urn:xmpp:hints");
|
||||||
|
packet.setTo(otrSession.getSessionID().getAccountID() + "/"
|
||||||
|
+ otrSession.getSessionID().getUserID());
|
||||||
|
try {
|
||||||
|
packet.setBody(otrSession.transformSending(CryptoHelper.FILETRANSFER+CryptoHelper.bytesToHex(symmetricKey)));
|
||||||
|
account.getXmppConnection().sendMessagePacket(packet);
|
||||||
|
conversation.setSymmetricKey(symmetricKey);
|
||||||
|
return true;
|
||||||
|
} catch (OtrException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void pushContactToServer(Contact contact) {
|
public void pushContactToServer(Contact contact) {
|
||||||
contact.resetOption(Contact.Options.DIRTY_DELETE);
|
contact.resetOption(Contact.Options.DIRTY_DELETE);
|
||||||
Account account = contact.getAccount();
|
Account account = contact.getAccount();
|
||||||
|
@ -1451,4 +1482,12 @@ public class XmppConnectionService extends Service {
|
||||||
received.setAttribute("id", id);
|
received.setAttribute("id", id);
|
||||||
account.getXmppConnection().sendMessagePacket(receivedPacket);
|
account.getXmppConnection().sendMessagePacket(receivedPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SecureRandom getRNG() {
|
||||||
|
return this.mRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PowerManager getPowerManager() {
|
||||||
|
return this.pm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,7 +237,7 @@ public class ContactsActivity extends XmppActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
String mucName = CryptoHelper.randomMucName();
|
String mucName = CryptoHelper.randomMucName(xmppConnectionService.getRNG());
|
||||||
String serverName = account.getXmppConnection()
|
String serverName = account.getXmppConnection()
|
||||||
.getMucServer();
|
.getMucServer();
|
||||||
String jid = mucName + "@" + serverName;
|
String jid = mucName + "@" + serverName;
|
||||||
|
|
|
@ -418,7 +418,8 @@ public class ConversationActivity extends XmppActivity {
|
||||||
} else if (getSelectedConversation().getNextEncryption() == Message.ENCRYPTION_NONE) {
|
} else if (getSelectedConversation().getNextEncryption() == Message.ENCRYPTION_NONE) {
|
||||||
selectPresenceToAttachFile(attachmentChoice);
|
selectPresenceToAttachFile(attachmentChoice);
|
||||||
} else {
|
} else {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
selectPresenceToAttachFile(attachmentChoice);
|
||||||
|
/*AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setTitle(getString(R.string.otr_file_transfer));
|
builder.setTitle(getString(R.string.otr_file_transfer));
|
||||||
builder.setMessage(getString(R.string.otr_file_transfer_msg));
|
builder.setMessage(getString(R.string.otr_file_transfer_msg));
|
||||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||||
|
@ -448,7 +449,7 @@ public class ConversationActivity extends XmppActivity {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
builder.create().show();
|
builder.create().show();*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -316,7 +316,9 @@ public class ConversationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayDecryptionFailed(ViewHolder viewHolder) {
|
private void displayDecryptionFailed(ViewHolder viewHolder) {
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
if (viewHolder.download_button != null) {
|
||||||
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
viewHolder.image.setVisibility(View.GONE);
|
viewHolder.image.setVisibility(View.GONE);
|
||||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||||
viewHolder.messageBody
|
viewHolder.messageBody
|
||||||
|
@ -525,7 +527,8 @@ public class ConversationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
|
} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
|
||||||
|| (item.getEncryption() == Message.ENCRYPTION_NONE)) {
|
|| (item.getEncryption() == Message.ENCRYPTION_NONE)
|
||||||
|
|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
|
||||||
displayImageMessage(viewHolder, item);
|
displayImageMessage(viewHolder, item);
|
||||||
} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
|
} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
displayInfoMessage(viewHolder,
|
displayInfoMessage(viewHolder,
|
||||||
|
|
|
@ -5,14 +5,14 @@ import java.nio.charset.Charset;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
public class CryptoHelper {
|
public class CryptoHelper {
|
||||||
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
public static final String FILETRANSFER = "?FILETRANSFERv1:";
|
||||||
|
final protected static char[] hexArray = "0123456789abcdef".toCharArray();
|
||||||
final protected static char[] vowels = "aeiou".toCharArray();
|
final protected static char[] vowels = "aeiou".toCharArray();
|
||||||
final protected static char[] consonants = "bcdfghjklmnpqrstvwxyz"
|
final protected static char[] consonants = "bcdfghjklmnpqrstvwxyz"
|
||||||
.toCharArray();
|
.toCharArray();
|
||||||
|
@ -24,7 +24,11 @@ public class CryptoHelper {
|
||||||
hexChars[j * 2] = hexArray[v >>> 4];
|
hexChars[j * 2] = hexArray[v >>> 4];
|
||||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||||
}
|
}
|
||||||
return new String(hexChars).toLowerCase();
|
return new String(hexChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] hexToBytes(String hexString) {
|
||||||
|
return new BigInteger(hexString, 16).toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String saslPlain(String username, String password) {
|
public static String saslPlain(String username, String password) {
|
||||||
|
@ -40,9 +44,8 @@ public class CryptoHelper {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String saslDigestMd5(Account account, String challenge) {
|
public static String saslDigestMd5(Account account, String challenge, SecureRandom random) {
|
||||||
try {
|
try {
|
||||||
Random random = new SecureRandom();
|
|
||||||
String[] challengeParts = new String(Base64.decode(challenge,
|
String[] challengeParts = new String(Base64.decode(challenge,
|
||||||
Base64.DEFAULT)).split(",");
|
Base64.DEFAULT)).split(",");
|
||||||
String nonce = "";
|
String nonce = "";
|
||||||
|
@ -84,12 +87,11 @@ public class CryptoHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String randomMucName() {
|
public static String randomMucName(SecureRandom random) {
|
||||||
Random random = new SecureRandom();
|
|
||||||
return randomWord(3, random) + "." + randomWord(7, random);
|
return randomWord(3, random) + "." + randomWord(7, random);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String randomWord(int lenght, Random random) {
|
protected static String randomWord(int lenght, SecureRandom random) {
|
||||||
StringBuilder builder = new StringBuilder(lenght);
|
StringBuilder builder = new StringBuilder(lenght);
|
||||||
for (int i = 0; i < lenght; ++i) {
|
for (int i = 0; i < lenght; ++i) {
|
||||||
if (i % 2 == 0) {
|
if (i % 2 == 0) {
|
||||||
|
|
326
src/eu/siacs/conversations/utils/PRNGFixes.java
Normal file
326
src/eu/siacs/conversations/utils/PRNGFixes.java
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.SecureRandomSpi;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes for the output of the default PRNG having low entropy.
|
||||||
|
*
|
||||||
|
* The fixes need to be applied via {@link #apply()} before any use of Java
|
||||||
|
* Cryptography Architecture primitives. A good place to invoke them is in the
|
||||||
|
* application's {@code onCreate}.
|
||||||
|
*/
|
||||||
|
public final class PRNGFixes {
|
||||||
|
|
||||||
|
private static final int VERSION_CODE_JELLY_BEAN = 16;
|
||||||
|
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
|
||||||
|
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
|
||||||
|
getBuildFingerprintAndDeviceSerial();
|
||||||
|
|
||||||
|
/** Hidden constructor to prevent instantiation. */
|
||||||
|
private PRNGFixes() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies all fixes.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if a fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
public static void apply() {
|
||||||
|
applyOpenSSLFix();
|
||||||
|
installLinuxPRNGSecureRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
|
||||||
|
* fix is not needed.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
private static void applyOpenSSLFix() throws SecurityException {
|
||||||
|
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|
||||||
|
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
|
||||||
|
// No need to apply the fix
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Mix in the device- and invocation-specific seed.
|
||||||
|
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||||
|
.getMethod("RAND_seed", byte[].class)
|
||||||
|
.invoke(null, generateSeed());
|
||||||
|
|
||||||
|
// Mix output of Linux PRNG into OpenSSL's PRNG
|
||||||
|
int bytesRead = (Integer) Class.forName(
|
||||||
|
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||||
|
.getMethod("RAND_load_file", String.class, long.class)
|
||||||
|
.invoke(null, "/dev/urandom", 1024);
|
||||||
|
if (bytesRead != 1024) {
|
||||||
|
throw new IOException(
|
||||||
|
"Unexpected number of bytes read from Linux PRNG: "
|
||||||
|
+ bytesRead);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
|
||||||
|
* default. Does nothing if the implementation is already the default or if
|
||||||
|
* there is not need to install the implementation.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
private static void installLinuxPRNGSecureRandom()
|
||||||
|
throws SecurityException {
|
||||||
|
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
||||||
|
// No need to apply the fix
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install a Linux PRNG-based SecureRandom implementation as the
|
||||||
|
// default, if not yet installed.
|
||||||
|
Provider[] secureRandomProviders =
|
||||||
|
Security.getProviders("SecureRandom.SHA1PRNG");
|
||||||
|
if ((secureRandomProviders == null)
|
||||||
|
|| (secureRandomProviders.length < 1)
|
||||||
|
|| (!LinuxPRNGSecureRandomProvider.class.equals(
|
||||||
|
secureRandomProviders[0].getClass()))) {
|
||||||
|
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that new SecureRandom() and
|
||||||
|
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
|
||||||
|
// by the Linux PRNG-based SecureRandom implementation.
|
||||||
|
SecureRandom rng1 = new SecureRandom();
|
||||||
|
if (!LinuxPRNGSecureRandomProvider.class.equals(
|
||||||
|
rng1.getProvider().getClass())) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"new SecureRandom() backed by wrong Provider: "
|
||||||
|
+ rng1.getProvider().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureRandom rng2;
|
||||||
|
try {
|
||||||
|
rng2 = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new SecurityException("SHA1PRNG not available", e);
|
||||||
|
}
|
||||||
|
if (!LinuxPRNGSecureRandomProvider.class.equals(
|
||||||
|
rng2.getProvider().getClass())) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
|
||||||
|
+ " Provider: " + rng2.getProvider().getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code Provider} of {@code SecureRandom} engines which pass through
|
||||||
|
* all requests to the Linux PRNG.
|
||||||
|
*/
|
||||||
|
private static class LinuxPRNGSecureRandomProvider extends Provider {
|
||||||
|
|
||||||
|
public LinuxPRNGSecureRandomProvider() {
|
||||||
|
super("LinuxPRNG",
|
||||||
|
1.0,
|
||||||
|
"A Linux-specific random number provider that uses"
|
||||||
|
+ " /dev/urandom");
|
||||||
|
// Although /dev/urandom is not a SHA-1 PRNG, some apps
|
||||||
|
// explicitly request a SHA1PRNG SecureRandom and we thus need to
|
||||||
|
// prevent them from getting the default implementation whose output
|
||||||
|
// may have low entropy.
|
||||||
|
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
|
||||||
|
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
|
||||||
|
* ({@code /dev/urandom}).
|
||||||
|
*/
|
||||||
|
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
|
||||||
|
* are passed through to the Linux PRNG (/dev/urandom). Instances of
|
||||||
|
* this class seed themselves by mixing in the current time, PID, UID,
|
||||||
|
* build fingerprint, and hardware serial number (where available) into
|
||||||
|
* Linux PRNG.
|
||||||
|
*
|
||||||
|
* Concurrency: Read requests to the underlying Linux PRNG are
|
||||||
|
* serialized (on sLock) to ensure that multiple threads do not get
|
||||||
|
* duplicated PRNG output.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final File URANDOM_FILE = new File("/dev/urandom");
|
||||||
|
|
||||||
|
private static final Object sLock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input stream for reading from Linux PRNG or {@code null} if not yet
|
||||||
|
* opened.
|
||||||
|
*
|
||||||
|
* @GuardedBy("sLock")
|
||||||
|
*/
|
||||||
|
private static DataInputStream sUrandomIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output stream for writing to Linux PRNG or {@code null} if not yet
|
||||||
|
* opened.
|
||||||
|
*
|
||||||
|
* @GuardedBy("sLock")
|
||||||
|
*/
|
||||||
|
private static OutputStream sUrandomOut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this engine instance has been seeded. This is needed because
|
||||||
|
* each instance needs to seed itself if the client does not explicitly
|
||||||
|
* seed it.
|
||||||
|
*/
|
||||||
|
private boolean mSeeded;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineSetSeed(byte[] bytes) {
|
||||||
|
try {
|
||||||
|
OutputStream out;
|
||||||
|
synchronized (sLock) {
|
||||||
|
out = getUrandomOutputStream();
|
||||||
|
}
|
||||||
|
out.write(bytes);
|
||||||
|
out.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// On a small fraction of devices /dev/urandom is not writable.
|
||||||
|
// Log and ignore.
|
||||||
|
Log.w(PRNGFixes.class.getSimpleName(),
|
||||||
|
"Failed to mix seed into " + URANDOM_FILE);
|
||||||
|
} finally {
|
||||||
|
mSeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineNextBytes(byte[] bytes) {
|
||||||
|
if (!mSeeded) {
|
||||||
|
// Mix in the device- and invocation-specific seed.
|
||||||
|
engineSetSeed(generateSeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DataInputStream in;
|
||||||
|
synchronized (sLock) {
|
||||||
|
in = getUrandomInputStream();
|
||||||
|
}
|
||||||
|
synchronized (in) {
|
||||||
|
in.readFully(bytes);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Failed to read from " + URANDOM_FILE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] engineGenerateSeed(int size) {
|
||||||
|
byte[] seed = new byte[size];
|
||||||
|
engineNextBytes(seed);
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataInputStream getUrandomInputStream() {
|
||||||
|
synchronized (sLock) {
|
||||||
|
if (sUrandomIn == null) {
|
||||||
|
// NOTE: Consider inserting a BufferedInputStream between
|
||||||
|
// DataInputStream and FileInputStream if you need higher
|
||||||
|
// PRNG output performance and can live with future PRNG
|
||||||
|
// output being pulled into this process prematurely.
|
||||||
|
try {
|
||||||
|
sUrandomIn = new DataInputStream(
|
||||||
|
new FileInputStream(URANDOM_FILE));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException("Failed to open "
|
||||||
|
+ URANDOM_FILE + " for reading", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sUrandomIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getUrandomOutputStream() throws IOException {
|
||||||
|
synchronized (sLock) {
|
||||||
|
if (sUrandomOut == null) {
|
||||||
|
sUrandomOut = new FileOutputStream(URANDOM_FILE);
|
||||||
|
}
|
||||||
|
return sUrandomOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a device- and invocation-specific seed to be mixed into the
|
||||||
|
* Linux PRNG.
|
||||||
|
*/
|
||||||
|
private static byte[] generateSeed() {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream seedBufferOut =
|
||||||
|
new DataOutputStream(seedBuffer);
|
||||||
|
seedBufferOut.writeLong(System.currentTimeMillis());
|
||||||
|
seedBufferOut.writeLong(System.nanoTime());
|
||||||
|
seedBufferOut.writeInt(Process.myPid());
|
||||||
|
seedBufferOut.writeInt(Process.myUid());
|
||||||
|
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
|
||||||
|
seedBufferOut.close();
|
||||||
|
return seedBuffer.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException("Failed to generate seed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the hardware serial number of this device.
|
||||||
|
*
|
||||||
|
* @return serial number or {@code null} if not available.
|
||||||
|
*/
|
||||||
|
private static String getDeviceSerialNumber() {
|
||||||
|
// We're using the Reflection API because Build.SERIAL is only available
|
||||||
|
// since API Level 9 (Gingerbread, Android 2.3).
|
||||||
|
try {
|
||||||
|
return (String) Build.class.getField("SERIAL").get(null);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getBuildFingerprintAndDeviceSerial() {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
String fingerprint = Build.FINGERPRINT;
|
||||||
|
if (fingerprint != null) {
|
||||||
|
result.append(fingerprint);
|
||||||
|
}
|
||||||
|
String serial = getDeviceSerialNumber();
|
||||||
|
if (serial != null) {
|
||||||
|
result.append(serial);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return result.toString().getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("UTF-8 encoding not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ import android.os.PowerManager.WakeLock;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.DNSHelper;
|
import eu.siacs.conversations.utils.DNSHelper;
|
||||||
import eu.siacs.conversations.utils.zlib.ZLibOutputStream;
|
import eu.siacs.conversations.utils.zlib.ZLibOutputStream;
|
||||||
|
@ -63,7 +64,7 @@ public class XmppConnection implements Runnable {
|
||||||
|
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
|
||||||
private SecureRandom random = new SecureRandom();
|
private SecureRandom mRandom;
|
||||||
|
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
private XmlReader tagReader;
|
private XmlReader tagReader;
|
||||||
|
@ -100,9 +101,10 @@ public class XmppConnection implements Runnable {
|
||||||
private OnTLSExceptionReceived tlsListener = null;
|
private OnTLSExceptionReceived tlsListener = null;
|
||||||
private OnBindListener bindListener = null;
|
private OnBindListener bindListener = null;
|
||||||
|
|
||||||
public XmppConnection(Account account, PowerManager pm) {
|
public XmppConnection(Account account, XmppConnectionService service) {
|
||||||
|
this.mRandom = service.getRNG();
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
this.wakeLock = service.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
account.getJid());
|
account.getJid());
|
||||||
tagWriter = new TagWriter();
|
tagWriter = new TagWriter();
|
||||||
}
|
}
|
||||||
|
@ -248,7 +250,7 @@ public class XmppConnection implements Runnable {
|
||||||
response.setAttribute("xmlns",
|
response.setAttribute("xmlns",
|
||||||
"urn:ietf:params:xml:ns:xmpp-sasl");
|
"urn:ietf:params:xml:ns:xmpp-sasl");
|
||||||
response.setContent(CryptoHelper.saslDigestMd5(account,
|
response.setContent(CryptoHelper.saslDigestMd5(account,
|
||||||
challange));
|
challange,mRandom));
|
||||||
tagWriter.writeElement(response);
|
tagWriter.writeElement(response);
|
||||||
} else if (nextTag.isStart("enabled")) {
|
} else if (nextTag.isStart("enabled")) {
|
||||||
this.stanzasSent = 0;
|
this.stanzasSent = 0;
|
||||||
|
@ -772,7 +774,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String nextRandomId() {
|
private String nextRandomId() {
|
||||||
return new BigInteger(50, random).toString(32);
|
return new BigInteger(50, mRandom).toString(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
|
public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
|
||||||
|
|
|
@ -9,11 +9,11 @@ import java.util.Map.Entry;
|
||||||
|
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
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.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
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.jingle.stanzas.Content;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
||||||
|
@ -24,7 +24,7 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
public class JingleConnection {
|
public class JingleConnection {
|
||||||
|
|
||||||
private final String[] extensions = {"webp","jpeg","jpg","png"};
|
private final String[] extensions = {"webp","jpeg","jpg","png"};
|
||||||
private final String[] cryptoExtensions = {"pgp","gpg"};
|
private final String[] cryptoExtensions = {"pgp","gpg","otr"};
|
||||||
|
|
||||||
private JingleConnectionManager mJingleConnectionManager;
|
private JingleConnectionManager mJingleConnectionManager;
|
||||||
private XmppConnectionService mXmppConnectionService;
|
private XmppConnectionService mXmppConnectionService;
|
||||||
|
@ -244,6 +244,7 @@ public class JingleConnection {
|
||||||
Element fileNameElement = fileOffer.findChild("name");
|
Element fileNameElement = fileOffer.findChild("name");
|
||||||
if (fileNameElement!=null) {
|
if (fileNameElement!=null) {
|
||||||
boolean supportedFile = false;
|
boolean supportedFile = false;
|
||||||
|
Log.d("xmppService","file offer: "+fileNameElement.getContent());
|
||||||
String[] filename = fileNameElement.getContent().toLowerCase().split("\\.");
|
String[] filename = fileNameElement.getContent().toLowerCase().split("\\.");
|
||||||
if (Arrays.asList(this.extensions).contains(filename[filename.length - 1])) {
|
if (Arrays.asList(this.extensions).contains(filename[filename.length - 1])) {
|
||||||
supportedFile = true;
|
supportedFile = true;
|
||||||
|
@ -251,7 +252,12 @@ public class JingleConnection {
|
||||||
if (filename.length == 3) {
|
if (filename.length == 3) {
|
||||||
if (Arrays.asList(this.extensions).contains(filename[filename.length -2])) {
|
if (Arrays.asList(this.extensions).contains(filename[filename.length -2])) {
|
||||||
supportedFile = true;
|
supportedFile = true;
|
||||||
this.message.setEncryption(Message.ENCRYPTION_PGP);
|
if (filename[filename.length - 1].equals("otr")) {
|
||||||
|
Log.d("xmppService","receiving otr file");
|
||||||
|
this.message.setEncryption(Message.ENCRYPTION_OTR);
|
||||||
|
} else {
|
||||||
|
this.message.setEncryption(Message.ENCRYPTION_PGP);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,6 +275,9 @@ public class JingleConnection {
|
||||||
this.mXmppConnectionService.updateUi(conversation, true);
|
this.mXmppConnectionService.updateUi(conversation, true);
|
||||||
}
|
}
|
||||||
this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
|
this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
|
||||||
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
|
this.file.setKey(conversation.getSymmetricKey());
|
||||||
|
}
|
||||||
this.file.setExpectedSize(size);
|
this.file.setExpectedSize(size);
|
||||||
} else {
|
} else {
|
||||||
this.sendCancel();
|
this.sendCancel();
|
||||||
|
@ -287,7 +296,14 @@ public class JingleConnection {
|
||||||
if (message.getType() == Message.TYPE_IMAGE) {
|
if (message.getType() == Message.TYPE_IMAGE) {
|
||||||
content.setTransportId(this.transportId);
|
content.setTransportId(this.transportId);
|
||||||
this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
|
this.file = this.mXmppConnectionService.getFileBackend().getJingleFile(message,false);
|
||||||
content.setFileOffer(this.file);
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
|
Conversation conversation = this.message.getConversation();
|
||||||
|
this.mXmppConnectionService.renewSymmetricKey(conversation);
|
||||||
|
content.setFileOffer(this.file, true);
|
||||||
|
this.file.setKey(conversation.getSymmetricKey());
|
||||||
|
} else {
|
||||||
|
content.setFileOffer(this.file,false);
|
||||||
|
}
|
||||||
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());
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.security.Key;
|
||||||
|
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
public class JingleFile extends File {
|
public class JingleFile extends File {
|
||||||
|
|
||||||
|
@ -8,6 +14,7 @@ public class JingleFile extends File {
|
||||||
|
|
||||||
private long expectedSize = 0;
|
private long expectedSize = 0;
|
||||||
private String sha1sum;
|
private String sha1sum;
|
||||||
|
private Key aeskey;
|
||||||
|
|
||||||
public JingleFile(String path) {
|
public JingleFile(String path) {
|
||||||
super(path);
|
super(path);
|
||||||
|
@ -32,4 +39,23 @@ public class JingleFile extends File {
|
||||||
public void setSha1Sum(String sum) {
|
public void setSha1Sum(String sum) {
|
||||||
this.sha1sum = sum;
|
this.sha1sum = sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setKey(byte[] key) {
|
||||||
|
Log.d("xmppService","using aes key "+CryptoHelper.bytesToHex(key));
|
||||||
|
if (key.length>=32) {
|
||||||
|
byte[] secretKey = new byte[32];
|
||||||
|
System.arraycopy(key, 0, secretKey, 0, 32);
|
||||||
|
this.aeskey = new SecretKeySpec(key, "AES");
|
||||||
|
} else if (key.length>=16) {
|
||||||
|
byte[] secretKey = new byte[15];
|
||||||
|
System.arraycopy(key, 0, secretKey, 0, 16);
|
||||||
|
this.aeskey = new SecretKeySpec(key, "AES");
|
||||||
|
} else {
|
||||||
|
Log.d("xmppService","weird key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getKey() {
|
||||||
|
return this.aeskey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
|
||||||
public class JingleSocks5Transport extends JingleTransport {
|
public class JingleSocks5Transport extends JingleTransport {
|
||||||
|
@ -90,19 +91,23 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
FileInputStream fileInputStream = null;
|
InputStream fileInputStream = null;
|
||||||
try {
|
try {
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
digest.reset();
|
digest.reset();
|
||||||
fileInputStream = new FileInputStream(file);
|
fileInputStream = getInputStream(file);
|
||||||
int count;
|
int count;
|
||||||
|
long txbytes = 0;
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
while ((count = fileInputStream.read(buffer)) > 0) {
|
while ((count = fileInputStream.read(buffer)) != -1) {
|
||||||
|
txbytes += count;
|
||||||
outputStream.write(buffer, 0, count);
|
outputStream.write(buffer, 0, count);
|
||||||
digest.update(buffer, 0, count);
|
digest.update(buffer, 0, count);
|
||||||
|
Log.d("xmppService","tx bytes: "+txbytes);
|
||||||
}
|
}
|
||||||
outputStream.flush();
|
outputStream.flush();
|
||||||
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
||||||
|
//outputStream.close();
|
||||||
if (callback!=null) {
|
if (callback!=null) {
|
||||||
callback.onFileTransmitted(file);
|
callback.onFileTransmitted(file);
|
||||||
}
|
}
|
||||||
|
@ -110,8 +115,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO Auto-generated catch block
|
Log.d("xmppService","io exception: "+e.getMessage());
|
||||||
e.printStackTrace();
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -141,36 +145,30 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
inputStream.skip(45);
|
inputStream.skip(45);
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
OutputStream fileOutputStream = getOutputStream(file);
|
||||||
long remainingSize = file.getExpectedSize();
|
long remainingSize = file.getExpectedSize();
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
int count = buffer.length;
|
int count = buffer.length;
|
||||||
while(remainingSize > 0) {
|
//while(remainingSize > 0) {
|
||||||
if (remainingSize<=count) {
|
while((count = inputStream.read(buffer)) > 0) {
|
||||||
count = (int) remainingSize;
|
Log.d("xmppService","remaining size: "+remainingSize+" reading "+count+" bytes");
|
||||||
}
|
count = inputStream.read(buffer);
|
||||||
count = inputStream.read(buffer, 0, count);
|
if (count!=-1) {
|
||||||
if (count==-1) {
|
|
||||||
// TODO throw exception
|
|
||||||
} else {
|
|
||||||
fileOutputStream.write(buffer, 0, count);
|
fileOutputStream.write(buffer, 0, count);
|
||||||
digest.update(buffer, 0, count);
|
digest.update(buffer, 0, count);
|
||||||
remainingSize-=count;
|
|
||||||
}
|
}
|
||||||
|
remainingSize-=count;
|
||||||
}
|
}
|
||||||
fileOutputStream.flush();
|
fileOutputStream.flush();
|
||||||
fileOutputStream.close();
|
fileOutputStream.close();
|
||||||
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
||||||
callback.onFileTransmitted(file);
|
callback.onFileTransmitted(file);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
// TODO Auto-generated catch block
|
Log.d("xmppService","file not found exception");
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO Auto-generated catch block
|
Log.d("xmppService","io exception: "+e.getMessage());
|
||||||
e.printStackTrace();
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
// TODO Auto-generated catch block
|
Log.d("xmppService","no such algo"+e.getMessage());
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
|
@ -1,7 +1,66 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.CipherOutputStream;
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
public abstract class JingleTransport {
|
public abstract class JingleTransport {
|
||||||
public abstract void connect(final OnTransportConnected callback);
|
public abstract void connect(final OnTransportConnected callback);
|
||||||
public abstract void receive(final JingleFile file, final OnFileTransmitted callback);
|
public abstract void receive(final JingleFile file, final OnFileTransmitted callback);
|
||||||
public abstract void send(final JingleFile file, final OnFileTransmitted callback);
|
public abstract void send(final JingleFile file, final OnFileTransmitted callback);
|
||||||
|
|
||||||
|
protected InputStream getInputStream(JingleFile file) throws FileNotFoundException {
|
||||||
|
if (file.getKey() == null) {
|
||||||
|
return new FileInputStream(file);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, file.getKey());
|
||||||
|
Log.d("xmppService","opening encrypted input stream");
|
||||||
|
return new CipherInputStream(new FileInputStream(file), cipher);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.d("xmppService","no such algo: "+e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
Log.d("xmppService","no such padding: "+e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
Log.d("xmppService","invalid key: "+e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OutputStream getOutputStream(JingleFile file) throws FileNotFoundException {
|
||||||
|
if (file.getKey() == null) {
|
||||||
|
return new FileOutputStream(file);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, file.getKey());
|
||||||
|
Log.d("xmppService","opening encrypted output stream");
|
||||||
|
return new CipherOutputStream(new FileOutputStream(file), cipher);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.d("xmppService","no such algo: "+e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
Log.d("xmppService","no such padding: "+e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
Log.d("xmppService","invalid key: "+e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,16 @@ public class Content extends Element {
|
||||||
this.transportId = sid;
|
this.transportId = sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFileOffer(JingleFile actualFile) {
|
public void setFileOffer(JingleFile actualFile, boolean otr) {
|
||||||
Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3");
|
Element description = this.addChild("description", "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(""+actualFile.getSize());
|
file.addChild("size").setContent(""+actualFile.getSize());
|
||||||
file.addChild("name").setContent(actualFile.getName());
|
if (otr) {
|
||||||
|
file.addChild("name").setContent(actualFile.getName()+".otr");
|
||||||
|
} else {
|
||||||
|
file.addChild("name").setContent(actualFile.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element getFileOffer() {
|
public Element getFileOffer() {
|
||||||
|
|
Loading…
Reference in a new issue