show certificate information

This commit is contained in:
Daniel Gultsch 2015-12-23 19:18:53 +01:00
parent d0bad09f13
commit f46cbb38a9
10 changed files with 240 additions and 10 deletions

View file

@ -575,6 +575,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return axolotlStore.getFingerprintTrust(fingerprint);
}
public X509Certificate getFingerprintCertificate(String fingerprint) {
return axolotlStore.getFingerprintCertificate(fingerprint);
}
public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
axolotlStore.setFingerprintTrust(fingerprint, trust);
}

View file

@ -219,6 +219,10 @@ public class SQLiteAxolotlStore implements AxolotlStore {
mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate);
}
public X509Certificate getFingerprintCertificate(String fingerprint) {
return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint);
}
public Set<IdentityKey> getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) {
return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust);
}

View file

@ -15,13 +15,15 @@ import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashSet;
@ -1077,6 +1079,33 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
SQLiteDatabase db = this.getReadableDatabase();
String[] selectionArgs = {
account.getUuid(),
fingerprint
};
String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
if (cursor.getCount() < 1) {
return null;
} else {
cursor.moveToFirst();
byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
if (certificate == null || certificate.length == 0) {
return null;
}
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
} catch (CertificateException e) {
Log.d(Config.LOGTAG,"certificate exception "+e.getMessage());
return null;
}
}
}
public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
}

View file

@ -14,6 +14,7 @@ import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -30,12 +31,14 @@ import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpUtils;
import java.security.cert.X509Certificate;
import java.util.List;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
@ -394,7 +397,12 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) {
boolean highlight = fingerprint.equals(messageFingerprint);
hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight);
hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight, new OnClickListener() {
@Override
public void onClick(View v) {
onOmemoKeyClicked(contact.getAccount(), fingerprint);
}
});
}
if (contact.getPgpKeyId() != 0) {
hasKeys = true;
@ -446,6 +454,40 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
}
private void onOmemoKeyClicked(Account account, String fingerprint) {
Log.d(Config.LOGTAG,"on omemo key clicked");
final XmppAxolotlSession.Trust trust = account.getAxolotlService().getFingerprintTrust(fingerprint);
if (trust != null) {
X509Certificate x509Certificate = account.getAxolotlService().getFingerprintCertificate(fingerprint);
if (x509Certificate != null) {
Log.d(Config.LOGTAG, "certificate for fingerprint " + fingerprint + " was not null");
showCertificateInformationDialog(CryptoHelper.extractCertificateInformation(x509Certificate));
}
}
}
private void showCertificateInformationDialog(Bundle bundle) {
View view = getLayoutInflater().inflate(R.layout.certificate_information, null);
final String not_available = getString(R.string.certicate_info_not_available);
TextView subject_cn = (TextView) view.findViewById(R.id.subject_cn);
TextView subject_o = (TextView) view.findViewById(R.id.subject_o);
TextView issuer_cn = (TextView) view.findViewById(R.id.issuer_cn);
TextView issuer_o = (TextView) view.findViewById(R.id.issuer_o);
TextView sha1 = (TextView) view.findViewById(R.id.sha1);
subject_cn.setText(bundle.getString("subject_cn", not_available));
subject_o.setText(bundle.getString("subject_o", not_available));
issuer_cn.setText(bundle.getString("issuer_cn", not_available));
issuer_o.setText(bundle.getString("issuer_o", not_available));
sha1.setText(bundle.getString("sha1", not_available));
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.certificate_information);
builder.setView(view);
builder.setPositiveButton(R.string.ok, null);
builder.create().show();
}
protected void confirmToDeleteFingerprint(final String fingerprint) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.delete_fingerprint);

View file

@ -710,7 +710,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
continue;
}
boolean highlight = fingerprint.equals(messageFingerprint);
hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight);
hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null);
}
if (hasKeys) {
keysCard.setVisibility(View.VISIBLE);

View file

@ -126,6 +126,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
// own fingerprints have no impact on locked status.
}
},
null,
null
);
}
@ -140,6 +141,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
lockOrUnlockAsNeeded();
}
},
null,
null
);
}

View file

