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); return axolotlStore.getFingerprintTrust(fingerprint);
} }
public X509Certificate getFingerprintCertificate(String fingerprint) {
return axolotlStore.getFingerprintCertificate(fingerprint);
}
public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
axolotlStore.setFingerprintTrust(fingerprint, trust); axolotlStore.setFingerprintTrust(fingerprint, trust);
} }

View file

@ -219,6 +219,10 @@ public class SQLiteAxolotlStore implements AxolotlStore {
mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate); 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) { public Set<IdentityKey> getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) {
return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, 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.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair; import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@ -600,16 +602,16 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
} }
public Pair<Long,String> getLastMessageReceived(Account account) { public Pair<Long, String> getLastMessageReceived(Account account) {
SQLiteDatabase db = this.getReadableDatabase(); SQLiteDatabase db = this.getReadableDatabase();
String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1"; String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
String[] args = {account.getUuid()}; String[] args = {account.getUuid()};
Cursor cursor = db.rawQuery(sql, args); Cursor cursor = db.rawQuery(sql, args);
if (cursor.getCount() ==0) { if (cursor.getCount() == 0) {
return null; return null;
} else { } else {
cursor.moveToFirst(); cursor.moveToFirst();
return new Pair<>(cursor.getLong(0),cursor.getString(1)); return new Pair<>(cursor.getLong(0), cursor.getString(1));
} }
} }
@ -1072,11 +1074,38 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ SQLiteAxolotlStore.FINGERPRINT + " = ? ", + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
selectionArgs) == 1; selectionArgs) == 1;
} catch (CertificateEncodingException e) { } catch (CertificateEncodingException e) {
Log.d(Config.LOGTAG,"could not encode certificate"); Log.d(Config.LOGTAG, "could not encode certificate");
return false; return false;
} }
} }
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) { public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); 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.CommonDataKinds;
import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents; import android.provider.ContactsContract.Intents;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -30,12 +31,14 @@ import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils;
import java.security.cert.X509Certificate;
import java.util.List; 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.crypto.PgpEngine; import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; 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.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem; 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)) { for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) {
boolean highlight = fingerprint.equals(messageFingerprint); 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) { if (contact.getPgpKeyId() != 0) {
hasKeys = true; 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) { protected void confirmToDeleteFingerprint(final String fingerprint) {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.delete_fingerprint); builder.setTitle(R.string.delete_fingerprint);

View file

@ -710,7 +710,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
continue; continue;
} }
boolean highlight = fingerprint.equals(messageFingerprint); boolean highlight = fingerprint.equals(messageFingerprint);
hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight); hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null);
} }
if (hasKeys) { if (hasKeys) {
keysCard.setVisibility(View.VISIBLE); 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. // own fingerprints have no impact on locked status.
} }
}, },
null,
null null
); );
} }
@ -140,6 +141,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
lockOrUnlockAsNeeded(); lockOrUnlockAsNeeded();
} }
}, },
null,
null null
); );
} }

View file

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

View file

@ -1,5 +1,6 @@
package eu.siacs.conversations.utils; package eu.siacs.conversations.utils;
import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@ -9,6 +10,7 @@ import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.jce.PrincipalUtil; import org.bouncycastle.jce.PrincipalUtil;
import java.security.MessageDigest;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
@ -121,6 +123,14 @@ public final class CryptoHelper {
return builder.toString(); 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) { public static String[] getOrderedCipherSuites(final String[] platformSupportedCipherSuites) {
final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS)); final Collection<String> cipherSuites = new LinkedHashSet<>(Arrays.asList(Config.ENABLED_CIPHERS));
final List<String> platformCiphers = Arrays.asList(platformSupportedCipherSuites); 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) { public static int encryptionTypeToText(int encryption) {
switch (encryption) { switch (encryption) {
case Message.ENCRYPTION_OTR: 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="no_storage_permission">Conversations need access to external storage</string>
<string name="sync_with_contacts">Synchronize with contacts</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="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> </resources>