initial UI work to allow setting up accounts from certifcates

This commit is contained in:
Daniel Gultsch 2015-10-09 13:37:08 +02:00
parent ef605e4cbd
commit b23cb5a9e4
8 changed files with 157 additions and 45 deletions

View file

@ -29,6 +29,7 @@ dependencies {
compile project(':libs:MemorizingTrustManager') compile project(':libs:MemorizingTrustManager')
compile 'com.android.support:support-v13:23.0.1' compile 'com.android.support:support-v13:23.0.1'
compile 'org.bouncycastle:bcprov-jdk15on:1.52' compile 'org.bouncycastle:bcprov-jdk15on:1.52'
compile 'org.bouncycastle:bcmail-jdk15on:1.52'
compile 'org.jitsi:org.otr4j:0.22' compile 'org.jitsi:org.otr4j:0.22'
compile 'org.gnu.inet:libidn:1.15' compile 'org.gnu.inet:libidn:1.15'
compile 'com.google.zxing:core:3.2.1' compile 'com.google.zxing:core:3.2.1'

View file

@ -194,18 +194,14 @@ public class Account extends AbstractEntity {
return jid.getLocalpart(); return jid.getLocalpart();
} }
public void setUsername(final String username) throws InvalidJidException { public void setJid(final Jid jid) {
jid = Jid.fromParts(username, jid.getDomainpart(), jid.getResourcepart()); this.jid = jid;
} }
public Jid getServer() { public Jid getServer() {
return jid.toDomainJid(); return jid.toDomainJid();
} }
public void setServer(final String server) throws InvalidJidException {
jid = Jid.fromParts(jid.getLocalpart(), server, jid.getResourcepart());
}
public String getPassword() { public String getPassword() {
return password; 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 @Override
public ContentValues getContentValues() { public ContentValues getContentValues() {
final ContentValues values = new ContentValues(); final ContentValues values = new ContentValues();

View file

@ -25,6 +25,8 @@ import android.os.PowerManager.WakeLock;
import android.os.SystemClock; import android.os.SystemClock;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.util.Log; import android.util.Log;
import android.util.LruCache; 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.SessionImpl;
import net.java.otr4j.session.SessionStatus; 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.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection; import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.SecureRandom; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -1285,6 +1298,43 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
updateAccountUi(); 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) { public void updateAccount(final Account account) {
this.statusListener.onStatusChanged(account); this.statusListener.onStatusChanged(account);
databaseBackend.updateAccount(account); databaseBackend.updateAccount(account);
@ -2699,54 +2749,59 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} }
} }
public interface OnMoreMessagesLoaded { public interface OnAccountCreated {
public void onMoreMessagesLoaded(int count, Conversation conversation); 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 interface OnAccountPasswordChanged {
public void onPasswordChangeSucceeded(); void onPasswordChangeSucceeded();
public void onPasswordChangeFailed(); void onPasswordChangeFailed();
} }
public interface OnAffiliationChanged { 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 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 interface OnConversationUpdate {
public void onConversationUpdate(); void onConversationUpdate();
} }
public interface OnAccountUpdate { public interface OnAccountUpdate {
public void onAccountUpdate(); void onAccountUpdate();
} }
public interface OnRosterUpdate { public interface OnRosterUpdate {
public void onRosterUpdate(); void onRosterUpdate();
} }
public interface OnMucRosterUpdate { public interface OnMucRosterUpdate {
public void onMucRosterUpdate(); void onMucRosterUpdate();
} }
public interface OnConferenceConfigurationFetched { public interface OnConferenceConfigurationFetched {
public void onConferenceConfigurationFetched(Conversation conversation); void onConferenceConfigurationFetched(Conversation conversation);
} }
public interface OnConferenceOptionsPushed { public interface OnConferenceOptionsPushed {
public void onPushSucceeded(); void onPushSucceeded();
public void onPushFailed(); void onPushFailed();
} }
public interface OnShowErrorToast { public interface OnShowErrorToast {

View file

@ -74,6 +74,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
private LinearLayout keysCard; private LinearLayout keysCard;
private Jid jidToEdit; private Jid jidToEdit;
private boolean mInitMode = false;
private Account mAccount; private Account mAccount;
private String messageFingerprint; private String messageFingerprint;
@ -83,6 +84,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
@Override @Override
public void onClick(final View v) { 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()) { if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) {
mAccount.setOption(Account.OPTION_DISABLED, false); mAccount.setOption(Account.OPTION_DISABLED, false);
xmppConnectionService.updateAccount(mAccount); xmppConnectionService.updateAccount(mAccount);
@ -129,12 +133,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} }
} }
if (mAccount != null) { if (mAccount != null) {
try { mAccount.setJid(jid);
mAccount.setUsername(jid.hasLocalpart() ? jid.getLocalpart() : "");
mAccount.setServer(jid.getDomainpart());
} catch (final InvalidJidException ignored) {
return;
}
mAccountJid.setError(null); mAccountJid.setError(null);
mPasswordConfirm.setError(null); mPasswordConfirm.setError(null);
mAccount.setPassword(password); mAccount.setPassword(password);
@ -152,9 +151,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
xmppConnectionService.createAccount(mAccount); xmppConnectionService.createAccount(mAccount);
} }
if (jidToEdit != null if (!mAccount.isOptionSet(Account.OPTION_DISABLED)
&& !mAccount.isOptionSet(Account.OPTION_DISABLED) && !registerNewAccount
&& !registerNewAccount) { && !mInitMode) {
finish(); finish();
} else { } else {
updateSaveButton(); updateSaveButton();
@ -179,12 +178,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
startActivity(new Intent(getApplicationContext(), startActivity(new Intent(getApplicationContext(),
ManageAccountActivity.class)); ManageAccountActivity.class));
finish(); finish();
} else if (jidToEdit == null && mAccount != null } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
&& mAccount.getStatus() == Account.State.ONLINE) {
if (!mFetchingAvatar) { if (!mFetchingAvatar) {
mFetchingAvatar = true; mFetchingAvatar = true;
xmppConnectionService.checkForAvatar(mAccount, xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
mAvatarFetchCallback);
} }
} else { } else {
updateSaveButton(); updateSaveButton();
@ -236,8 +233,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
@Override @Override
public void onClick(final View view) { public void onClick(final View view) {
if (mAccount != null) { if (mAccount != null) {
final Intent intent = new Intent(getApplicationContext(), final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
PublishProfilePictureActivity.class);
intent.putExtra("account", mAccount.getJid().toBareJid().toString()); intent.putExtra("account", mAccount.getJid().toBareJid().toString());
startActivity(intent); startActivity(intent);
} }
@ -269,7 +265,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} }
protected void updateSaveButton() { protected void updateSaveButton() {
if (accountInfoEdited() && jidToEdit != null) { if (accountInfoEdited() && !mInitMode) {
this.mSaveButton.setText(R.string.save); this.mSaveButton.setText(R.string.save);
this.mSaveButton.setEnabled(true); this.mSaveButton.setEnabled(true);
this.mSaveButton.setTextColor(getPrimaryTextColor()); this.mSaveButton.setTextColor(getPrimaryTextColor());
@ -277,14 +273,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mSaveButton.setEnabled(false); this.mSaveButton.setEnabled(false);
this.mSaveButton.setTextColor(getSecondaryTextColor()); this.mSaveButton.setTextColor(getSecondaryTextColor());
this.mSaveButton.setText(R.string.account_status_connecting); 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.setEnabled(true);
this.mSaveButton.setTextColor(getPrimaryTextColor()); this.mSaveButton.setTextColor(getPrimaryTextColor());
this.mSaveButton.setText(R.string.enable); this.mSaveButton.setText(R.string.enable);
} else { } else {
this.mSaveButton.setEnabled(true); this.mSaveButton.setEnabled(true);
this.mSaveButton.setTextColor(getPrimaryTextColor()); this.mSaveButton.setTextColor(getPrimaryTextColor());
if (jidToEdit != null) { if (!mInitMode) {
if (mAccount != null && mAccount.isOnlineAndConnected()) { if (mAccount != null && mAccount.isOnlineAndConnected()) {
this.mSaveButton.setText(R.string.save); this.mSaveButton.setText(R.string.save);
if (!accountInfoEdited()) { if (!accountInfoEdited()) {
@ -421,8 +417,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} catch (final InvalidJidException | NullPointerException ignored) { } catch (final InvalidJidException | NullPointerException ignored) {
this.jidToEdit = null; this.jidToEdit = null;
} }
this.mInitMode = getIntent().getBooleanExtra("init", false) || this.jidToEdit == null;
this.messageFingerprint = getIntent().getStringExtra("fingerprint"); this.messageFingerprint = getIntent().getStringExtra("fingerprint");
if (this.jidToEdit != null) { if (!mInitMode) {
this.mRegisterNew.setVisibility(View.GONE); this.mRegisterNew.setVisibility(View.GONE);
if (getActionBar() != null) { if (getActionBar() != null) {
getActionBar().setTitle(getString(R.string.account_details)); getActionBar().setTitle(getString(R.string.account_details));
@ -440,7 +437,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
protected void onBackendConnected() { protected void onBackendConnected() {
if (this.jidToEdit != null) { if (this.jidToEdit != null) {
this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); 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) { } else if (this.xmppConnectionService.getAccounts().size() == 0) {
if (getActionBar() != null) { if (getActionBar() != null) {
getActionBar().setDisplayHomeAsUpEnabled(false); getActionBar().setDisplayHomeAsUpEnabled(false);
@ -492,7 +496,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} }
this.mPassword.setText(this.mAccount.getPassword()); this.mPassword.setText(this.mAccount.getPassword());
} }
if (this.jidToEdit != null) { if (!mInitMode) {
this.mAvatar.setVisibility(View.VISIBLE); this.mAvatar.setVisibility(View.VISIBLE);
this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72))); this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72)));
} }

View file

@ -5,6 +5,9 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu; import android.view.Menu;
@ -14,6 +17,7 @@ import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -21,10 +25,11 @@ import java.util.List;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.ui.adapter.AccountAdapter; 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; protected Account selectedAccount = null;
@ -144,6 +149,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
case R.id.action_enable_all: case R.id.action_enable_all:
enableAllAccounts(); enableAllAccounts();
break; break;
case R.id.action_add_account_from_key:
addAccountFromKey();
break;
default: default:
break; 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) { private void publishAvatar(Account account) {
Intent intent = new Intent(getApplicationContext(), Intent intent = new Intent(getApplicationContext(),
PublishProfilePictureActivity.class); 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();
}
});
}
} }

View file

@ -435,8 +435,13 @@ public abstract class XmppActivity extends Activity {
} }
public void switchToAccount(Account account) { public void switchToAccount(Account account) {
switchToAccount(account,false);
}
public void switchToAccount(Account account, boolean init) {
Intent intent = new Intent(this, EditAccountActivity.class); Intent intent = new Intent(this, EditAccountActivity.class);
intent.putExtra("jid", account.getJid().toBareJid().toString()); intent.putExtra("jid", account.getJid().toBareJid().toString());
intent.putExtra("init", init);
startActivity(intent); startActivity(intent);
} }

View file

@ -6,6 +6,12 @@
android:icon="?attr/icon_add_person" android:icon="?attr/icon_add_person"
android:showAsAction="always" android:showAsAction="always"
android:title="@string/action_add_account"/> android:title="@string/action_add_account"/>
<item
android:id="@+id/action_add_account_from_key"
android:showAsAction="never"
android:icon="?attr/icon_add_person"
android:title="@string/action_add_account_from_key"
android:visible="false"/>
<item <item
android:id="@+id/action_enable_all" android:id="@+id/action_enable_all"
android:title="@string/enable_all_accounts"/> android:title="@string/enable_all_accounts"/>

View file

@ -524,4 +524,7 @@
<string name="pref_away_when_screen_off_summary">Marks your resource as away when the screen is turned off</string> <string name="pref_away_when_screen_off_summary">Marks your resource as away when the screen is turned off</string>
<string name="pref_xa_on_silent_mode">Not available in silent mode</string> <string name="pref_xa_on_silent_mode">Not available in silent mode</string>
<string name="pref_xa_on_silent_mode_summary">Marks your resource as not available when phone is in silent mode</string> <string name="pref_xa_on_silent_mode_summary">Marks your resource as not available when phone is in silent mode</string>
<string name="action_add_account_from_key">Add account from key</string>
<string name="unable_to_parse_certificate">Unable to parse certificate</string>
<string name="authenticate_with_certificate">Leave empty to authenticate w/ certificate</string>
</resources> </resources>