From b23cb5a9e43d804551dc18f399ffbec991479ee6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 9 Oct 2015 13:37:08 +0200 Subject: [PATCH] initial UI work to allow setting up accounts from certifcates --- build.gradle | 1 + .../siacs/conversations/entities/Account.java | 16 ++-- .../services/XmppConnectionService.java | 87 +++++++++++++++---- .../conversations/ui/EditAccountActivity.java | 46 +++++----- .../ui/ManageAccountActivity.java | 38 +++++++- .../siacs/conversations/ui/XmppActivity.java | 5 ++ src/main/res/menu/manageaccounts.xml | 6 ++ src/main/res/values/strings.xml | 3 + 8 files changed, 157 insertions(+), 45 deletions(-) diff --git a/build.gradle b/build.gradle index 3a3a30f8d..d52af31ed 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ dependencies { compile project(':libs:MemorizingTrustManager') compile 'com.android.support:support-v13:23.0.1' compile 'org.bouncycastle:bcprov-jdk15on:1.52' + compile 'org.bouncycastle:bcmail-jdk15on:1.52' compile 'org.jitsi:org.otr4j:0.22' compile 'org.gnu.inet:libidn:1.15' compile 'com.google.zxing:core:3.2.1' diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 002c276e2..0eb38c9fd 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -194,18 +194,14 @@ public class Account extends AbstractEntity { return jid.getLocalpart(); } - public void setUsername(final String username) throws InvalidJidException { - jid = Jid.fromParts(username, jid.getDomainpart(), jid.getResourcepart()); + public void setJid(final Jid jid) { + this.jid = jid; } public Jid getServer() { return jid.toDomainJid(); } - public void setServer(final String server) throws InvalidJidException { - jid = Jid.fromParts(jid.getLocalpart(), server, jid.getResourcepart()); - } - public String getPassword() { return password; } @@ -272,6 +268,14 @@ public class Account extends AbstractEntity { } } + public boolean setPrivateKeyAlias(String alias) { + return setKey("private_key_alias", alias); + } + + public String getPrivateKeyAlias() { + return getKey("private_key_alias"); + } + @Override public ContentValues getContentValues() { final ContentValues values = new ContentValues(); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index ddc40d34e..4d0228e94 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -25,6 +25,8 @@ import android.os.PowerManager.WakeLock; import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.ContactsContract; +import android.security.KeyChain; +import android.security.KeyChainException; import android.util.Log; import android.util.LruCache; @@ -34,11 +36,22 @@ import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionStatus; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.jce.PrincipalUtil; +import org.bouncycastle.jce.X509Principal; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; import java.math.BigInteger; +import java.security.PrivateKey; import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -1285,6 +1298,43 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateAccountUi(); } + public void createAccountFromKey(final String alias, final OnAccountCreated callback) { + new Thread(new Runnable() { + @Override + public void run() { + try { + X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); + PrivateKey key = KeyChain.getPrivateKey(XmppConnectionService.this, alias); + X500Name x500name = new JcaX509CertificateHolder(chain[0]).getSubject(); + String email = IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue()); + String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()); + Jid jid = Jid.fromString(email); + if (findAccountByJid(jid) == null) { + Account account = new Account(jid, ""); + account.setPrivateKeyAlias(alias); + account.setOption(Account.OPTION_DISABLED, true); + createAccount(account); + callback.onAccountCreated(account); + } else { + callback.informUser(R.string.account_already_exists); + } + } catch (KeyChainException e) { + callback.informUser(R.string.unable_to_parse_certificate); + } catch (InterruptedException e) { + callback.informUser(R.string.unable_to_parse_certificate); + e.printStackTrace(); + } catch (CertificateEncodingException e) { + callback.informUser(R.string.unable_to_parse_certificate); + e.printStackTrace(); + } catch (InvalidJidException e) { + callback.informUser(R.string.unable_to_parse_certificate); + e.printStackTrace(); + } + } + }).start(); + + } + public void updateAccount(final Account account) { this.statusListener.onStatusChanged(account); databaseBackend.updateAccount(account); @@ -2699,54 +2749,59 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public interface OnMoreMessagesLoaded { - public void onMoreMessagesLoaded(int count, Conversation conversation); + public interface OnAccountCreated { + void onAccountCreated(Account account); + void informUser(int r); + } - public void informUser(int r); + public interface OnMoreMessagesLoaded { + void onMoreMessagesLoaded(int count, Conversation conversation); + + void informUser(int r); } public interface OnAccountPasswordChanged { - public void onPasswordChangeSucceeded(); + void onPasswordChangeSucceeded(); - public void onPasswordChangeFailed(); + void onPasswordChangeFailed(); } public interface OnAffiliationChanged { - public void onAffiliationChangedSuccessful(Jid jid); + void onAffiliationChangedSuccessful(Jid jid); - public void onAffiliationChangeFailed(Jid jid, int resId); + void onAffiliationChangeFailed(Jid jid, int resId); } public interface OnRoleChanged { - public void onRoleChangedSuccessful(String nick); + void onRoleChangedSuccessful(String nick); - public void onRoleChangeFailed(String nick, int resid); + void onRoleChangeFailed(String nick, int resid); } public interface OnConversationUpdate { - public void onConversationUpdate(); + void onConversationUpdate(); } public interface OnAccountUpdate { - public void onAccountUpdate(); + void onAccountUpdate(); } public interface OnRosterUpdate { - public void onRosterUpdate(); + void onRosterUpdate(); } public interface OnMucRosterUpdate { - public void onMucRosterUpdate(); + void onMucRosterUpdate(); } public interface OnConferenceConfigurationFetched { - public void onConferenceConfigurationFetched(Conversation conversation); + void onConferenceConfigurationFetched(Conversation conversation); } public interface OnConferenceOptionsPushed { - public void onPushSucceeded(); + void onPushSucceeded(); - public void onPushFailed(); + void onPushFailed(); } public interface OnShowErrorToast { diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 00e3a0f91..dbd5f117b 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -74,6 +74,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private LinearLayout keysCard; private Jid jidToEdit; + private boolean mInitMode = false; private Account mAccount; private String messageFingerprint; @@ -83,6 +84,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View v) { + if (mInitMode && mAccount != null) { + mAccount.setOption(Account.OPTION_DISABLED, false); + } if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) { mAccount.setOption(Account.OPTION_DISABLED, false); xmppConnectionService.updateAccount(mAccount); @@ -129,12 +133,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } if (mAccount != null) { - try { - mAccount.setUsername(jid.hasLocalpart() ? jid.getLocalpart() : ""); - mAccount.setServer(jid.getDomainpart()); - } catch (final InvalidJidException ignored) { - return; - } + mAccount.setJid(jid); mAccountJid.setError(null); mPasswordConfirm.setError(null); mAccount.setPassword(password); @@ -152,9 +151,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); xmppConnectionService.createAccount(mAccount); } - if (jidToEdit != null - && !mAccount.isOptionSet(Account.OPTION_DISABLED) - && !registerNewAccount) { + if (!mAccount.isOptionSet(Account.OPTION_DISABLED) + && !registerNewAccount + && !mInitMode) { finish(); } else { updateSaveButton(); @@ -179,12 +178,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate startActivity(new Intent(getApplicationContext(), ManageAccountActivity.class)); finish(); - } else if (jidToEdit == null && mAccount != null - && mAccount.getStatus() == Account.State.ONLINE) { + } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) { if (!mFetchingAvatar) { mFetchingAvatar = true; - xmppConnectionService.checkForAvatar(mAccount, - mAvatarFetchCallback); + xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback); } } else { updateSaveButton(); @@ -236,8 +233,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View view) { if (mAccount != null) { - final Intent intent = new Intent(getApplicationContext(), - PublishProfilePictureActivity.class); + final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); intent.putExtra("account", mAccount.getJid().toBareJid().toString()); startActivity(intent); } @@ -269,7 +265,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } protected void updateSaveButton() { - if (accountInfoEdited() && jidToEdit != null) { + if (accountInfoEdited() && !mInitMode) { this.mSaveButton.setText(R.string.save); this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); @@ -277,14 +273,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mSaveButton.setEnabled(false); this.mSaveButton.setTextColor(getSecondaryTextColor()); this.mSaveButton.setText(R.string.account_status_connecting); - } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED) { + } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) { this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); this.mSaveButton.setText(R.string.enable); } else { this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); - if (jidToEdit != null) { + if (!mInitMode) { if (mAccount != null && mAccount.isOnlineAndConnected()) { this.mSaveButton.setText(R.string.save); if (!accountInfoEdited()) { @@ -421,8 +417,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } catch (final InvalidJidException | NullPointerException ignored) { this.jidToEdit = null; } + this.mInitMode = getIntent().getBooleanExtra("init", false) || this.jidToEdit == null; this.messageFingerprint = getIntent().getStringExtra("fingerprint"); - if (this.jidToEdit != null) { + if (!mInitMode) { this.mRegisterNew.setVisibility(View.GONE); if (getActionBar() != null) { getActionBar().setTitle(getString(R.string.account_details)); @@ -440,7 +437,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate protected void onBackendConnected() { if (this.jidToEdit != null) { this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); - updateAccountInformation(true); + if (this.mAccount != null) { + if (this.mAccount.getPrivateKeyAlias() != null) { + this.mPassword.setHint(R.string.authenticate_with_certificate); + if (this.mInitMode) { + this.mPassword.requestFocus(); + } + } updateAccountInformation(true); + } } else if (this.xmppConnectionService.getAccounts().size() == 0) { if (getActionBar() != null) { getActionBar().setDisplayHomeAsUpEnabled(false); @@ -492,7 +496,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } this.mPassword.setText(this.mAccount.getPassword()); } - if (this.jidToEdit != null) { + if (!mInitMode) { this.mAvatar.setVisibility(View.VISIBLE); this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72))); } diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java index 5b9b73550..80e775069 100644 --- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -5,6 +5,9 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; @@ -14,6 +17,7 @@ import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; +import android.widget.Toast; import java.util.ArrayList; import java.util.List; @@ -21,10 +25,11 @@ import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.ui.adapter.AccountAdapter; -public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate { +public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated { protected Account selectedAccount = null; @@ -61,7 +66,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda @Override public void onItemClick(AdapterView arg0, View view, - int position, long arg3) { + int position, long arg3) { switchToAccount(accountList.get(position)); } }); @@ -144,6 +149,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda case R.id.action_enable_all: enableAllAccounts(); break; + case R.id.action_add_account_from_key: + addAccountFromKey(); + break; default: break; } @@ -179,6 +187,10 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } + private void addAccountFromKey() { + KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); + } + private void publishAvatar(Account account) { Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); @@ -281,4 +293,26 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } } + + @Override + public void alias(String alias) { + if (alias != null) { + xmppConnectionService.createAccountFromKey(alias, this); + } + } + + @Override + public void onAccountCreated(Account account) { + switchToAccount(account, true); + } + + @Override + public void informUser(final int r) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ManageAccountActivity.this,r,Toast.LENGTH_LONG).show(); + } + }); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 967efec92..e3848fe28 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -435,8 +435,13 @@ public abstract class XmppActivity extends Activity { } public void switchToAccount(Account account) { + switchToAccount(account,false); + } + + public void switchToAccount(Account account, boolean init) { Intent intent = new Intent(this, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("init", init); startActivity(intent); } diff --git a/src/main/res/menu/manageaccounts.xml b/src/main/res/menu/manageaccounts.xml index f8a30ff7b..6dd57ae54 100644 --- a/src/main/res/menu/manageaccounts.xml +++ b/src/main/res/menu/manageaccounts.xml @@ -6,6 +6,12 @@ android:icon="?attr/icon_add_person" android:showAsAction="always" android:title="@string/action_add_account"/> + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 29b531554..37263ed4f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -524,4 +524,7 @@ Marks your resource as away when the screen is turned off Not available in silent mode Marks your resource as not available when phone is in silent mode + Add account from key + Unable to parse certificate + Leave empty to authenticate w/ certificate