add menu item in account details to renew certificate

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java  | 105 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java          |  21 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  52 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java         |  80 
src/main/java/eu/siacs/conversations/utils/CryptoHelper.java             |  19 
src/main/res/menu/editaccount.xml                                        |   7 
src/main/res/values/strings.xml                                          |   5 
7 files changed, 228 insertions(+), 61 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java 🔗

@@ -1,5 +1,7 @@
 package eu.siacs.conversations.crypto.axolotl;
 
+import android.security.KeyChain;
+import android.security.KeyChainException;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.util.Log;
@@ -18,7 +20,12 @@ import org.whispersystems.libaxolotl.state.PreKeyRecord;
 import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
 import org.whispersystems.libaxolotl.util.KeyHelper;
 
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -46,6 +53,7 @@ public class AxolotlService {
 	public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
 	public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
 	public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
+	public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
 
 	public static final String LOGPREFIX = "AxolotlService";
 
@@ -242,11 +250,11 @@ public class AxolotlService {
 		return this.pepBroken;
 	}
 
-	public void regenerateKeys() {
+	public void regenerateKeys(boolean wipeOther) {
 		axolotlStore.regenerate();
 		sessions.clear();
 		fetchStatusMap.clear();
-		publishBundlesIfNeeded(true);
+		publishBundlesIfNeeded(true, wipeOther);
 	}
 
 	public int getOwnDeviceId() {
@@ -380,7 +388,43 @@ public class AxolotlService {
 		}
 	}
 
-	public void publishBundlesIfNeeded(final boolean announceAfter) {
+	public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
+												   final Set<PreKeyRecord> preKeyRecords,
+												   final boolean announceAfter,
+												   final boolean wipe) {
+		try {
+			IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
+			PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
+			X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
+			Signature verifier = Signature.getInstance("sha256WithRSA");
+			verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
+			verifier.update(axolotlPublicKey.serialize());
+			byte[] signature = verifier.sign();
+			IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
+			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
+			Log.d(Config.LOGTAG,"verification : "+packet.toString());
+			mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+				@Override
+				public void onIqPacketReceived(Account account, IqPacket packet) {
+					publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
+				}
+			});
+		} catch (KeyChainException e) {
+			e.printStackTrace();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		} catch (NoSuchAlgorithmException e) {
+			Log.d(Config.LOGTAG,"no such algo "+e.getMessage());
+			e.printStackTrace();
+		} catch (java.security.InvalidKeyException e) {
+			e.printStackTrace();
+		} catch (SignatureException e) {
+			e.printStackTrace();
+		}
+
+	}
+
+	public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
 		if (pepBroken) {
 			Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
 			return;
@@ -470,27 +514,16 @@ public class AxolotlService {
 
 
 					if (changed) {
-						IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
-								signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
-								preKeyRecords, getOwnDeviceId());
-						Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
-						mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
-							@Override
-							public void onIqPacketReceived(Account account, IqPacket packet) {
-								if (packet.getType() == IqPacket.TYPE.RESULT) {
-									Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
-									if (announceAfter) {
-										Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
-										publishOwnDeviceIdIfNeeded();
-									}
-								} else {
-									Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
-								}
-							}
-						});
+						if (account.getPrivateKeyAlias() == null) {
+							publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+						} else {
+							publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+						}
 					} else {
 						Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
-						if (announceAfter) {
+						if (wipe) {
+							wipeOtherPepDevices();
+						} else if (announce) {
 							Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
 							publishOwnDeviceIdIfNeeded();
 						}
@@ -503,6 +536,32 @@ public class AxolotlService {
 		});
 	}
 
