parse omemo fingerprints from uris

This commit is contained in:
Daniel Gultsch 2016-11-17 20:09:42 +01:00
parent 3f3b360eee
commit 7e2e42cb11
11 changed files with 172 additions and 44 deletions

View File

@ -98,6 +98,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return false;
}
public void preVerifyFingerprint(Contact contact, String fingerprint) {
axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().toBareJid().toPreppedString(), fingerprint);
}
private static class AxolotlAddressMap<T> {
protected Map<String, Map<Integer, T>> map;
protected final Object MAP_LOCK = new Object();
@ -200,7 +204,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public void put(AxolotlAddress address, XmppAxolotlSession value) {
super.put(address, value);
value.setNotFresh();
xmppConnectionService.syncRosterToDisk(account);
xmppConnectionService.syncRosterToDisk(account); //TODO why?
}
public void put(XmppAxolotlSession session) {
@ -417,7 +421,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void purgeKey(final String fingerprint) {
axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised());
axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised());
}
public void publishOwnDeviceIdIfNeeded() {
@ -690,7 +694,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void setFingerprintTrust(String fingerprint, FingerprintStatus status) {
axolotlStore.setFingerprintTrust(fingerprint, status);
axolotlStore.setFingerprintStatus(fingerprint, status);
}
private void verifySessionWithPEP(final XmppAxolotlSession session) {
@ -749,14 +753,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
&& !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress);
Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address);
if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
FetchStatus report = null;
if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED)
| fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) {
if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
report = FetchStatus.SUCCESS;
} else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
report = FetchStatus.SUCCESS_VERIFIED;
} else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR)
|| fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
} else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
report = FetchStatus.ERROR;
}
mXmppConnectionService.keyStatusUpdated(report);
@ -812,7 +817,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (Config.X509_VERIFICATION) {
verifySessionWithPEP(session);
} else {
fetchStatusMap.put(address, FetchStatus.SUCCESS);
FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s",""));
boolean verified = status != null && status.isVerified();
fetchStatusMap.put(address, verified ? FetchStatus.SUCCESS_VERIFIED : FetchStatus.SUCCESS);
finishBuildingSessionsFromPEP(address);
}
} catch (UntrustedIdentityException | InvalidKeyException e) {

View File

@ -114,6 +114,20 @@ public class FingerprintStatus {
return status;
}
public FingerprintStatus toVerified() {
FingerprintStatus status = new FingerprintStatus();
status.active = active;
status.trust = Trust.VERIFIED;
return status;
}
public static FingerprintStatus createInactiveVerified() {
final FingerprintStatus status = new FingerprintStatus();
status.trust = Trust.VERIFIED;
status.active = false;
return status;
}
public enum Trust {
COMPROMISED,
UNDECIDED,

View File

@ -187,7 +187,15 @@ public class SQLiteAxolotlStore implements AxolotlStore {
@Override
public void saveIdentity(String name, IdentityKey identityKey) {
if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) {
mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey);
String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
FingerprintStatus status = getFingerprintStatus(fingerprint);
if (status == null) {
status = FingerprintStatus.createActiveUndecided(); //default for new keys
} else {
status = status.toActive();
}
mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey, status);
trustCache.remove(fingerprint);
}
}
@ -214,7 +222,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
return (fingerprint == null)? null : trustCache.get(fingerprint);
}
public void setFingerprintTrust(String fingerprint, FingerprintStatus status) {
public void setFingerprintStatus(String fingerprint, FingerprintStatus status) {
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, status);
trustCache.remove(fingerprint);
}
@ -430,4 +438,8 @@ public class SQLiteAxolotlStore implements AxolotlStore {
public void removeSignedPreKey(int signedPreKeyId) {
mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
}
public void preVerifyFingerprint(Account account, String name, String fingerprint) {
mXmppConnectionService.databaseBackend.storePreVerification(account,name,fingerprint,FingerprintStatus.createInactiveVerified());
}
}

View File

