@@ -8,7 +8,13 @@ import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libsignal.IdentityKey;
@@ -733,58 +739,62 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
axolotlStore.setFingerprintStatus(fingerprint, status);
}
- private void verifySessionWithPEP(final XmppAxolotlSession session) {
+ private ListenableFuture<XmppAxolotlSession> verifySessionWithPEP(final XmppAxolotlSession session) {
Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
final SignalProtocolAddress address = session.getRemoteAddress();
final IdentityKey identityKey = session.getIdentityKey();
+ final Jid jid;
try {
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.of(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) {
+ jid = Jid.of(address.getName());
+ } catch (final IllegalArgumentException e) {
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ return Futures.immediateFuture(session);
+ }
+ final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
+ final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
+ Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
+ if (verification != null) {
+ try {
+ Signature verifier = Signature.getInstance("sha256WithRSA");
+ verifier.initVerify(verification.first[0]);
+ verifier.update(identityKey.serialize());
+ if (verifier.verify(verification.second)) {
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");
- String fingerprint = session.getFingerprint();
- Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint);
- setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
- axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
- fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
- Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid = Jid.of(address.getName());
- Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
- account.getRoster().getContact(jid).setCommonName(cn);
- } catch (final IllegalArgumentException ignored) {
- //ignored
- }
- finishBuildingSessionsFromPEP(address);
- return;
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "could not verify certificate");
- }
+ mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
+ String fingerprint = session.getFingerprint();
+ Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint);
+ setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
+ axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
+ fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
+ Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid1 = Jid.of(address.getName());
+ Log.d(Config.LOGTAG, "setting common name for " + jid1 + " to " + cn);
+ account.getRoster().getContact(jid1).setCommonName(cn);
+ } catch (final IllegalArgumentException ignored) {
+ //ignored
}
+ finishBuildingSessionsFromPEP(address);
+ future.set(session);
+ return;
} catch (Exception e) {
- Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
+ Log.d(Config.LOGTAG, "could not verify certificate");
}
- } else {
- Log.d(Config.LOGTAG, "no verification found");
}
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
}
- });
- } catch (IllegalArgumentException e) {
+ } else {
+ Log.d(Config.LOGTAG, "no verification found");
+ }
fetchStatusMap.put(address, FetchStatus.SUCCESS);
finishBuildingSessionsFromPEP(address);
- }
+ future.set(session);
+ });
+ return future;
}
private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
@@ -1255,12 +1265,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
);
}
- public OmemoVerifiedPayload<RtpContentMap> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) throws CryptoFailedException {
+ public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
final OmemoVerification omemoVerification = new OmemoVerification();
+ final ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures = new ImmutableList.Builder<>();
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : omemoVerifiedRtpContentMap.contents.entrySet()) {
final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
- final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from);
+ final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport;
+ try {
+ decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures);
+ } catch (CryptoFailedException e) {
+ return Futures.immediateFailedFuture(e);
+ }
omemoVerification.setOrEnsureEqual(decryptedTransport);
descriptionTransportBuilder.put(
content.getKey(),
@@ -1268,13 +1284,26 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
);
}
processPostponed();
- return new OmemoVerifiedPayload<>(
- omemoVerification,
- new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
+ final ImmutableList<ListenableFuture<XmppAxolotlSession>> sessionFutures = pepVerificationFutures.build();
+ return Futures.transform(
+ Futures.allAsList(sessionFutures),
+ sessions -> {
+ if (Config.REQUIRE_RTP_VERIFICATION) {
+ for (XmppAxolotlSession session : sessions) {
+ requireVerification(session);
+ }
+ }
+ return new OmemoVerifiedPayload<>(
+ omemoVerification,
+ new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
+ );
+
+ },
+ MoreExecutors.directExecutor()
);
}
- private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from) throws CryptoFailedException {
+ private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures) throws CryptoFailedException {
final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
final OmemoVerification omemoVerification = new OmemoVerification();
@@ -1286,14 +1315,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage);
- if (Config.REQUIRE_RTP_VERIFICATION) {
- requireVerification(session);
- }
final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext = xmppAxolotlMessage.decrypt(session, getOwnDeviceId());
final Integer preKeyId = session.getPreKeyIdAndReset();
if (preKeyId != null) {
postponedSessions.add(session);
}
+ if (session.isFresh()) {
+ pepVerificationFutures.add(putFreshSession(session));
+ } else if (Config.REQUIRE_RTP_VERIFICATION) {
+ pepVerificationFutures.add(Futures.immediateFuture(session));
+ }
fingerprint.setContent(plaintext.getPlaintext());
omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
@@ -1512,15 +1543,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return keyTransportMessage;
}
- private void putFreshSession(XmppAxolotlSession session) {
+ private ListenableFuture<XmppAxolotlSession> putFreshSession(XmppAxolotlSession session) {
sessions.put(session);
if (Config.X509_VERIFICATION) {
if (session.getIdentityKey() != null) {
- verifySessionWithPEP(session);
+ return verifySessionWithPEP(session);
} else {
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
}
}
+ return Futures.immediateFuture(session);
}
public enum FetchStatus {
@@ -3,6 +3,9 @@ package eu.siacs.conversations.xmpp.jingle;
import android.os.SystemClock;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
@@ -12,7 +15,10 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
@@ -243,7 +249,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
private void receiveTransportInfo(final JinglePacket jinglePacket) {
- if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
+ //Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to INITIALIZED only after transport-info has been received
+ if (isInState(State.NULL, State.PROCEED, State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
respondOk(jinglePacket);
final RtpContentMap contentMap;
try {
@@ -306,22 +313,19 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
}
- private RtpContentMap receiveRtpContentMap(final JinglePacket jinglePacket, final boolean expectVerification) {
+ private ListenableFuture<RtpContentMap> receiveRtpContentMap(final JinglePacket jinglePacket, final boolean expectVerification) {
final RtpContentMap receivedContentMap = RtpContentMap.of(jinglePacket);
if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) {
- final AxolotlService.OmemoVerifiedPayload<RtpContentMap> omemoVerifiedPayload;
- try {
- omemoVerifiedPayload = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with);
- } catch (final CryptoFailedException e) {
- throw new SecurityException("Unable to verify DTLS Fingerprint with OMEMO", e);
- }
- this.omemoVerification.setOrEnsureEqual(omemoVerifiedPayload);
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + this.omemoVerification);
- return omemoVerifiedPayload.getPayload();
+ final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with);
+ return Futures.transform(future, omemoVerifiedPayload -> {
+ omemoVerification.setOrEnsureEqual(omemoVerifiedPayload);
+ Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification);
+ return omemoVerifiedPayload.getPayload();
+ }, MoreExecutors.directExecutor());
} else if (expectVerification) {
throw new SecurityException("DTLS fingerprint was unexpectedly not verifiable");
} else {
- return receivedContentMap;
+ return Futures.immediateFuture(receivedContentMap);
}
}
@@ -340,9 +344,23 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
return;
}
- final RtpContentMap contentMap;
+ final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, false);
+ Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
+ @Override
+ public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
+ receiveSessionInitiate(jinglePacket, rtpContentMap);
+ }
+
+ @Override
+ public void onFailure(@NonNull final Throwable throwable) {
+ respondOk(jinglePacket);
+ sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ private void receiveSessionInitiate(final JinglePacket jinglePacket, final RtpContentMap contentMap) {
try {
- contentMap = receiveRtpContentMap(jinglePacket, false);
contentMap.requireContentDescriptions();
contentMap.requireDTLSFingerprint();
} catch (final RuntimeException e) {
@@ -396,9 +414,25 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
terminateWithOutOfOrder(jinglePacket);
return;
}
- final RtpContentMap contentMap;
+ final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
+ Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
+ @Override
+ public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
+ receiveSessionAccept(jinglePacket, rtpContentMap);
+ }
+
+ @Override
+ public void onFailure(@NonNull final Throwable throwable) {
+ respondOk(jinglePacket);
+ Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents in session-accept", throwable);
+ webRTCWrapper.close();
+ sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ private void receiveSessionAccept(final JinglePacket jinglePacket, final RtpContentMap contentMap) {
try {
- contentMap = receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
contentMap.requireContentDescriptions();
contentMap.requireDTLSFingerprint();
} catch (final RuntimeException e) {
@@ -762,7 +796,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} catch (final WebRTCWrapper.InitializationException e) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to initialize WebRTC");
webRTCWrapper.close();
- sendRetract(Reason.ofException(e));
+ sendRetract(Reason.ofThrowable(e));
return;
}
try {
@@ -774,7 +808,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} catch (final Exception e) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(e));
webRTCWrapper.close();
- final Reason reason = Reason.ofException(e);
+ final Reason reason = Reason.ofThrowable(e);
if (isInState(targetState)) {
sendSessionTerminate(reason);
} else {
@@ -1010,7 +1044,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return false;
}
final FingerprintStatus status = id.account.getAxolotlService().getFingerprintTrust(fingerprint);
- return status != null && status.getTrust() == FingerprintStatus.Trust.VERIFIED;
+ return status != null && status.isVerified();
}
public synchronized void acceptCall() {