+	private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
+									 Set<PreKeyRecord> preKeyRecords,
+									 final boolean announceAfter,
+									 final boolean wipe) {
+		IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
+				signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
+				preKeyRecords, getOwnDeviceId());
+		Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
+		mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+			@Override
+			public void onIqPacketReceived(Account account, IqPacket packet) {
+				if (packet.getType() == IqPacket.TYPE.RESULT) {
+					Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
+					if (wipe) {
+						wipeOtherPepDevices();
+					} else if (announceAfter) {
+						Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+						publishOwnDeviceIdIfNeeded();
+					}
+				} else {
+					Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
+				}
+			}
+		});
+	}
+
 	public boolean isContactAxolotlCapable(Contact contact) {
 		Jid jid = contact.getJid().toBareJid();
 		return hasAny(contact) ||
@@ -774,7 +833,7 @@ public class AxolotlService {
 			plaintextMessage = message.decrypt(session, getOwnDeviceId());
 			Integer preKeyId = session.getPreKeyId();
 			if (preKeyId != null) {
-				publishBundlesIfNeeded(false);
+				publishBundlesIfNeeded(false, false);
 				session.resetPreKeyId();
 			}
 		} catch (CryptoFailedException e) {

src/main/java/eu/siacs/conversations/generator/IqGenerator.java 🔗

@@ -2,16 +2,20 @@ package eu.siacs.conversations.generator;
 
 
 import android.util.Base64;
+import android.util.Log;
 
 import org.whispersystems.libaxolotl.IdentityKey;
 import org.whispersystems.libaxolotl.ecc.ECPublicKey;
 import org.whispersystems.libaxolotl.state.PreKeyRecord;
 import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
 
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
@@ -173,6 +177,23 @@ public class IqGenerator extends AbstractGenerator {
 		return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item);
 	}
 
+	public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
+		final Element item = new Element("item");
+		final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
+		final Element chain = verification.addChild("chain");
+		for(int i = 0; i < certificates.length; ++i) {
+			try {
+				Element certificate = chain.addChild("certificate");
+				certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT));
+				certificate.setAttribute("index",i);
+			} catch (CertificateEncodingException e) {
+				Log.d(Config.LOGTAG, "could not encode certificate");
+			}
+		}
+		verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT));
+		return publish(AxolotlService.PEP_VERIFICATION+":"+deviceId, item);
+	}
+
 	public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
 		final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
 		final Element query = packet.query("urn:xmpp:mam:0");

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -30,6 +30,7 @@ import android.security.KeyChainException;
 import android.util.Log;
 import android.util.LruCache;
 import android.util.DisplayMetrics;
+import android.util.Pair;
 
 import net.java.otr4j.OtrException;
 import net.java.otr4j.session.Session;
@@ -37,21 +38,17 @@ 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.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -175,7 +172,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 			mMessageArchiveService.executePendingQueries(account);
 			mJingleConnectionManager.cancelInTransmission();
 			syncDirtyContacts(account);
-			account.getAxolotlService().publishBundlesIfNeeded(true);
+			account.getAxolotlService().publishBundlesIfNeeded(true, false);
 		}
 	};
 	private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
@@ -1307,17 +1304,18 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 			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, "");
+					Pair<Jid,String> info = CryptoHelper.extractJidAndName(chain[0]);
+					if (findAccountByJid(info.first) == null) {
+						Account account = new Account(info.first, "");
 						account.setPrivateKeyAlias(alias);
 						account.setOption(Account.OPTION_DISABLED, true);
 						createAccount(account);
 						callback.onAccountCreated(account);
+						try {
+							getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA");
+						} catch (CertificateException e) {
+							callback.informUser(R.string.certificate_chain_is_not_trusted);
+						}
 					} else {
 						callback.informUser(R.string.account_already_exists);
 					}
@@ -1338,6 +1336,34 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 
 	}
 
