introduced code to verify omemo device keys with x509 certificates.
cleaned up TrustKeysActivity to automatically close if there is nothing to do
This commit is contained in:
parent
fb7359e6a3
commit
cfeb67d71d
|
@ -1,10 +1,10 @@
|
||||||
package eu.siacs.conversations.crypto.axolotl;
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
import android.security.KeyChain;
|
import android.security.KeyChain;
|
||||||
import android.security.KeyChainException;
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.whispersystems.libaxolotl.AxolotlAddress;
|
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||||
|
@ -20,11 +20,9 @@ import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||||
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||||
import org.whispersystems.libaxolotl.util.KeyHelper;
|
import org.whispersystems.libaxolotl.util.KeyHelper;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -43,12 +41,13 @@ import eu.siacs.conversations.parser.IqParser;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
|
|
||||||
public class AxolotlService {
|
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
|
|
||||||
public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
|
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_DEVICE_LIST = PEP_PREFIX + ".devicelist";
|
||||||
|
@ -71,6 +70,15 @@ public class AxolotlService {
|
||||||
private int numPublishTriesOnEmptyPep = 0;
|
private int numPublishTriesOnEmptyPep = 0;
|
||||||
private boolean pepBroken = false;
|
private boolean pepBroken = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAdvancedStreamFeaturesAvailable(Account account) {
|
||||||
|
if (account.getXmppConnection().getFeatures().pep()) {
|
||||||
|
publishBundlesIfNeeded(true, false);
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class AxolotlAddressMap<T> {
|
private static class AxolotlAddressMap<T> {
|
||||||
protected Map<String, Map<Integer, T>> map;
|
protected Map<String, Map<Integer, T>> map;
|
||||||
protected final Object MAP_LOCK = new Object();
|
protected final Object MAP_LOCK = new Object();
|
||||||
|
@ -402,7 +410,6 @@ public class AxolotlService {
|
||||||
byte[] signature = verifier.sign();
|
byte[] signature = verifier.sign();
|
||||||
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
|
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+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() {
|
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||||
|
@ -565,6 +572,50 @@ public class AxolotlService {
|
||||||
axolotlStore.setFingerprintTrust(fingerprint, trust);
|
axolotlStore.setFingerprintTrust(fingerprint, trust);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifySessionWithPEP(final XmppAxolotlSession session, final IdentityKey identityKey) {
|
||||||
|
final AxolotlAddress address = session.getRemoteAddress();
|
||||||
|
try {
|
||||||
|
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
|
||||||
|
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
||||||
|
@Override
|
||||||
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||||
|
Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
|
||||||
|
if (verification != null) {
|
||||||
|
try {
|
||||||
|
Signature verifier = Signature.getInstance("sha256WithRSA");
|
||||||
|
verifier.initVerify(verification.first[0]);
|
||||||
|
verifier.update(identityKey.serialize());
|
||||||
|
if (verifier.verify(verification.second)) {
|
||||||
|
try {
|
||||||
|
mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
|
||||||
|
Log.d(Config.LOGTAG, "verified session with x.509 signature");
|
||||||
|
setFingerprintTrust(session.getFingerprint(), XmppAxolotlSession.Trust.TRUSTED);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(Config.LOGTAG,"could not verify certificate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, " unable to parse verification");
|
||||||
|
}
|
||||||
|
finishBuildingSessionsFromPEP(address);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (InvalidJidException e) {
|
||||||
|
finishBuildingSessionsFromPEP(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
|
||||||
|
AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
|
||||||
|
if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
|
||||||
|
&& !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
|
||||||
|
mXmppConnectionService.keyStatusUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void buildSessionFromPEP(final AxolotlAddress address) {
|
private void buildSessionFromPEP(final AxolotlAddress address) {
|
||||||
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
|
||||||
if (address.getDeviceId() == getOwnDeviceId()) {
|
if (address.getDeviceId() == getOwnDeviceId()) {
|
||||||
|
@ -576,13 +627,6 @@ public class AxolotlService {
|
||||||
Jid.fromString(address.getName()), address.getDeviceId());
|
Jid.fromString(address.getName()), address.getDeviceId());
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
|
||||||
mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
|
mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
|
||||||
private void finish() {
|
|
||||||
AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
|
|
||||||
if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
|
|
||||||
&& !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
|
|
||||||
mXmppConnectionService.keyStatusUpdated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||||
|
@ -594,7 +638,7 @@ public class AxolotlService {
|
||||||
if (preKeyBundleList.isEmpty() || bundle == null) {
|
if (preKeyBundleList.isEmpty() || bundle == null) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
|
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
|
||||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
finish();
|
finishBuildingSessionsFromPEP(address);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
|
@ -602,7 +646,7 @@ public class AxolotlService {
|
||||||
if (preKey == null) {
|
if (preKey == null) {
|
||||||
//should never happen
|
//should never happen
|
||||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
finish();
|
finishBuildingSessionsFromPEP(address);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -617,17 +661,21 @@ public class AxolotlService {
|
||||||
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey().getFingerprint().replaceAll("\\s", ""));
|
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey().getFingerprint().replaceAll("\\s", ""));
|
||||||
sessions.put(address, session);
|
sessions.put(address, session);
|
||||||
fetchStatusMap.put(address, FetchStatus.SUCCESS);
|
fetchStatusMap.put(address, FetchStatus.SUCCESS);
|
||||||
|
if (Config.X509_VERIFICATION) {
|
||||||
|
verifySessionWithPEP(session, bundle.getIdentityKey());
|
||||||
|
} else {
|
||||||
|
finishBuildingSessionsFromPEP(address);
|
||||||
|
}
|
||||||
} catch (UntrustedIdentityException | InvalidKeyException e) {
|
} catch (UntrustedIdentityException | InvalidKeyException e) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
|
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
|
||||||
+ e.getClass().getName() + ", " + e.getMessage());
|
+ e.getClass().getName() + ", " + e.getMessage());
|
||||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
|
finishBuildingSessionsFromPEP(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
finish();
|
|
||||||
} else {
|
} else {
|
||||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
|
Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
|
||||||
finish();
|
finishBuildingSessionsFromPEP(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -699,9 +747,9 @@ public class AxolotlService {
|
||||||
return newSessions;
|
return newSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasPendingKeyFetches(Conversation conversation) {
|
public boolean hasPendingKeyFetches(Account account, Contact contact) {
|
||||||
AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
|
AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
|
||||||
AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(), 0);
|
AxolotlAddress foreignAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0);
|
||||||
return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
|
return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
|
||||||
|| fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
|
|| fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
|
||||||
|
|
||||||
|
|
|
@ -297,6 +297,9 @@ public class Account extends AbstractEntity {
|
||||||
public void initAccountServices(final XmppConnectionService context) {
|
public void initAccountServices(final XmppConnectionService context) {
|
||||||
this.mOtrService = new OtrService(context, this);
|
this.mOtrService = new OtrService(context, this);
|
||||||
this.axolotlService = new AxolotlService(this, context);
|
this.axolotlService = new AxolotlService(this, context);
|
||||||
|
if (xmppConnection != null) {
|
||||||
|
xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public OtrService getOtrService() {
|
public OtrService getOtrService() {
|
||||||
|
|
|
@ -137,9 +137,13 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
|
|
||||||
public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
|
public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
|
||||||
final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null);
|
final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null);
|
||||||
if(to != null) {
|
|
||||||
packet.setTo(to);
|
packet.setTo(to);
|
||||||
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) {
|
||||||
|
final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION+":"+deviceid, null);
|
||||||
|
packet.setTo(to);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package eu.siacs.conversations.parser;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.whispersystems.libaxolotl.IdentityKey;
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
|
@ -10,6 +11,10 @@ import org.whispersystems.libaxolotl.ecc.Curve;
|
||||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||||
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -204,6 +209,30 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
return preKeyRecords;
|
return preKeyRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Pair<X509Certificate[],byte[]> verification(final IqPacket packet) {
|
||||||
|
Element item = getItem(packet);
|
||||||
|
Element verification = item != null ? item.findChild("verification",AxolotlService.PEP_PREFIX) : null;
|
||||||
|
Element chain = verification != null ? verification.findChild("chain") : null;
|
||||||
|
Element signature = verification != null ? verification.findChild("signature") : null;
|
||||||
|
if (chain != null && signature != null) {
|
||||||
|
List<Element> certElements = chain.getChildren();
|
||||||
|
X509Certificate[] certificates = new X509Certificate[certElements.size()];
|
||||||
|
try {
|
||||||
|
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||||
|
int i = 0;
|
||||||
|
for(Element cert : certElements) {
|
||||||
|
certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(),Base64.DEFAULT)));
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return new Pair<>(certificates,Base64.decode(signature.getContent(),Base64.DEFAULT));
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PreKeyBundle bundle(final IqPacket bundle) {
|
public PreKeyBundle bundle(final IqPacket bundle) {
|
||||||
Element bundleItem = getItem(bundle);
|
Element bundleItem = getItem(bundle);
|
||||||
if(bundleItem == null) {
|
if(bundleItem == null) {
|
||||||
|
|
|
@ -60,6 +60,7 @@ import de.duenndns.ssl.MemorizingTrustManager;
|
||||||
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.XmppAxolotlMessage;
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Blockable;
|
import eu.siacs.conversations.entities.Blockable;
|
||||||
|
@ -256,7 +257,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
mMessageArchiveService.executePendingQueries(account);
|
mMessageArchiveService.executePendingQueries(account);
|
||||||
mJingleConnectionManager.cancelInTransmission();
|
mJingleConnectionManager.cancelInTransmission();
|
||||||
syncDirtyContacts(account);
|
syncDirtyContacts(account);
|
||||||
account.getAxolotlService().publishBundlesIfNeeded(true, false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private OnStatusChanged statusListener = new OnStatusChanged() {
|
private OnStatusChanged statusListener = new OnStatusChanged() {
|
||||||
|
@ -459,7 +459,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
final String action = intent == null ? null : intent.getAction();
|
final String action = intent == null ? null : intent.getAction();
|
||||||
boolean interactive = false;
|
boolean interactive = false;
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
Log.d(Config.LOGTAG, "action: " + action);
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ConnectivityManager.CONNECTIVITY_ACTION:
|
case ConnectivityManager.CONNECTIVITY_ACTION:
|
||||||
if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
|
if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
|
||||||
|
@ -760,6 +759,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
connection.setOnBindListener(this.mOnBindListener);
|
connection.setOnBindListener(this.mOnBindListener);
|
||||||
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
|
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
|
||||||
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
|
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
|
||||||
|
AxolotlService axolotlService = account.getAxolotlService();
|
||||||
|
if (axolotlService != null) {
|
||||||
|
connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
|
||||||
|
}
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1066,8 +1069,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
public void run() {
|
public void run() {
|
||||||
Log.d(Config.LOGTAG, "restoring roster");
|
Log.d(Config.LOGTAG, "restoring roster");
|
||||||
for (Account account : accounts) {
|
for (Account account : accounts) {
|
||||||
databaseBackend.readRoster(account.getRoster());
|
|
||||||
account.initAccountServices(XmppConnectionService.this);
|
account.initAccountServices(XmppConnectionService.this);
|
||||||
|
databaseBackend.readRoster(account.getRoster());
|
||||||
}
|
}
|
||||||
getBitmapCache().evictAll();
|
getBitmapCache().evictAll();
|
||||||
Looper.prepare();
|
Looper.prepare();
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.widget.Button;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.whispersystems.libaxolotl.IdentityKey;
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
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;
|
||||||
|
@ -27,11 +29,11 @@ import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
|
public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
|
||||||
private Jid accountJid;
|
private Jid accountJid;
|
||||||
private Jid contactJid;
|
private Jid contactJid;
|
||||||
private boolean hasOtherTrustedKeys = false;
|
|
||||||
private boolean hasPendingFetches = false;
|
|
||||||
private boolean hasNoTrustedKeys = true;
|
private boolean hasNoTrustedKeys = true;
|
||||||
|
|
||||||
private Contact contact;
|
private Contact contact;
|
||||||
|
private Account mAccount;
|
||||||
private TextView keyErrorMessage;
|
private TextView keyErrorMessage;
|
||||||
private LinearLayout keyErrorMessageCard;
|
private LinearLayout keyErrorMessageCard;
|
||||||
private TextView ownKeysTitle;
|
private TextView ownKeysTitle;
|
||||||
|
@ -50,10 +52,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
commitTrusts();
|
commitTrusts();
|
||||||
Intent data = new Intent();
|
finishOk();
|
||||||
data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
|
|
||||||
setResult(RESULT_OK, data);
|
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -157,11 +156,11 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
foreignKeysTitle.setText(contactJid.toString());
|
foreignKeysTitle.setText(contactJid.toString());
|
||||||
foreignKeysCard.setVisibility(View.VISIBLE);
|
foreignKeysCard.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
if(hasPendingFetches) {
|
if(hasPendingKeyFetches()) {
|
||||||
setFetching();
|
setFetching();
|
||||||
lock();
|
lock();
|
||||||
} else {
|
} else {
|
||||||
if (!hasForeignKeys && !hasOtherTrustedKeys) {
|
if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
|
||||||
keyErrorMessageCard.setVisibility(View.VISIBLE);
|
keyErrorMessageCard.setVisibility(View.VISIBLE);
|
||||||
keyErrorMessage.setText(R.string.error_no_keys_to_trust);
|
keyErrorMessage.setText(R.string.error_no_keys_to_trust);
|
||||||
ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
|
ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
|
||||||
|
@ -172,12 +171,13 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getFingerprints(final Account account) {
|
private boolean reloadFingerprints() {
|
||||||
Set<IdentityKey> ownKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
|
AxolotlService service = this.mAccount.getAxolotlService();
|
||||||
Set<IdentityKey> foreignKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact);
|
Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
|
||||||
|
Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact);
|
||||||
if (hasNoTrustedKeys) {
|
if (hasNoTrustedKeys) {
|
||||||
ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED));
|
ownKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED));
|
||||||
foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
|
foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
|
||||||
}
|
}
|
||||||
for(final IdentityKey identityKey : ownKeysSet) {
|
for(final IdentityKey identityKey : ownKeysSet) {
|
||||||
if(!ownKeysToTrust.containsKey(identityKey)) {
|
if(!ownKeysToTrust.containsKey(identityKey)) {
|
||||||
|
@ -189,39 +189,55 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
|
foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ownKeysSet.size() + foreignKeysSet.size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackendConnected() {
|
public void onBackendConnected() {
|
||||||
if ((accountJid != null) && (contactJid != null)) {
|
if ((accountJid != null) && (contactJid != null)) {
|
||||||
final Account account = xmppConnectionService
|
this.mAccount = xmppConnectionService.findAccountByJid(accountJid);
|
||||||
.findAccountByJid(accountJid);
|
if (this.mAccount == null) {
|
||||||
if (account == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.contact = account.getRoster().getContact(contactJid);
|
this.contact = this.mAccount.getRoster().getContact(contactJid);
|
||||||
ownKeysToTrust.clear();
|
ownKeysToTrust.clear();
|
||||||
foreignKeysToTrust.clear();
|
foreignKeysToTrust.clear();
|
||||||
getFingerprints(account);
|
reloadFingerprints();
|
||||||
|
|
||||||
if(account.getAxolotlService().getNumTrustedKeys(contact) > 0) {
|
|
||||||
hasOtherTrustedKeys = true;
|
|
||||||
}
|
|
||||||
Conversation conversation = xmppConnectionService.findOrCreateConversation(account, contactJid, false);
|
|
||||||
if(account.getAxolotlService().hasPendingKeyFetches(conversation)) {
|
|
||||||
hasPendingFetches = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
populateView();
|
populateView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasNoOtherTrustedKeys() {
|
||||||
|
return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasPendingKeyFetches() {
|
||||||
|
return mAccount != null && contact != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount,contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onKeyStatusUpdated() {
|
public void onKeyStatusUpdated() {
|
||||||
final Account account = xmppConnectionService.findAccountByJid(accountJid);
|
boolean keysToTrust = reloadFingerprints();
|
||||||
hasPendingFetches = false;
|
if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
|
||||||
getFingerprints(account);
|
|
||||||
refreshUi();
|
refreshUi();
|
||||||
|
} else {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(TrustKeysActivity.this, "Nothing to do", Toast.LENGTH_SHORT).show();
|
||||||
|
finishOk();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishOk() {
|
||||||
|
Intent data = new Intent();
|
||||||
|
data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
|
||||||
|
setResult(RESULT_OK, data);
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitTrusts() {
|
private void commitTrusts() {
|
||||||
|
@ -248,7 +264,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lockOrUnlockAsNeeded() {
|
private void lockOrUnlockAsNeeded() {
|
||||||
if (!hasOtherTrustedKeys && !foreignKeysToTrust.values().contains(true)){
|
if (hasNoOtherTrustedKeys() && !foreignKeysToTrust.values().contains(true)){
|
||||||
lock();
|
lock();
|
||||||
} else {
|
} else {
|
||||||
unlock();
|
unlock();
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
package eu.siacs.conversations.utils;
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
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 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.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.cert.X509Extension;
|
||||||
import java.text.Normalizer;
|
import java.text.Normalizer;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -137,11 +142,26 @@ public final class CryptoHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pair<Jid,String> extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException {
|
public static Pair<Jid,String> extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException, CertificateParsingException {
|
||||||
|
Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames();
|
||||||
|
List<String> emails = new ArrayList<>();
|
||||||
|
if (alternativeNames != null) {
|
||||||
|
for(List<?> san : alternativeNames) {
|
||||||
|
Integer type = (Integer) san.get(0);
|
||||||
|
if (type == 1) {
|
||||||
|
emails.add((String) san.get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
|
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());
|
if (emails.size() == 0) {
|
||||||
String email = IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue());
|
emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue()));
|
||||||
|
}
|
||||||
String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue());
|
String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue());
|
||||||
return new Pair<>(Jid.fromString(email),name);
|
if (emails.size() >= 1) {
|
||||||
|
return new Pair<>(Jid.fromString(emails.get(0)), name);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -947,11 +947,10 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
disco.put(jid, info);
|
disco.put(jid, info);
|
||||||
if (account.getServer().equals(jid)) {
|
if ((jid.equals(account.getServer()) || jid.equals(account.getJid().toBareJid()))
|
||||||
|
&& disco.containsKey(account.getServer())
|
||||||
|
&& disco.containsKey(account.getJid().toBareJid())) {
|
||||||
enableAdvancedStreamFeatures();
|
enableAdvancedStreamFeatures();
|
||||||
for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
|
|
||||||
listener.onAdvancedStreamFeaturesAvailable(account);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not query disco info for "+jid.toString());
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not query disco info for "+jid.toString());
|
||||||
|
@ -969,6 +968,9 @@ public class XmppConnection implements Runnable {
|
||||||
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list");
|
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list");
|
||||||
this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser());
|
this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser());
|
||||||
}
|
}
|
||||||
|
for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
|
||||||
|
listener.onAdvancedStreamFeaturesAvailable(account);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendServiceDiscoveryItems(final Jid server) {
|
private void sendServiceDiscoveryItems(final Jid server) {
|
||||||
|
|
Loading…
Reference in a new issue