basic pgp support.

This commit is contained in:
Daniel Gultsch 2014-02-28 00:22:56 +01:00
parent bfee69b00b
commit 37d1a53806
17 changed files with 558 additions and 77 deletions

View File

@ -9,9 +9,9 @@ package de.gultsch.chat;
public final class R { public final class R {
public static final class array { public static final class array {
public static final int conversation_encryption_type_entries=0x7f050000; public static final int conversation_encryption_type_entries=0x7f060000;
public static final int conversation_encryption_type_values=0x7f050001; public static final int conversation_encryption_type_values=0x7f060001;
public static final int manage_account_options=0x7f050002; public static final int manage_account_options=0x7f060002;
} }
public static final class attr { public static final class attr {
} }
@ -22,24 +22,25 @@ public final class R {
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here. screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
*/ */
public static final int activity_horizontal_margin=0x7f060000; public static final int activity_horizontal_margin=0x7f070000;
public static final int activity_vertical_margin=0x7f060001; public static final int activity_vertical_margin=0x7f070001;
} }
public static final class drawable { public static final class drawable {
public static final int es_slidingpane_shadow=0x7f020000; public static final int es_slidingpane_shadow=0x7f020000;
public static final int ic_action_add=0x7f020001; public static final int ic_action_add=0x7f020001;
public static final int ic_action_add_person=0x7f020002; public static final int ic_action_add_person=0x7f020002;
public static final int ic_action_delete=0x7f020003; public static final int ic_action_cancel_launchersize=0x7f020003;
public static final int ic_action_refresh=0x7f020004; public static final int ic_action_delete=0x7f020004;
public static final int ic_action_secure=0x7f020005; public static final int ic_action_refresh=0x7f020005;
public static final int ic_action_send=0x7f020006; public static final int ic_action_secure=0x7f020006;
public static final int ic_action_send_now=0x7f020007; public static final int ic_action_send=0x7f020007;
public static final int ic_action_unsecure=0x7f020008; public static final int ic_action_send_now=0x7f020008;
public static final int ic_launcher=0x7f020009; public static final int ic_action_unsecure=0x7f020009;
public static final int ic_profile=0x7f02000a; public static final int ic_launcher=0x7f02000a;
public static final int message_border=0x7f02000b; public static final int ic_profile=0x7f02000b;
public static final int notification=0x7f02000c; public static final int message_border=0x7f02000c;
public static final int section_header=0x7f02000d; public static final int notification=0x7f02000d;
public static final int section_header=0x7f02000e;
} }
public static final class id { public static final class id {
public static final int account_confirm_password_desc=0x7f0a001c; public static final int account_confirm_password_desc=0x7f0a001c;
@ -57,9 +58,10 @@ public final class R {
public static final int action_add_account=0x7f0a0034; public static final int action_add_account=0x7f0a0034;
public static final int action_archive=0x7f0a002e; public static final int action_archive=0x7f0a002e;
public static final int action_details=0x7f0a002d; public static final int action_details=0x7f0a002d;
public static final int action_refresh_contacts=0x7f0a0038; public static final int action_refresh_contacts=0x7f0a0039;
public static final int action_security=0x7f0a002c; public static final int action_security=0x7f0a002c;
public static final int action_settings=0x7f0a0030; public static final int action_settings=0x7f0a0030;
public static final int announce_pgp=0x7f0a0038;
public static final int contactList=0x7f0a0006; public static final int contactList=0x7f0a0006;
public static final int contact_display_name=0x7f0a0008; public static final int contact_display_name=0x7f0a0008;
public static final int contact_jid=0x7f0a0009; public static final int contact_jid=0x7f0a0009;
@ -123,17 +125,21 @@ public final class R {
public static final int newconversation=0x7f090004; public static final int newconversation=0x7f090004;
} }
public static final class string { public static final class string {
public static final int action_accounts=0x7f070003; public static final int action_accounts=0x7f050005;
public static final int action_add=0x7f070002; public static final int action_add=0x7f050004;
public static final int action_add_account=0x7f070007; public static final int action_add_account=0x7f050009;
public static final int action_archive=0x7f070004; public static final int action_archive=0x7f050006;
public static final int action_details=0x7f070005; public static final int action_details=0x7f050007;
public static final int action_secure=0x7f070006; public static final int action_secure=0x7f050008;
public static final int action_settings=0x7f070001; public static final int action_settings=0x7f050003;
public static final int app_name=0x7f070000; public static final int announce_pgp=0x7f05000d;
public static final int just_now=0x7f070009; public static final int app_name=0x7f050002;
public static final int sending=0x7f07000a; public static final int encrypted_message=0x7f05000e;
public static final int title_activity_new_conversation=0x7f070008; public static final int just_now=0x7f05000b;
public static final int openpgp_install_openkeychain_via=0x7f050001;
public static final int openpgp_list_preference_none=0x7f050000;
public static final int sending=0x7f05000c;
public static final int title_activity_new_conversation=0x7f05000a;
} }
public static final class style { public static final class style {
/** /**

View File

@ -84,5 +84,4 @@
android:typeface="monospace"/> android:typeface="monospace"/>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -24,6 +24,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Hi, how are you?" android:text="Hi, how are you?"
android:autoLink="all"
android:textSize="16sp" android:textSize="16sp"
android:id="@+id/message_body" android:id="@+id/message_body"
android:textColor="#333333"/> android:textColor="#333333"/>

View File

@ -26,6 +26,7 @@
android:text="Hi, how are you?" android:text="Hi, how are you?"
android:textSize="16sp" android:textSize="16sp"
android:id="@+id/message_body" android:id="@+id/message_body"
android:autoLink="all"
android:textColor="#333333"/> android:textColor="#333333"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -16,4 +16,10 @@
android:showAsAction="always" android:showAsAction="always"
android:visible="false"/> android:visible="false"/>
<item
android:id="@+id/announce_pgp"
android:orderInCategory="75"
android:showAsAction="never"
android:title="@string/announce_pgp" />
</menu> </menu>

View File

@ -12,4 +12,6 @@
<string name="title_activity_new_conversation">New Conversation</string> <string name="title_activity_new_conversation">New Conversation</string>
<string name="just_now">just now</string> <string name="just_now">just now</string>
<string name="sending">sending&#8230;</string> <string name="sending">sending&#8230;</string>
<string name="announce_pgp">Renew PGP announcement</string>
<string name="encrypted_message">This message is encrypted. Click to decrypt.</string>
</resources> </resources>

View File

@ -93,8 +93,6 @@ public class OtrEngine implements OtrEngineHost {
e.printStackTrace(); e.printStackTrace();
} catch (InvalidKeySpecException e) { } catch (InvalidKeySpecException e) {
e.printStackTrace(); e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} }
} }

View File

@ -0,0 +1,148 @@
package de.gultsch.chat.crypto;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpConstants;
import android.app.PendingIntent;
import android.os.Bundle;
import android.util.Log;
public class PgpEngine {
private OpenPgpApi api;
public PgpEngine(OpenPgpApi api) {
this.api = api;
}
public String decrypt(String message) throws UserInputRequiredException,
OpenPgpException {
InputStream is = new ByteArrayInputStream(message.getBytes());
ByteArrayOutputStream os = new ByteArrayOutputStream();
Bundle result = api.decryptAndVerify(is, os);
switch (result.getInt(OpenPgpConstants.RESULT_CODE)) {
case OpenPgpConstants.RESULT_CODE_SUCCESS:
return os.toString();
case OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED:
throw new UserInputRequiredException(
(PendingIntent) result
.getParcelable(OpenPgpConstants.RESULT_INTENT));
case OpenPgpConstants.RESULT_CODE_ERROR:
throw new OpenPgpException(
(OpenPgpError) result
.getParcelable(OpenPgpConstants.RESULT_ERRORS));
default:
return null;
}
}
public String encrypt(long keyId, String message) {
Bundle params = new Bundle();
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
long[] keyIds = { keyId };
params.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS, keyIds);
InputStream is = new ByteArrayInputStream(message.getBytes());
ByteArrayOutputStream os = new ByteArrayOutputStream();
Bundle result = api.encrypt(params, is, os);
StringBuilder encryptedMessageBody = new StringBuilder();
String[] lines = os.toString().split("\n");
for (int i = 3; i < lines.length - 1; ++i) {
encryptedMessageBody.append(lines[i].trim());
}
return encryptedMessageBody.toString();
}
public long fetchKeyId(String status, String signature)
throws OpenPgpException {
StringBuilder pgpSig = new StringBuilder();
pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
pgpSig.append('\n');
pgpSig.append("Hash: SHA1");
pgpSig.append('\n');
pgpSig.append('\n');
pgpSig.append(status);
pgpSig.append('\n');
pgpSig.append("-----BEGIN PGP SIGNATURE-----");
pgpSig.append('\n');
pgpSig.append('\n');
pgpSig.append(signature.replace("\n", "").trim());
pgpSig.append('\n');
pgpSig.append("-----END PGP SIGNATURE-----");
Bundle params = new Bundle();
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
ByteArrayOutputStream os = new ByteArrayOutputStream();
Bundle result = api.decryptAndVerify(params, is, os);
switch (result.getInt(OpenPgpConstants.RESULT_CODE)) {
case OpenPgpConstants.RESULT_CODE_SUCCESS:
OpenPgpSignatureResult sigResult = result
.getParcelable(OpenPgpConstants.RESULT_SIGNATURE);
return sigResult.getKeyId();
case OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED:
break;
case OpenPgpConstants.RESULT_CODE_ERROR:
throw new OpenPgpException(
(OpenPgpError) result
.getParcelable(OpenPgpConstants.RESULT_ERRORS));
}
return 0;
}
public String generateSignature(String status)
throws UserInputRequiredException {
Bundle params = new Bundle();
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
InputStream is = new ByteArrayInputStream(status.getBytes());
ByteArrayOutputStream os = new ByteArrayOutputStream();
Bundle result = api.sign(params, is, os);
StringBuilder signatureBuilder = new StringBuilder();
switch (result.getInt(OpenPgpConstants.RESULT_CODE)) {
case OpenPgpConstants.RESULT_CODE_SUCCESS:
String[] lines = os.toString().split("\n");
for (int i = 7; i < lines.length - 1; ++i) {
signatureBuilder.append(lines[i].trim());
}
break;
case OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED:
UserInputRequiredException exception = new UserInputRequiredException(
(PendingIntent) result
.getParcelable(OpenPgpConstants.RESULT_INTENT));
throw exception;
case OpenPgpConstants.RESULT_CODE_ERROR:
break;
}
return signatureBuilder.toString();
}
public class UserInputRequiredException extends Exception {
private static final long serialVersionUID = -6913480043269132016L;
private PendingIntent pi;
public UserInputRequiredException(PendingIntent pi) {
this.pi = pi;
}
public PendingIntent getPendingIntent() {
return this.pi;
}
}
public class OpenPgpException extends Exception {
private static final long serialVersionUID = -7324789703473056077L;
private OpenPgpError error;
public OpenPgpException(OpenPgpError openPgpError) {
this.error = openPgpError;
}
public OpenPgpError getOpenPgpError() {
return this.error;
}
}
}

View File

@ -137,8 +137,13 @@ public class Account extends AbstractEntity{
return keys; return keys;
} }
public void setKey(String keyName, String keyValue) throws JSONException { public boolean setKey(String keyName, String keyValue) {
this.keys.put(keyName, keyValue); try {
this.keys.put(keyName, keyValue);
return true;
} catch (JSONException e) {
return false;
}
} }
@Override @Override

View File

@ -221,6 +221,26 @@ public class Contact extends AbstractEntity implements Serializable {
} }
} }
public void setPgpKeyId(long keyId) {
try {
this.keys.put("pgp_keyid", keyId);
} catch (JSONException e) {
}
}
public long getPgpKeyId() {
if (this.keys.has("pgp_keyid")) {
try {
return this.keys.getLong("pgp_keyid");
} catch (JSONException e) {
return 0;
}
} else {
return 0;
}
}
public void setSubscriptionOption(int option) { public void setSubscriptionOption(int option) {
this.subscription |= 1 << option; this.subscription |= 1 << option;
} }

View File

@ -17,6 +17,7 @@ public class Message extends AbstractEntity {
public static final int ENCRYPTION_NONE = 0; public static final int ENCRYPTION_NONE = 0;
public static final int ENCRYPTION_PGP = 1; public static final int ENCRYPTION_PGP = 1;
public static final int ENCRYPTION_OTR = 2; public static final int ENCRYPTION_OTR = 2;
public static final int ENCRYPTION_DECRYPTED = 3;
public static String CONVERSATION = "conversationUuid"; public static String CONVERSATION = "conversationUuid";
public static String COUNTERPART = "counterpart"; public static String COUNTERPART = "counterpart";
@ -137,4 +138,7 @@ public class Message extends AbstractEntity {
this.encryption = encryption; this.encryption = encryption;
} }
public void setBody(String body) {
this.body = body;
}
} }

View File

@ -1,5 +1,16 @@
package de.gultsch.chat.services; package de.gultsch.chat.services;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
@ -8,11 +19,21 @@ import java.util.Hashtable;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.json.JSONException;
import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.openintents.openpgp.OpenPgpError;
import net.java.otr4j.OtrException; import net.java.otr4j.OtrException;
import net.java.otr4j.session.Session; import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionImpl;
import net.java.otr4j.session.SessionStatus; import net.java.otr4j.session.SessionStatus;
import de.gultsch.chat.crypto.PgpEngine;
import de.gultsch.chat.crypto.PgpEngine.OpenPgpException;
import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
import de.gultsch.chat.entities.Account; import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact; import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation; import de.gultsch.chat.entities.Conversation;
@ -46,7 +67,9 @@ import android.database.DatabaseUtils;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.RemoteException;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.util.Log; import android.util.Log;
@ -84,7 +107,12 @@ public class XmppConnectionService extends Service {
Message message = null; Message message = null;
boolean notify = false; boolean notify = false;
if ((packet.getType() == MessagePacket.TYPE_CHAT)) { if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
if (packet.hasChild("body") String pgpBody = MessageParser.getPgpBody(packet);
if (pgpBody != null) {
message = MessageParser.parsePgpChat(pgpBody, packet,
account, service);
notify = false;
} else if (packet.hasChild("body")
&& (packet.getBody().startsWith("?OTR"))) { && (packet.getBody().startsWith("?OTR"))) {
message = MessageParser.parseOtrChat(packet, account, message = MessageParser.parseOtrChat(packet, account,
service); service);
@ -164,6 +192,13 @@ public class XmppConnectionService extends Service {
if (convChangedListener != null) { if (convChangedListener != null) {
convChangedListener.onConversationListChanged(); convChangedListener.onConversationListChanged();
} }
if (account.getKeys().has("pgp_signature")) {
try {
sendPgpPresence(account, account.getKeys().getString("pgp_signature"));
} catch (JSONException e) {
//
}
}
} }
} }
}; };
@ -195,6 +230,18 @@ public class XmppConnectionService extends Service {
} else if (show.getContent().equals("dnd")) { } else if (show.getContent().equals("dnd")) {
contact.updatePresence(fromParts[1], Presences.DND); contact.updatePresence(fromParts[1], Presences.DND);
} }
Element x = packet.findChild("x");
if ((x != null)
&& (x.getAttribute("xmlns").equals("jabber:x:signed"))) {
try {
Log.d(LOGTAG,"pgp signature for contact" +packet.getAttribute("from"));
contact.setPgpKeyId(getPgpEngine().fetchKeyId(packet.findChild("status")
.getContent(), x.getContent()));
databaseBackend.updateContact(contact);
} catch (OpenPgpException e) {
Log.d(LOGTAG,"faulty pgp. just ignore");
}
}
databaseBackend.updateContact(contact); databaseBackend.updateContact(contact);
} else if (type.equals("unavailable")) { } else if (type.equals("unavailable")) {
if (fromParts.length != 2) { if (fromParts.length != 2) {
@ -242,6 +289,23 @@ public class XmppConnectionService extends Service {
} }
}; };
private OpenPgpServiceConnection pgpServiceConnection;
private PgpEngine mPgpEngine = null;
public PgpEngine getPgpEngine() {
if (pgpServiceConnection.isBound()) {
if (this.mPgpEngine == null) {
this.mPgpEngine = new PgpEngine(new OpenPgpApi(
getApplicationContext(),
pgpServiceConnection.getService()));
}
return mPgpEngine;
} else {
return null;
}
}
private void processRosterItems(Account account, Element elements) { private void processRosterItems(Account account, Element elements) {
for (Element item : elements.getChildren()) { for (Element item : elements.getChildren()) {
if (item.getName().equals("item")) { if (item.getName().equals("item")) {
@ -307,6 +371,9 @@ public class XmppConnectionService extends Service {
getContentResolver().registerContentObserver( getContentResolver().registerContentObserver(
ContactsContract.Contacts.CONTENT_URI, true, contactObserver); ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
this.pgpServiceConnection = new OpenPgpServiceConnection(
getApplicationContext(), "org.sufficientlysecure.keychain");
this.pgpServiceConnection.bindToService();
} }
@Override @Override
@ -332,7 +399,8 @@ public class XmppConnectionService extends Service {
return connection; return connection;
} }
public void sendMessage(Account account, Message message, String presence) { public void sendMessage(Message message, String presence) {
Account account = message.getConversation().getAccount();
Conversation conv = message.getConversation(); Conversation conv = message.getConversation();
boolean saveInDb = false; boolean saveInDb = false;
boolean addToConversation = false; boolean addToConversation = false;
@ -352,6 +420,25 @@ public class XmppConnectionService extends Service {
} }
saveInDb = true; saveInDb = true;
addToConversation = true; addToConversation = true;
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
long keyId = message.getConversation().getContact()
.getPgpKeyId();
packet = new MessagePacket();
packet.setType(MessagePacket.TYPE_CHAT);
packet.setFrom(message.getConversation().getAccount()
.getFullJid());
packet.setTo(message.getCounterpart());
packet.setBody("This is an XEP-0027 encryted message");
Element x = new Element("x");
x.setAttribute("xmlns", "jabber:x:encrypted");
x.setContent(this.getPgpEngine().encrypt(keyId,
message.getBody()));
packet.addChild(x);
account.getXmppConnection().sendMessagePacket(packet);
message.setStatus(Message.STATUS_SEND);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
saveInDb = true;
addToConversation = true;
} else { } else {
// don't encrypt // don't encrypt
if (message.getConversation().getMode() == Conversation.MODE_SINGLE) { if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
@ -777,6 +864,10 @@ public class XmppConnectionService extends Service {
databaseBackend.updateContact(contact); databaseBackend.updateContact(contact);
} }
public void updateMessage(Message message) {
databaseBackend.updateMessage(message);
}
public void createContact(Contact contact) { public void createContact(Contact contact) {
SharedPreferences sharedPref = PreferenceManager SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext()); .getDefaultSharedPreferences(getApplicationContext());
@ -841,4 +932,27 @@ public class XmppConnectionService extends Service {
Log.d(LOGTAG, packet.toString()); Log.d(LOGTAG, packet.toString());
contact.getAccount().getXmppConnection().sendPresencePacket(packet); contact.getAccount().getXmppConnection().sendPresencePacket(packet);
} }
public void sendPgpPresence(Account account, String signature) {
PresencePacket packet = new PresencePacket();
packet.setAttribute("from", account.getFullJid());
Element status = new Element("status");
status.setContent("online");
packet.addChild(status);
Element x = new Element("x");
x.setAttribute("xmlns", "jabber:x:signed");
x.setContent(signature);
packet.addChild(x);
account.getXmppConnection().sendPresencePacket(packet);
}
public void generatePgpAnnouncement(Account account)
throws PgpEngine.UserInputRequiredException {
if (account.getStatus() == Account.STATUS_ONLINE) {
String signature = getPgpEngine().generateSignature("online");
account.setKey("pgp_signature", signature);
databaseBackend.updateAccount(account);
sendPgpPresence(account, signature);
}
}
} }

View File

@ -5,8 +5,13 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpConstants;
import de.gultsch.chat.R; import de.gultsch.chat.R;
import de.gultsch.chat.R.id; import de.gultsch.chat.R.id;
import de.gultsch.chat.crypto.PgpEngine;
import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
import de.gultsch.chat.entities.Account; import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact; import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation; import de.gultsch.chat.entities.Conversation;
@ -20,6 +25,7 @@ import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
@ -45,7 +51,8 @@ public class ConversationActivity extends XmppActivity {
public static final String VIEW_CONVERSATION = "viewConversation"; public static final String VIEW_CONVERSATION = "viewConversation";
public static final String CONVERSATION = "conversationUuid"; public static final String CONVERSATION = "conversationUuid";
public static final int INSERT_CONTACT = 0x9889; public static final int REQUEST_SEND_MESSAGE = 0x75441;
public static final int REQUEST_DECRYPT_PGP = 0x76783;
protected SlidingPaneLayout spl; protected SlidingPaneLayout spl;
@ -354,6 +361,9 @@ public class ConversationActivity extends XmppActivity {
case Message.ENCRYPTION_PGP: case Message.ENCRYPTION_PGP:
popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true); popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true);
break; break;
case Message.ENCRYPTION_DECRYPTED:
popup.getMenu().findItem(R.id.encryption_choice_pgp).setChecked(true);
break;
default: default:
popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true); popup.getMenu().findItem(R.id.encryption_choice_none).setChecked(true);
break; break;
@ -459,11 +469,4 @@ public class ConversationActivity extends XmppActivity {
} }
} }
} }
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode==INSERT_CONTACT) {
Log.d("xmppService","contact inserted");
this.contactInserted = true;
}
}
} }

View File

@ -3,17 +3,16 @@ package de.gultsch.chat.ui;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.crypto.spec.PSource;
import net.java.otr4j.OtrException;
import net.java.otr4j.session.SessionStatus; import net.java.otr4j.session.SessionStatus;
import de.gultsch.chat.R; import de.gultsch.chat.R;
import de.gultsch.chat.crypto.PgpEngine.OpenPgpException;
import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
import de.gultsch.chat.entities.Contact; import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation; import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message; import de.gultsch.chat.entities.Message;
@ -23,18 +22,20 @@ import de.gultsch.chat.utils.UIHelper;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Fragment; import android.app.Fragment;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.IntentSender;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.IntentSender.SendIntentException;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
@ -42,6 +43,7 @@ import android.widget.LinearLayout;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
public class ConversationFragment extends Fragment { public class ConversationFragment extends Fragment {
@ -54,27 +56,45 @@ public class ConversationFragment extends Fragment {
protected Contact contact; protected Contact contact;
protected BitmapCache mBitmapCache = new BitmapCache(); protected BitmapCache mBitmapCache = new BitmapCache();
protected String queuedPqpMessage = null;
private EditText chatMsg; private EditText chatMsg;
protected Bitmap selfBitmap; protected Bitmap selfBitmap;
private IntentSender askForPassphraseIntent = null;
private OnClickListener sendMsgListener = new OnClickListener() { private OnClickListener sendMsgListener = new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService;
if (chatMsg.getText().length() < 1) if (chatMsg.getText().length() < 1)
return; return;
Message message = new Message(conversation, chatMsg.getText() Message message = new Message(conversation, chatMsg.getText()
.toString(), conversation.nextMessageEncryption); .toString(), conversation.nextMessageEncryption);
if (conversation.nextMessageEncryption == Message.ENCRYPTION_OTR) { if (conversation.nextMessageEncryption == Message.ENCRYPTION_OTR) {
sendOtrMessage(message); sendOtrMessage(message);
} else if (conversation.nextMessageEncryption == Message.ENCRYPTION_PGP) {
sendPgpMessage(message);
} else { } else {
sendPlainTextMessage(message); sendPlainTextMessage(message);
} }
} }
}; };
protected OnClickListener clickToDecryptListener = new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("gultsch","clicked to decrypt");
if (askForPassphraseIntent!=null) {
try {
getActivity().startIntentSenderForResult(askForPassphraseIntent, ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0);
} catch (SendIntentException e) {
Log.d("gultsch","couldnt fire intent");
}
}
}
};
public void updateChatMsgHint() { public void updateChatMsgHint() {
if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getMode() == Conversation.MODE_MULTI) {
@ -89,6 +109,10 @@ public class ConversationFragment extends Fragment {
break; break;
case Message.ENCRYPTION_PGP: case Message.ENCRYPTION_PGP:
chatMsg.setHint("Send openPGP encryted messeage"); chatMsg.setHint("Send openPGP encryted messeage");
break;
case Message.ENCRYPTION_DECRYPTED:
chatMsg.setHint("Send openPGP encryted messeage");
break;
default: default:
break; break;
} }
@ -155,23 +179,28 @@ public class ConversationFragment extends Fragment {
viewHolder.imageView = (ImageView) view viewHolder.imageView = (ImageView) view
.findViewById(R.id.message_photo); .findViewById(R.id.message_photo);
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) { if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
Uri uri = item.getConversation().getProfilePhotoUri(); Uri uri = item.getConversation()
.getProfilePhotoUri();
if (uri != null) { if (uri != null) {
viewHolder.imageView viewHolder.imageView
.setImageBitmap(mBitmapCache.get(item .setImageBitmap(mBitmapCache.get(item
.getConversation().getName(), uri)); .getConversation().getName(),
uri));
} else { } else {
viewHolder.imageView viewHolder.imageView
.setImageBitmap(mBitmapCache.get(item .setImageBitmap(mBitmapCache.get(item
.getConversation().getName(), null)); .getConversation().getName(),
null));
} }
} }
break; break;
case ERROR: case ERROR:
view = (View) inflater.inflate(R.layout.message_error, null); view = (View) inflater.inflate(R.layout.message_error,
null);
viewHolder.imageView = (ImageView) view viewHolder.imageView = (ImageView) view
.findViewById(R.id.message_photo); .findViewById(R.id.message_photo);
viewHolder.imageView.setImageBitmap(mBitmapCache.getError()); viewHolder.imageView.setImageBitmap(mBitmapCache
.getError());
break; break;
default: default:
viewHolder = null; viewHolder = null;
@ -186,7 +215,7 @@ public class ConversationFragment extends Fragment {
viewHolder = (ViewHolder) view.getTag(); viewHolder = (ViewHolder) view.getTag();
} }
if (type == RECIEVED) { if (type == RECIEVED) {
if (item.getConversation().getMode() == Conversation.MODE_MULTI) { if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
if (item.getCounterpart() != null) { if (item.getCounterpart() != null) {
viewHolder.imageView.setImageBitmap(mBitmapCache viewHolder.imageView.setImageBitmap(mBitmapCache
.get(item.getCounterpart(), null)); .get(item.getCounterpart(), null));
@ -199,7 +228,15 @@ public class ConversationFragment extends Fragment {
} }
String body = item.getBody(); String body = item.getBody();
if (body != null) { if (body != null) {
viewHolder.messageBody.setText(body.trim()); if (item.getEncryption() == Message.ENCRYPTION_PGP) {
viewHolder.messageBody.setText(getString(R.string.encrypted_message));
viewHolder.messageBody.setTextColor(0xff33B5E5);
viewHolder.messageBody.setOnClickListener(clickToDecryptListener);
} else {
viewHolder.messageBody.setText(body.trim());
viewHolder.messageBody.setTextColor(0xff000000);
viewHolder.messageBody.setOnClickListener(null);
}
} }
if (item.getStatus() == Message.STATUS_UNSEND) { if (item.getStatus() == Message.STATUS_UNSEND) {
viewHolder.time.setTypeface(null, Typeface.ITALIC); viewHolder.time.setTypeface(null, Typeface.ITALIC);
@ -236,7 +273,7 @@ public class ConversationFragment extends Fragment {
if (showPhoneSelfContactPicture) { if (showPhoneSelfContactPicture) {
Uri selfiUri = PhoneHelper.getSefliUri(getActivity()); Uri selfiUri = PhoneHelper.getSefliUri(getActivity());
if (selfiUri!=null) { if (selfiUri != null) {
try { try {
self = BitmapFactory.decodeStream(getActivity() self = BitmapFactory.decodeStream(getActivity()
.getContentResolver().openInputStream(selfiUri)); .getContentResolver().openInputStream(selfiUri));
@ -245,7 +282,7 @@ public class ConversationFragment extends Fragment {
} }
} }
} }
if (self==null){ if (self == null) {
self = UIHelper.getUnknownContactPicture(conversation.getAccount() self = UIHelper.getUnknownContactPicture(conversation.getAccount()
.getJid(), 200); .getJid(), 200);
} }
@ -280,17 +317,38 @@ public class ConversationFragment extends Fragment {
activity.updateConversationList(); activity.updateConversationList();
} }
} }
if (queuedPqpMessage != null) {
this.conversation.nextMessageEncryption = Message.ENCRYPTION_PGP;
Message message = new Message(conversation, queuedPqpMessage,
Message.ENCRYPTION_PGP);
sendPgpMessage(message);
}
} }
public void updateMessages() { public void updateMessages() {
ConversationActivity activity = (ConversationActivity) getActivity(); ConversationActivity activity = (ConversationActivity) getActivity();
List<Message> encryptedMessages = new LinkedList<Message>();
for(Message message : this.conversation.getMessages()) {
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
encryptedMessages.add(message);
}
}
if (encryptedMessages.size() > 0) {
DecryptMessage task = new DecryptMessage();
Message[] msgs = new Message[encryptedMessages.size()];
task.execute(encryptedMessages.toArray(msgs));
}
this.messageList.clear(); this.messageList.clear();
this.messageList.addAll(this.conversation.getMessages()); this.messageList.addAll(this.conversation.getMessages());
this.messageListAdapter.notifyDataSetChanged(); this.messageListAdapter.notifyDataSetChanged();
if (messageList.size() >= 1) { if (messageList.size() >= 1) {
int latestEncryption = this.conversation.getLatestMessage() int latestEncryption = this.conversation.getLatestMessage()
.getEncryption(); .getEncryption();
conversation.nextMessageEncryption = latestEncryption; if (latestEncryption== Message.ENCRYPTION_DECRYPTED) {
conversation.nextMessageEncryption = Message.ENCRYPTION_PGP;
} else {
conversation.nextMessageEncryption = latestEncryption;
}
makeFingerprintWarning(latestEncryption); makeFingerprintWarning(latestEncryption);
} }
getActivity().invalidateOptionsMenu(); getActivity().invalidateOptionsMenu();
@ -339,17 +397,47 @@ public class ConversationFragment extends Fragment {
protected void sendPlainTextMessage(Message message) { protected void sendPlainTextMessage(Message message) {
ConversationActivity activity = (ConversationActivity) getActivity(); ConversationActivity activity = (ConversationActivity) getActivity();
activity.xmppConnectionService.sendMessage(conversation.getAccount(), activity.xmppConnectionService.sendMessage(message, null);
message, null);
chatMsg.setText(""); chatMsg.setText("");
} }
protected void sendPgpMessage(final Message message) {
ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService;
Contact contact = message.getConversation().getContact();
if (contact.getPgpKeyId() != 0) {
xmppService.sendMessage(message, null);
chatMsg.setText("");
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("No openPGP key found");
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setMessage("There is no openPGP key assoziated with this contact");
builder.setNegativeButton("Cancel", null);
builder.setPositiveButton("Send plain text",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
message.setEncryption(Message.ENCRYPTION_NONE);
xmppService.sendMessage(message, null);
chatMsg.setText("");
}
});
builder.create().show();
}
}
public void resendPgpMessage(String msg) {
this.queuedPqpMessage = msg;
}
protected void sendOtrMessage(final Message message) { protected void sendOtrMessage(final Message message) {
ConversationActivity activity = (ConversationActivity) getActivity(); ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService; final XmppConnectionService xmppService = activity.xmppConnectionService;
if (conversation.hasValidOtrSession()) { if (conversation.hasValidOtrSession()) {
activity.xmppConnectionService.sendMessage( activity.xmppConnectionService.sendMessage(message, null);
conversation.getAccount(), message, null);
chatMsg.setText(""); chatMsg.setText("");
} else { } else {
Hashtable<String, Integer> presences; Hashtable<String, Integer> presences;
@ -372,17 +460,15 @@ public class ConversationFragment extends Fragment {
int which) { int which) {
conversation.nextMessageEncryption = Message.ENCRYPTION_NONE; conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
message.setEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE);
xmppService.sendMessage( xmppService.sendMessage(message, null);
conversation.getAccount(), message,
null);
chatMsg.setText(""); chatMsg.setText("");
} }
}); });
builder.setNegativeButton("Cancel", null); builder.setNegativeButton("Cancel", null);
builder.create().show(); builder.create().show();
} else if (presences.size() == 1) { } else if (presences.size() == 1) {
xmppService.sendMessage(conversation.getAccount(), message, xmppService.sendMessage(message, (String) presences.keySet()
(String) presences.keySet().toArray()[0]); .toArray()[0]);
chatMsg.setText(""); chatMsg.setText("");
} else { } else {
AlertDialog.Builder builder = new AlertDialog.Builder( AlertDialog.Builder builder = new AlertDialog.Builder(
@ -396,8 +482,7 @@ public class ConversationFragment extends Fragment {
@Override @Override
public void onClick(DialogInterface dialog, public void onClick(DialogInterface dialog,
int which) { int which) {
xmppService.sendMessage( xmppService.sendMessage(message,
conversation.getAccount(), message,
presencesArray[which]); presencesArray[which]);
chatMsg.setText(""); chatMsg.setText("");
} }
@ -446,4 +531,48 @@ public class ConversationFragment extends Fragment {
return error; return error;
} }
} }
class DecryptMessage extends AsyncTask<Message, Void, Boolean> {
@Override
protected Boolean doInBackground(Message... params) {
XmppActivity activity = (XmppActivity) getActivity();
askForPassphraseIntent = null;
for(int i = 0; i < params.length; ++i) {
if (params[i].getEncryption() == Message.ENCRYPTION_PGP) {
String body = params[i].getBody();
String decrypted = null;
try {
if (activity==null) {
return false;
}
Log.d("gultsch","calling to decrypt message id #"+params[i].getUuid());
decrypted = activity.xmppConnectionService.getPgpEngine().decrypt(body);
} catch (UserInputRequiredException e) {
askForPassphraseIntent = e.getPendingIntent().getIntentSender();
return false;
} catch (OpenPgpException e) {
Log.d("gultsch","error decrypting pgp");
}
if (decrypted!=null) {
params[i].setBody(decrypted);
params[i].setEncryption(Message.ENCRYPTION_DECRYPTED);
activity.xmppConnectionService.updateMessage(params[i]);
}
if (activity!=null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
messageListAdapter.notifyDataSetChanged();
}
});
}
}
}
return true;
}
}
} }

View File

@ -63,7 +63,7 @@ public class DialogContactDetails extends DialogFragment {
intent.putExtra(Intents.Insert.IM_HANDLE,contact.getJid()); intent.putExtra(Intents.Insert.IM_HANDLE,contact.getJid());
intent.putExtra(Intents.Insert.IM_PROTOCOL,CommonDataKinds.Im.PROTOCOL_JABBER); intent.putExtra(Intents.Insert.IM_PROTOCOL,CommonDataKinds.Im.PROTOCOL_JABBER);
intent.putExtra("finishActivityOnSaveCompleted", true); intent.putExtra("finishActivityOnSaveCompleted", true);
getActivity().startActivityForResult(intent,ConversationActivity.INSERT_CONTACT); getActivity().startActivityForResult(intent,0);
mDetailsDialog.dismiss(); mDetailsDialog.dismiss();
} }
}; };

View File

@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.gultsch.chat.R; import de.gultsch.chat.R;
import de.gultsch.chat.crypto.PgpEngine;
import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
import de.gultsch.chat.entities.Account; import de.gultsch.chat.entities.Account;
import de.gultsch.chat.ui.EditAccount.EditAccountListener; import de.gultsch.chat.ui.EditAccount.EditAccountListener;
import android.app.Activity; import android.app.Activity;
@ -12,6 +14,7 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.ActionMode; import android.view.ActionMode;
@ -31,6 +34,8 @@ import android.widget.TextView;
public class ManageAccountActivity extends XmppActivity implements ActionMode.Callback { public class ManageAccountActivity extends XmppActivity implements ActionMode.Callback {
public static final int REQUEST_ANNOUNCE_PGP = 0x73731;
protected boolean isActionMode = false; protected boolean isActionMode = false;
protected ActionMode actionMode; protected ActionMode actionMode;
protected Account selectedAccountForActionMode = null; protected Account selectedAccountForActionMode = null;
@ -231,6 +236,17 @@ public class ManageAccountActivity extends XmppActivity implements ActionMode.Ca
}); });
builder.setNegativeButton("Cancel",null); builder.setNegativeButton("Cancel",null);
builder.create().show(); builder.create().show();
} else if (item.getItemId()==R.id.announce_pgp) {
mode.finish();
try {
xmppConnectionService.generatePgpAnnouncement(selectedAccountForActionMode);
} catch (PgpEngine.UserInputRequiredException e) {
try {
startIntentSenderForResult(e.getPendingIntent().getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
} catch (SendIntentException e1) {
Log.d("gultsch","sending intent failed");
}
}
} }
return true; return true;
} }
@ -279,4 +295,18 @@ public class ManageAccountActivity extends XmppActivity implements ActionMode.Ca
} }
}); });
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == REQUEST_ANNOUNCE_PGP) {
try {
xmppConnectionService.generatePgpAnnouncement(selectedAccountForActionMode);
} catch (UserInputRequiredException e) {
Log.d("gultsch","already came back. ignoring");
}
}
}
}
} }

View File

@ -23,6 +23,12 @@ public class MessageParser {
return new Message(conversation, packet.getFrom(), body, Message.ENCRYPTION_NONE, Message.STATUS_RECIEVED); return new Message(conversation, packet.getFrom(), body, Message.ENCRYPTION_NONE, Message.STATUS_RECIEVED);
} }
public static Message parsePgpChat(String pgpBody, MessagePacket packet, Account account, XmppConnectionService service) {
String[] fromParts = packet.getFrom().split("/");
Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
return new Message(conversation, packet.getFrom(), pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECIEVED);
}
public static Message parseOtrChat(MessagePacket packet, Account account, XmppConnectionService service) { public static Message parseOtrChat(MessagePacket packet, Account account, XmppConnectionService service) {
String[] fromParts = packet.getFrom().split("/"); String[] fromParts = packet.getFrom().split("/");
Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false); Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
@ -131,4 +137,13 @@ public class MessageParser {
} }
return new Message(conversation, packet.getFrom(), displayError, Message.ENCRYPTION_NONE, Message.STATUS_ERROR); return new Message(conversation, packet.getFrom(), displayError, Message.ENCRYPTION_NONE, Message.STATUS_ERROR);
} }
public static String getPgpBody(MessagePacket packet) {
for(Element child : packet.getChildren()) {
if (child.getName().equals("x")&&child.getAttribute("xmlns").equals("jabber:x:encrypted")) {
return child.getContent();
}
}
return null;
}
} }