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 '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'

View file

@ -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();

View file

@ -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 {

View file

@ -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)));
}

View file

@ -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();
}
});
}
}

View file

@ -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);
}

View file

@ -6,6 +6,12 @@
android:icon="?attr/icon_add_person"
android:showAsAction="always"
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
android:id="@+id/action_enable_all"
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_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="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>