+	public void updateKeyInAccount(final Account account, final String alias) {
+		Log.d(Config.LOGTAG,"update key in account "+alias);
+		try {
+			X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias);
+			Pair<Jid, String> info = CryptoHelper.extractJidAndName(chain[0]);
+			if (account.getJid().toBareJid().equals(info.first)) {
+				account.setPrivateKeyAlias(alias);
+				databaseBackend.updateAccount(account);
+				try {
+					getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA");
+				} catch (CertificateException e) {
+					showErrorToastInUi(R.string.certificate_chain_is_not_trusted);
+				}
+				account.getAxolotlService().regenerateKeys(true);
+			} else {
+				showErrorToastInUi(R.string.jid_does_not_match_certificate);
+			}
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		} catch (KeyChainException e) {
+			e.printStackTrace();
+		} catch (InvalidJidException e) {
+			e.printStackTrace();
+		} catch (CertificateEncodingException e) {
+			e.printStackTrace();
+		}
+	}
+
 	public void updateAccount(final Account account) {
 		this.statusListener.onStatusChanged(account);
 		databaseBackend.updateAccount(account);

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java 🔗

@@ -7,6 +7,8 @@ import android.content.DialogInterface;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Bundle;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.view.Menu;
@@ -34,6 +36,7 @@ import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
+import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
 import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
 import eu.siacs.conversations.utils.CryptoHelper;
@@ -46,7 +49,7 @@ import eu.siacs.conversations.xmpp.jid.Jid;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 
 public class EditAccountActivity extends XmppActivity implements OnAccountUpdate,
-		OnKeyStatusUpdated, OnCaptchaRequested {
+		OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast {
 
 	private AutoCompleteTextView mAccountJid;
 	private EditText mPassword;
@@ -107,7 +110,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			final Jid jid;
 			try {
 				if (Config.DOMAIN_LOCK != null) {
-					jid = Jid.fromParts(mAccountJid.getText().toString(),Config.DOMAIN_LOCK,null);
+					jid = Jid.fromParts(mAccountJid.getText().toString(), Config.DOMAIN_LOCK, null);
 				} else {
 					jid = Jid.fromString(mAccountJid.getText().toString());
 				}
@@ -182,7 +185,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				&& mAccount.getStatus() != Account.State.ONLINE
 				&& mFetchingAvatar) {
 			startActivity(new Intent(getApplicationContext(),
-						ManageAccountActivity.class));
+					ManageAccountActivity.class));
 			finish();
 		} else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
 			if (!mFetchingAvatar) {
@@ -201,6 +204,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 	public void onAccountUpdate() {
 		refreshUi();
 	}
+
 	private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
 
 		@Override
@@ -318,7 +322,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 
 	@Override
 	protected String getShareableUri() {
-		if (mAccount!=null) {
+		if (mAccount != null) {
 			return mAccount.getShareableUri();
 		} else {
 			return "";
@@ -369,7 +373,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 		final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() {
 			@Override
 			public void onCheckedChanged(final CompoundButton buttonView,
-					final boolean isChecked) {
+										 final boolean isChecked) {
 				if (isChecked) {
 					mPasswordConfirm.setVisibility(View.VISIBLE);
 				} else {
@@ -393,6 +397,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 		final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
 		final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
 		final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices);
+		final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
+
+		renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
+
 		if (mAccount != null && mAccount.isOnlineAndConnected()) {
 			if (!mAccount.getXmppConnection().getFeatures().blocking()) {
 				showBlocklist.setVisible(false);
@@ -445,11 +453,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
 			if (this.mAccount != null) {
 				if (this.mAccount.getPrivateKeyAlias() != null) {
-				this.mPassword.setHint(R.string.authenticate_with_certificate);
-				if (this.mInitMode) {
-					this.mPassword.requestFocus();
+					this.mPassword.setHint(R.string.authenticate_with_certificate);
+					if (this.mInitMode) {
+						this.mPassword.requestFocus();
+					}
 				}
-			}	updateAccountInformation(true);
+				updateAccountInformation(true);
 			}
 		} else if (this.xmppConnectionService.getAccounts().size() == 0) {
 			if (getActionBar() != null) {
@@ -489,10 +498,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			case R.id.action_clear_devices:
 				showWipePepDialog();
 				break;
+			case R.id.action_renew_certificate:
+				renewCertificate();
+				break;
 		}
 		return super.onOptionsItemSelected(item);
 	}
 
+	private void renewCertificate() {
+		KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
+	}
+
+	@Override
+	public void alias(String alias) {
+		if (alias != null) {
+			xmppConnectionService.updateKeyInAccount(mAccount, alias);
+		}
+	}
+
 	private void updateAccountInformation(boolean init) {
 		if (init) {
 			if (Config.DOMAIN_LOCK != null) {
@@ -517,7 +540,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 		if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
 			this.mStats.setVisibility(View.VISIBLE);
 			this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
-						.getLastSessionEstablished()));
+					.getLastSessionEstablished()));
 			Features features = this.mAccount.getXmppConnection().getFeatures();
 			if (features.rosterVersioning()) {
 				this.mServerInfoRosterVersion.setText(R.string.server_info_available);
@@ -528,7 +551,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				this.mServerInfoCarbons.setText(R.string.server_info_available);
 			} else {
 				this.mServerInfoCarbons
-					.setText(R.string.server_info_unavailable);
+						.setText(R.string.server_info_unavailable);
 			}
 			if (features.mam()) {
 				this.mServerInfoMam.setText(R.string.server_info_available);
@@ -570,21 +593,21 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
 				this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
 				this.mOtrFingerprintToClipboardButton
-					.setVisibility(View.VISIBLE);
+						.setVisibility(View.VISIBLE);
 				this.mOtrFingerprintToClipboardButton
-					.setOnClickListener(new View.OnClickListener() {
+						.setOnClickListener(new View.OnClickListener() {
 
-						@Override
-						public void onClick(final View v) {
+							@Override
+							public void onClick(final View v) {
 
-							if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) {
-								Toast.makeText(
-										EditAccountActivity.this,
-										R.string.toast_message_otr_fingerprint,
-										Toast.LENGTH_SHORT).show();
+								if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) {
+									Toast.makeText(
+											EditAccountActivity.this,
+											R.string.toast_message_otr_fingerprint,
+											Toast.LENGTH_SHORT).show();
+								}
 							}
-						}
-					});
+						});
 			} else {
 				this.mOtrFingerprintBox.setVisibility(View.GONE);
 			}
@@ -627,7 +650,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			boolean hasKeys = false;
 			keys.removeAllViews();
 			for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) {
-				if(ownFingerprint.equals(fingerprint)) {
+				if (ownFingerprint.equals(fingerprint)) {
 					continue;
 				}
 				boolean highlight = fingerprint.equals(messageFingerprint);
@@ -661,7 +684,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				new DialogInterface.OnClickListener() {
 					@Override
 					public void onClick(DialogInterface dialog, int which) {
-						mAccount.getAxolotlService().regenerateKeys();
+						mAccount.getAxolotlService().regenerateKeys(false);
 					}
 				});
 		builder.create().show();
@@ -753,4 +776,13 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			}
 		});
 	}
+
+	public void onShowErrorToast(final int resId) {
+		runOnUiThread(new Runnable() {
+			@Override
+			public void run() {
+				Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show();
+			}
+		});
+	}
 }

src/main/java/eu/siacs/conversations/utils/CryptoHelper.java 🔗

@@ -1,6 +1,15 @@
 package eu.siacs.conversations.utils;
 
+import android.util.Pair;
+
+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 java.security.SecureRandom;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
 import java.text.Normalizer;
 import java.util.Arrays;
 import java.util.Collection;
@@ -9,6 +18,8 @@ import java.util.LinkedHashSet;
 import java.util.List;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
 
 public final class CryptoHelper {
 	public static final String FILETRANSFER = "?FILETRANSFERv1:";
@@ -125,4 +136,12 @@ public final class CryptoHelper {
 			}
 		}
 	}