@ -73,7 +73,7 @@ public class XmppAxolotlSession {
}
protected void setTrust(FingerprintStatus status) {
sqLiteAxolotlStore.setFingerprintTrust(getFingerprint(), status);
sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
}
protected FingerprintStatus getTrust() {

View File

@ -1106,7 +1106,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
continue;
}
try {
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0));
String key = cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY));
if (key != null) {
identityKeys.add(new IdentityKey(Base64.decode(key, Base64.DEFAULT), 0));
} else {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Missing key (possibly preverified) in database for account" + account.getJid().toBareJid() + ", address: " + name);
}
} catch (InvalidKeyException e) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
}
@ -1134,10 +1139,6 @@ public class DatabaseBackend extends SQLiteOpenHelper {
);
}
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
storeIdentityKey(account, name, own, fingerprint, base64Serialized, FingerprintStatus.createActiveUndecided());
}
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, FingerprintStatus status) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
@ -1147,6 +1148,22 @@ public class DatabaseBackend extends SQLiteOpenHelper {
values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
values.put(SQLiteAxolotlStore.KEY, base64Serialized);
values.putAll(status.toContentValues());
String where = SQLiteAxolotlStore.ACCOUNT+"=? AND "+SQLiteAxolotlStore.NAME+"=? AND "+SQLiteAxolotlStore.FINGERPRINT+" =?";
String[] whereArgs = {account.getUuid(),name,fingerprint};
int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,values,where,whereArgs);
if (rows == 0) {
db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
}
}
public void storePreVerification(Account account, String name, String fingerprint, FingerprintStatus status) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
values.put(SQLiteAxolotlStore.NAME, name);
values.put(SQLiteAxolotlStore.OWN, 0);
values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
values.putAll(status.toContentValues());
db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
}
@ -1227,8 +1244,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
public void storeIdentityKey(Account account, String name, IdentityKey identityKey, FingerprintStatus status) {
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT), status);
}
public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {

View File

@ -65,6 +65,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpDecryptionService;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
@ -102,6 +103,7 @@ import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnBindListener;
import eu.siacs.conversations.xmpp.OnContactStatusChanged;
@ -3608,6 +3610,29 @@ public class XmppConnectionService extends Service {
});
}
public void verifyFingerprints(Contact contact, List<XmppUri.Fingerprint> fingerprints) {
boolean needsRosterWrite = false;
final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
for(XmppUri.Fingerprint fp : fingerprints) {
if (fp.type == XmppUri.FingerprintType.OTR) {
needsRosterWrite |= contact.addOtrFingerprint(fp.fingerprint);
} else if (fp.type == XmppUri.FingerprintType.OMEMO) {
String fingerprint = "05"+fp.fingerprint.replaceAll("\\s","");
FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint);
if (fingerprintStatus != null) {
if (!fingerprintStatus.isVerified()) {
axolotlService.setFingerprintTrust(fingerprint,fingerprintStatus.toVerified());
}
} else {
axolotlService.preVerifyFingerprint(contact,fingerprint);
}
}
}
if (needsRosterWrite) {
syncRosterToDisk(contact.getAccount());
}
}
public interface OnMamPreferencesFetched {
void onPreferencesFetched(Element prefs);
void onPreferencesFetchFailed();

View File

@ -397,11 +397,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
}
@SuppressLint("InflateParams")
protected void showCreateContactDialog(final String prefilledJid, final String fingerprint) {
protected void showCreateContactDialog(final String prefilledJid, final Invite invite) {
EnterJidDialog dialog = new EnterJidDialog(
this, mKnownHosts, mActivatedAccounts,
getString(R.string.create_contact), getString(R.string.create),
prefilledJid, null, fingerprint == null
prefilledJid, null, !invite.hasFingerprints()
);
dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() {
@ -420,7 +420,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
if (contact.showInRoster()) {
throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
} else {
contact.addOtrFingerprint(fingerprint);
//contact.addOtrFingerprint(fingerprint);
xmppConnectionService.createContact(contact);
switchToConversation(contact);
return true;
@ -842,6 +842,10 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
}
private boolean handleJid(Invite invite) {
Log.d(Config.LOGTAG,"handling invite for "+invite.getJid());
for(XmppUri.Fingerprint fp : invite.getFingerprints()) {
Log.d(Config.LOGTAG,fp.toString());
}
List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid());
if (invite.isMuc()) {
Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
@ -853,16 +857,19 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
return false;
}
} else if (contacts.size() == 0) {
showCreateContactDialog(invite.getJid().toString(), invite.getFingerprint());
showCreateContactDialog(invite.getJid().toString(), invite);
return false;
} else if (contacts.size() == 1) {
Contact contact = contacts.get(0);
if (invite.getFingerprint() != null) {
if (invite.hasFingerprints()) {
xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints());
}
/*if (invite.getFingerprint() != null) {
if (contact.addOtrFingerprint(invite.getFingerprint())) {
Log.d(Config.LOGTAG, "added new fingerprint");
xmppConnectionService.syncRosterToDisk(contact.getAccount());
}
}
}*/
switchToConversation(contact);
return true;
} else {

View File

@ -18,6 +18,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.OmemoActivity;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@ -245,7 +246,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show();
break;
case SUCCESS_VERIFIED:
Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show();
Toast.makeText(TrustKeysActivity.this,
Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified,
Toast.LENGTH_LONG).show();
break;
}
}

View File

@ -173,11 +173,10 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer
protected boolean verifyWithUri(XmppUri uri) {
Contact contact = mConversation.getContact();
if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.getFingerprint() != null) {
contact.addOtrFingerprint(uri.getFingerprint());
if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.hasFingerprints()) {
xmppConnectionService.verifyFingerprints(contact,uri.getFingerprints());
Toast.makeText(this,R.string.verified,Toast.LENGTH_SHORT).show();
updateView();
xmppConnectionService.syncRosterToDisk(contact.getAccount());
return true;
} else {
Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show();

View File

@ -4,7 +4,9 @@ import android.net.Uri;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@ -13,7 +15,9 @@ public class XmppUri {
protected String jid;
protected boolean muc;
protected String fingerprint;
protected List<Fingerprint> fingerprints = new ArrayList<>();
private static final String OMEMO_URI_PARAM = "omemo-sid-";
public XmppUri(String uri) {
try {
@ -56,7 +60,7 @@ public class XmppUri {
} else {
jid = uri.getSchemeSpecificPart().split("\\?")[0];
}
fingerprint = parseFingerprint(uri.getQuery());
this.fingerprints = parseFingerprints(uri.getQuery());
} else if ("imto".equalsIgnoreCase(scheme)) {
// sample: imto://xmpp/foo@bar.com
try {
@ -73,18 +77,28 @@ public class XmppUri {
}
}
protected String parseFingerprint(String query) {
if (query == null) {
return null;
} else {
final String NEEDLE = "otr-fingerprint=";
int index = query.indexOf(NEEDLE);
if (index >= 0 && query.length() >= (NEEDLE.length() + index + 40)) {
return query.substring(index + NEEDLE.length(), index + NEEDLE.length() + 40);
} else {
return null;
protected List<Fingerprint> parseFingerprints(String query) {
List<Fingerprint> fingerprints = new ArrayList<>();
String[] pairs = query == null ? new String[0] : query.split(";");
for(String pair : pairs) {
String[] parts = pair.split("=",2);
if (parts.length == 2) {
String key = parts[0].toLowerCase(Locale.US);
String value = parts[1];
if ("otr-fingerprint".equals(key)) {
fingerprints.add(new Fingerprint(FingerprintType.OTR,value));
}
if (key.startsWith(OMEMO_URI_PARAM)) {
try {
int id = Integer.parseInt(key.substring(OMEMO_URI_PARAM.length()));
fingerprints.add(new Fingerprint(FingerprintType.OMEMO,value,id));
} catch (Exception e) {
//ignoring invalid device id
}
}
}
}
return fingerprints;
}
public Jid getJid() {
@ -95,7 +109,36 @@ public class XmppUri {
}
}
public String getFingerprint() {
return this.fingerprint;
public List<Fingerprint> getFingerprints() {
return this.fingerprints;
}
public boolean hasFingerprints() {
return fingerprints.size() > 0;
}
public enum FingerprintType {
OMEMO,
OTR
}
public static class Fingerprint {
public final FingerprintType type;
public final String fingerprint;
public final int deviceId;
public Fingerprint(FingerprintType type, String fingerprint) {
this(type, fingerprint, 0);
}
public Fingerprint(FingerprintType type, String fingerprint, int deviceId) {
this.type = type;
this.fingerprint = fingerprint;
this.deviceId = deviceId;
}
@Override
public String toString() {
return type.toString()+": "+fingerprint+(deviceId != 0 ? " "+String.valueOf(deviceId) : "");
}
}
}

View File

@ -702,4 +702,5 @@
<string name="error_unable_to_create_temporary_file">Unable to create temporary file</string>
<string name="this_device_has_been_verified">This device has been verified</string>
<string name="copy_fingerprint">Copy fingerprint</string>
<string name="all_omemo_keys_have_been_verified">All OMEMO keys have been verified</string>
</resources>