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); | ||||||
|  |  | ||||||
|  | @ -55,9 +55,13 @@ public class FileBackend { | ||||||
| 		String filename; | 		String filename; | ||||||
| 		if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) { | 		if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) { | ||||||
| 			filename = message.getUuid() + ".webp"; | 			filename = message.getUuid() + ".webp"; | ||||||
|  | 		} else { | ||||||
|  | 			if (message.getEncryption() == Message.ENCRYPTION_OTR) { | ||||||
|  | 				filename = message.getUuid() + ".webp"; | ||||||
| 			} else { | 			} else { | ||||||
| 				filename = message.getUuid() + ".webp.pgp"; | 				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) { | ||||||
|  | 				if (viewHolder.download_button != null) { | ||||||
| 					viewHolder.download_button.setVisibility(View.GONE); | 					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) { | ||||||
|  |  | ||||||
|  | @ -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,10 +252,15 @@ 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; | ||||||
|  | 							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); | 								this.message.setEncryption(Message.ENCRYPTION_PGP); | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
|  | 				} | ||||||
| 				if (supportedFile) { | 				if (supportedFile) { | ||||||
| 					long size = Long.parseLong(fileSize.getContent()); | 					long size = Long.parseLong(fileSize.getContent()); | ||||||
| 					message.setBody(""+size); | 					message.setBody(""+size); | ||||||
|  | @ -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,13 +25,17 @@ 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()); | ||||||
|  | 		if (otr) { | ||||||
|  | 			file.addChild("name").setContent(actualFile.getName()+".otr"); | ||||||
|  | 		} else { | ||||||
| 			file.addChild("name").setContent(actualFile.getName()); | 			file.addChild("name").setContent(actualFile.getName()); | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 	 | 	 | ||||||
| 	public Element getFileOffer() { | 	public Element getFileOffer() { | ||||||
| 		Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); | 		Element description = this.findChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 iNPUTmice
						iNPUTmice