@ -648,7 +648,7 @@ public abstract class XmppActivity extends Activity {
builder.create().show();
}
protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight) {
protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
final XmppAxolotlSession.Trust trust = account.getAxolotlService()
.getFingerprintTrust(fingerprint);
if (trust == null) {
@ -670,7 +670,8 @@ public abstract class XmppActivity extends Activity {
XmppAxolotlSession.Trust.UNTRUSTED);
v.setEnabled(true);
}
}
},
onKeyClickedListener
);
}
@ -682,13 +683,16 @@ public abstract class XmppActivity extends Activity {
boolean showTag,
CompoundButton.OnCheckedChangeListener
onCheckedChangeListener,
View.OnClickListener onClickListener) {
View.OnClickListener onClickListener,
View.OnClickListener onKeyClickedListener) {
if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
return false;
}
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
TextView key = (TextView) view.findViewById(R.id.key);
key.setOnClickListener(onKeyClickedListener);
TextView keyType = (TextView) view.findViewById(R.id.key_type);
keyType.setOnClickListener(onKeyClickedListener);
Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
trustToggle.setVisibility(View.VISIBLE);
trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);

View file

@ -1,5 +1,6 @@
package eu.siacs.conversations.utils;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
@ -9,6 +10,7 @@ import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.jce.PrincipalUtil;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateParsingException;
@ -121,6 +123,14 @@ public final class CryptoHelper {
return builder.toString();
}
public static String prettifyFingerprintCert(String fingerprint) {
StringBuilder builder = new StringBuilder(fingerprint);
for(int i=2;i < builder.length(); i+=3) {
builder.insert(i,':');
}
return builder.toString();
}
public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) {
final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS));
final List<String> platformCiphers = Arrays.asList(platformSupportedCipherSuites);
@ -167,6 +177,46 @@ public final class CryptoHelper {
}
}
public static Bundle extractCertificateInformation(X509Certificate certificate) {
Bundle information = new Bundle();
try {
JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate);
X500Name subject = holder.getSubject();
try {
information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString());
} catch (Exception e) {
//ignored
}
try {
information.putString("subject_o",subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString());
} catch (Exception e) {
//ignored
}
X500Name issuer = holder.getIssuer();
try {
information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString());
} catch (Exception e) {
//ignored
}
try {
information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString());
} catch (Exception e) {
//ignored
}
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] fingerprint = md.digest(certificate.getEncoded());
information.putString("sha1", prettifyFingerprintCert(bytesToHex(fingerprint)));
} catch (Exception e) {
}
return information;
} catch (CertificateEncodingException e) {
return information;
}
}
public static int encryptionTypeToText(int encryption) {
switch (encryption) {
case Message.ENCRYPTION_OTR:

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/certificate_subject"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeHeadline"/>
<TextView
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/certificate_cn"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/subject_cn"
android:textColor="@color/black54"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/certificate_o"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/subject_o"
android:textColor="@color/black54"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/certificate_issuer"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeHeadline"/>
<TextView
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/certificate_cn"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/issuer_cn"
android:textColor="@color/black54"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/certificate_o"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/issuer_o"
android:textColor="@color/black54"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/certificate_sha1"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sha1"
android:textColor="@color/black54"
android:textSize="?attr/TextSizeBody"
android:typeface="monospace"
android:fontFamily="monospace"/>
</LinearLayout>

View file

@ -560,4 +560,11 @@
<string name="no_storage_permission">Conversations need access to external storage</string>
<string name="sync_with_contacts">Synchronize with contacts</string>
<string name="sync_with_contacts_long">Conversations wants to match your XMPP roster with your contacts to show their full names and avatars.\n\nConversations will only read your contacts and match them locally without uploading them to your server.\n\nYou will now be asked to grant permission to access your contacts.</string>
<string name="certificate_information">Certificate Information</string>
<string name="certificate_subject">Subject</string>
<string name="certificate_issuer">Issuer</string>
<string name="certificate_cn">Common Name</string>
<string name="certificate_o">Organization</string>
<string name="certificate_sha1">SHA1</string>
<string name="certicate_info_not_available">(Not available)</string>
</resources>