/** * Copyright (C) 2013 Jonathan Gillett, Joseph Heron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.tinfoil.sms.crypto; import java.security.Security; import java.util.ArrayList; import org.strippedcastle.jce.provider.BouncyCastleProvider; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.text.InputType; import android.util.Log; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Toast; import com.tinfoil.sms.R; import com.tinfoil.sms.dataStructures.Message; import com.tinfoil.sms.dataStructures.Number; import com.tinfoil.sms.dataStructures.TrustedContact; import com.tinfoil.sms.database.DBAccessor; import com.tinfoil.sms.settings.EditNumber; import com.tinfoil.sms.sms.ConversationView; import com.tinfoil.sms.utility.OnKeyExchangeResolvedListener; import com.tinfoil.sms.utility.SMSUtility; /** * A class that creates a thread to manage a user's exchange of keys with * contacts. It will go through each contact that the user wishes to exchange * keys with and queue a key exchange message. This thread only handles the * first phase of the key exchange. The second phase is handled in the * MessagerReceiver. */ public class ExchangeKey implements Runnable { //public static ProgressDialog keyDialog; private ArrayList<String> untrusted; private ArrayList<String> trusted; private Number number; private Activity activity; private TrustedContact trustedContact; private OnKeyExchangeResolvedListener listener; private DBAccessor dba; /* Register spongycastle as the most preferred security provider */ static { Security.insertProviderAt(new BouncyCastleProvider(), 1); } /** * Used to setup the a key exchange for a single contact or to untrust a * single contact. * @param trusted The number of the contact to send a key exchange to, * do not sent a key exchange if null * @param untrusted The number of the contact to un-trust, no contact is * untrusted. */ public void startThread(Activity activity, final String trusted, final String untrusted) { this.activity = activity; this.trusted = new ArrayList<String>(); this.untrusted = new ArrayList<String>(); if(trusted != null) { this.trusted.add(trusted); } if(untrusted != null) { this.untrusted.add(untrusted); } dba = new DBAccessor(activity); Thread thread = new Thread(this); thread.start(); } public void run() { /* * This is actually how removing contacts from trusted should look since it is just a * deletion of keys. We don't care if the contact will now fail to decrypt messages that * is the user's problem */ if (this.untrusted != null) { for (int i = 0; i < this.untrusted.size(); i++) { //untrusted.get(i).clearPublicKey(); this.number = dba.getNumber(this.untrusted.get(i)); this.number.clearPublicKey(); //set the initiator flag to false this.number.setInitiator(false); dba.updateKey(this.number); } } //TODO give a status update for each person sent and received /* * Start Key exchanges 1 by 1, messages are prepared and then placed in * the messaging queue. */ boolean invalid = false; if (this.trusted != null) { for (int i = 0; i < this.trusted.size(); i++) { number = dba.getNumber(trusted.get(i)); if(number != null) { if (SMSUtility.checksharedSecret(number.getSharedInfo1()) && SMSUtility.checksharedSecret(number.getSharedInfo2())) { Log.v("S1", number.getSharedInfo1()); Log.v("S2", number.getSharedInfo2()); sendKeyExchange(dba, number, true); } else { invalid = true; //Toast.makeText(c, "Invalid shared secrets", Toast.LENGTH_LONG).show(); Log.v("Shared Secret", "Invalid shared secrets"); } } else { //TODO give error message } } } if (invalid && activity != null) { trustedContact = dba.getRow(number.getNumber()); /* * Get the shared secrets from the user. */ activity.runOnUiThread(new Runnable() { public void run() { //Toast.makeText(activity, "Shared secrets must be set prior to key exchange", Toast.LENGTH_LONG).show(); AlertDialog.Builder builder = new AlertDialog.Builder(activity); LinearLayout linearLayout = new LinearLayout(activity); linearLayout.setOrientation(LinearLayout.VERTICAL); final EditText sharedSecret1 = new EditText(activity); sharedSecret1.setHint(R.string.shared_secret_hint_1); sharedSecret1.setMaxLines(EditNumber.SHARED_INFO_MAX); sharedSecret1.setInputType(InputType.TYPE_CLASS_TEXT); linearLayout.addView(sharedSecret1); final EditText sharedSecret2 = new EditText(activity); sharedSecret2.setHint(R.string.shared_secret_hint_2); sharedSecret2.setMaxLines(EditNumber.SHARED_INFO_MAX); sharedSecret2.setInputType(InputType.TYPE_CLASS_TEXT); linearLayout.addView(sharedSecret2); builder.setMessage(activity.getString(R.string.set_shared_secrets) + " " + trustedContact.getName() + ", " + number.getNumber()) .setCancelable(true) .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { //Save the shared secrets String s1 = sharedSecret1.getText().toString(); String s2 = sharedSecret2.getText().toString(); if (SMSUtility.checksharedSecret(s1) && SMSUtility.checksharedSecret(s2)) { sendKeyExchange(dba, number, s1, s2, true); } else { Toast.makeText(activity, R.string.invalid_secrets, Toast.LENGTH_LONG).show(); } }}) .setOnCancelListener(new OnCancelListener(){ @Override public void onCancel(DialogInterface arg0) { //Cancel the key exchange Toast.makeText(activity, R.string.key_exchange_cancelled, Toast.LENGTH_LONG).show(); } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { //Cancel the key exchange Toast.makeText(activity, R.string.key_exchange_cancelled, Toast.LENGTH_LONG).show(); }}); AlertDialog alert = builder.create(); alert.setView(linearLayout); alert.show(); } }); } ConversationView.updateList(activity, ConversationView.messageViewActive); if((trusted == null || trusted.size() == 0) && listener != null) { Log.v("onKeyExchangeResolved", "TRUE"); listener.onKeyExchangeResolved(); } //Dismisses the load dialog since the load is finished //keyDialog.dismiss(); } public void setOnFinishedTaskListener(OnKeyExchangeResolvedListener listener) { this.listener = listener; } public void removeOnFinishedTaskListener() { this.listener = null; } /** * Set the share and then make the key exchange. Note you must check whether the * share secrets are valid first. * @param dba The database interface. * @param number The number in use * @param s1 The first new shared secret * @param s2 The second new shared secret * @param initiator Whether the user is the key exchange initiator. */ public static void sendKeyExchange(DBAccessor dba, Number number, String s1, String s2, boolean initiator) { number.setSharedInfo1(s1); number.setSharedInfo2(s2); dba.updateNumberRow(number, number.getNumber(), number.getId()); sendKeyExchange(dba, number, initiator); } /** * Make the key exchange. Note you must check whether the share secrets are valid first. * @param dba The database interface. * @param number The number in use * @param initiator Whether the user is the key exchange initiator. */ public static void sendKeyExchange(DBAccessor dba, Number number, boolean initiator) { /* * Set the initiator flag since this user is starting the key exchange. */ number.setInitiator(initiator); dba.updateInitiator(number); String keyExchangeMessage = KeyExchange.sign(number, dba, SMSUtility.user); dba.addMessageToQueue(number.getNumber(), keyExchangeMessage, true); Message newMessage = new Message(keyExchangeMessage, true, Message.SENT_KEY_EXCHANGE_INIT); dba.addNewMessage(newMessage, number.getNumber(), false); } }