+
+	public static Pair<Jid,String> extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException {
+		X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
+		//String xmpp = IETFUtils.valueToString(x500name.getRDNs(new ASN1ObjectIdentifier("1.3.6.1.5.5.7.8.5"))[0].getFirst().getValue());
+		String email = IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue());
+		String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue());
+		return new Pair<>(Jid.fromString(email),name);
+	}
 }

src/main/res/menu/editaccount.xml 🔗

@@ -10,6 +10,13 @@
         android:title="@string/show_block_list"
         android:showAsAction="never" />
 
+    <item
+        android:id="@+id/action_renew_certificate"
+        android:title="@string/action_renew_certificate"
+        android:visible="false"
+        android:showAsAction="never" />
+        />
+
     <item
         android:id="@+id/action_server_info_show_more"
         android:title="@string/server_info_show_more"

src/main/res/values/strings.xml 🔗

@@ -524,10 +524,13 @@
 	<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="action_add_account_from_key">Add account from certificate</string>
 	<string name="unable_to_parse_certificate">Unable to parse certificate</string>
 	<string name="authenticate_with_certificate">Leave empty to authenticate w/ certificate</string>
 	<string name="captcha_ocr">Captcha text</string>
 	<string name="captcha_required">Captcha required</string>
 	<string name="captcha_hint">enter the text from the image</string>
+	<string name="certificate_chain_is_not_trusted">Certificate chain is not trusted</string>
+	<string name="jid_does_not_match_certificate">Jabber ID does not match certificate</string>
+	<string name="action_renew_certificate">Renew certificate</string>
 </resources>