encrypt rtp map as future

This commit is contained in:
Daniel Gultsch 2021-05-08 08:45:31 +02:00
parent 337aa4a110
commit 8d391753d7
3 changed files with 145 additions and 64 deletions

View file

@ -8,7 +8,6 @@ import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
@ -1238,32 +1237,52 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
public OmemoVerifiedPayload<OmemoVerifiedRtpContentMap> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) throws CryptoFailedException { public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId); return Futures.transformAsync(
final XmppAxolotlSession session = sessions.get(address); getSession(jid, deviceId),
if (session == null) { session -> encrypt(rtpContentMap, session),
throw new CryptoFailedException(String.format("No session found for %d", deviceId)); MoreExecutors.directExecutor()
);
} }
private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
if (Config.REQUIRE_RTP_VERIFICATION) { if (Config.REQUIRE_RTP_VERIFICATION) {
requireVerification(session); requireVerification(session);
} }
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>(); final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
final OmemoVerification omemoVerification = new OmemoVerification(); final OmemoVerification omemoVerification = new OmemoVerification();
omemoVerification.setDeviceId(deviceId); omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
omemoVerification.setSessionFingerprint(session.getFingerprint()); omemoVerification.setSessionFingerprint(session.getFingerprint());
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) { for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) {
final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue(); final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo = encrypt(descriptionTransport.transport, session); final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo;
try {
encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
} catch (final CryptoFailedException e) {
return Futures.immediateFailedFuture(e);
}
descriptionTransportBuilder.put( descriptionTransportBuilder.put(
content.getKey(), content.getKey(),
new RtpContentMap.DescriptionTransport(descriptionTransport.description, encryptedTransportInfo) new RtpContentMap.DescriptionTransport(descriptionTransport.description, encryptedTransportInfo)
); );
} }
return new OmemoVerifiedPayload<>( return Futures.immediateFuture(
new OmemoVerifiedPayload<>(
omemoVerification, omemoVerification,
new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build()) new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
));
}
private ListenableFuture<XmppAxolotlSession> getSession(final Jid jid, final int deviceId) {
final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
final XmppAxolotlSession session = sessions.get(address);
if (session == null) {
return Futures.immediateFailedFuture(
new CryptoFailedException(String.format("No session found for %d", deviceId))
); );
} }
return Futures.immediateFuture(session);
}
public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) { public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>(); final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();

View file

@ -318,6 +318,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) { if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) {
final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with); final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with);
return Futures.transform(future, omemoVerifiedPayload -> { return Futures.transform(future, omemoVerifiedPayload -> {
//TODO test if an exception here triggers a correct abort
omemoVerification.setOrEnsureEqual(omemoVerifiedPayload); omemoVerification.setOrEnsureEqual(omemoVerifiedPayload);
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification);
return omemoVerifiedPayload.getPayload(); return omemoVerifiedPayload.getPayload();
@ -532,17 +533,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
this.webRTCWrapper.setRemoteDescription(sdp).get(); this.webRTCWrapper.setRemoteDescription(sdp).get();
addIceCandidatesFromBlackLog(); addIceCandidatesFromBlackLog();
org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get(); org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get();
final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); prepareSessionAccept(webRTCSessionDescription);
final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
sendSessionAccept(respondingRtpContentMap);
this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
} catch (final Exception e) { } catch (final Exception e) {
Log.d(Config.LOGTAG, "unable to send session accept", Throwables.getRootCause(e)); failureToAcceptSession(e);
webRTCWrapper.close();
sendSessionTerminate(Reason.FAILED_APPLICATION);
} }
} }
private void failureToAcceptSession(final Throwable throwable) {
Log.d(Config.LOGTAG, "unable to send session accept", Throwables.getRootCause(throwable));
webRTCWrapper.close();
sendSessionTerminate(Reason.ofThrowable(throwable));
}
private void addIceCandidatesFromBlackLog() { private void addIceCandidatesFromBlackLog() {
while (!this.pendingIceCandidates.isEmpty()) { while (!this.pendingIceCandidates.isEmpty()) {
processCandidates(this.pendingIceCandidates.poll()); processCandidates(this.pendingIceCandidates.poll());
@ -550,24 +552,49 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
} }
private void sendSessionAccept(final RtpContentMap rtpContentMap) { private void prepareSessionAccept(final org.webrtc.SessionDescription webRTCSessionDescription) {
this.responderRtpContentMap = rtpContentMap; final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
this.transitionOrThrow(State.SESSION_ACCEPTED); final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
final RtpContentMap outgoingContentMap; this.responderRtpContentMap = respondingRtpContentMap;
if (this.omemoVerification.hasDeviceId()) { final ListenableFuture<RtpContentMap> outgoingContentMapFuture = prepareOutgoingContentMap(respondingRtpContentMap);
final AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap> verifiedPayload; Futures.addCallback(outgoingContentMapFuture,
try { new FutureCallback<RtpContentMap>() {
verifiedPayload = id.account.getAxolotlService().encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId()); @Override
outgoingContentMap = verifiedPayload.getPayload(); public void onSuccess(final RtpContentMap outgoingContentMap) {
this.omemoVerification.setOrEnsureEqual(verifiedPayload); sendSessionAccept(outgoingContentMap, webRTCSessionDescription);
} catch (final Exception e) {
throw new SecurityException("Unable to verify DTLS Fingerprint with OMEMO", e);
} }
} else {
outgoingContentMap = rtpContentMap; @Override
public void onFailure(@NonNull Throwable throwable) {
failureToAcceptSession(throwable);
} }
final JinglePacket sessionAccept = outgoingContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); },
MoreExecutors.directExecutor()
);
}
private void sendSessionAccept(final RtpContentMap rtpContentMap, final org.webrtc.SessionDescription webRTCSessionDescription) {
transitionOrThrow(State.SESSION_ACCEPTED);
final JinglePacket sessionAccept = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
send(sessionAccept); send(sessionAccept);
try {
webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
} catch (Exception e) {
failureToAcceptSession(e);
}
}
private ListenableFuture<RtpContentMap> prepareOutgoingContentMap(final RtpContentMap rtpContentMap) {
if (this.omemoVerification.hasDeviceId()) {
ListenableFuture<AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> verifiedPayloadFuture = id.account.getAxolotlService()
.encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId());
return Futures.transform(verifiedPayloadFuture, verifiedPayload -> {
omemoVerification.setOrEnsureEqual(verifiedPayload);
return verifiedPayload.getPayload();
}, MoreExecutors.directExecutor());
} else {
return Futures.immediateFuture(rtpContentMap);
}
} }
synchronized void deliveryMessage(final Jid from, final Element message, final String serverMessageId, final long timestamp) { synchronized void deliveryMessage(final Jid from, final Element message, final String serverMessageId, final long timestamp) {
@ -803,21 +830,22 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} }
try { try {
org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get(); org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get();
final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); prepareSessionInitiate(webRTCSessionDescription, targetState);
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
sendSessionInitiate(rtpContentMap, targetState);
this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
} catch (final Exception e) { } catch (final Exception e) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(e)); failureToInitiateSession(e, targetState);
}
}
private void failureToInitiateSession(final Throwable throwable, final State targetState) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(throwable));
webRTCWrapper.close(); webRTCWrapper.close();
final Reason reason = Reason.ofThrowable(e); final Reason reason = Reason.ofThrowable(throwable);
if (isInState(targetState)) { if (isInState(targetState)) {
sendSessionTerminate(reason); sendSessionTerminate(reason);
} else { } else {
sendRetract(reason); sendRetract(reason);
} }
} }
}
private void sendRetract(final Reason reason) { private void sendRetract(final Reason reason) {
//TODO embed reason into retract //TODO embed reason into retract
@ -826,27 +854,57 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
this.finish(); this.finish();
} }
private void sendSessionInitiate(final RtpContentMap rtpContentMap, final State targetState) { private void prepareSessionInitiate(final org.webrtc.SessionDescription webRTCSessionDescription, final State targetState) {
final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
this.initiatorRtpContentMap = rtpContentMap; this.initiatorRtpContentMap = rtpContentMap;
final RtpContentMap outgoingContentMap = encryptSessionInitiate(rtpContentMap); final ListenableFuture<RtpContentMap> outgoingContentMapFuture = encryptSessionInitiate(rtpContentMap);
final JinglePacket sessionInitiate = outgoingContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); Futures.addCallback(outgoingContentMapFuture, new FutureCallback<RtpContentMap>() {
this.transitionOrThrow(targetState); @Override
send(sessionInitiate); public void onSuccess(final RtpContentMap outgoingContentMap) {
sendSessionInitiate(outgoingContentMap, webRTCSessionDescription, targetState);
} }
private RtpContentMap encryptSessionInitiate(final RtpContentMap rtpContentMap) { @Override
if (this.omemoVerification.hasDeviceId()) { public void onFailure(@NonNull final Throwable throwable) {
final AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap> verifiedPayload; failureToInitiateSession(throwable, targetState);
}
}, MoreExecutors.directExecutor());
}
private void sendSessionInitiate(final RtpContentMap rtpContentMap, final org.webrtc.SessionDescription webRTCSessionDescription, final State targetState) {
this.transitionOrThrow(targetState);
final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
send(sessionInitiate);
try { try {
verifiedPayload = id.account.getAxolotlService().encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId()); this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
} catch (final CryptoFailedException e) { } catch (Exception e) {
failureToInitiateSession(e, targetState);
}
}
private ListenableFuture<RtpContentMap> encryptSessionInitiate(final RtpContentMap rtpContentMap) {
if (this.omemoVerification.hasDeviceId()) {
final ListenableFuture<AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> verifiedPayloadFuture = id.account.getAxolotlService()
.encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId());
final ListenableFuture<RtpContentMap> future = Futures.transform(verifiedPayloadFuture, verifiedPayload -> {
omemoVerification.setSessionFingerprint(verifiedPayload.getFingerprint());
return verifiedPayload.getPayload();
}, MoreExecutors.directExecutor());
if (Config.REQUIRE_RTP_VERIFICATION) {
return future;
}
return Futures.catching(
future,
CryptoFailedException.class,
e -> {
Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to use OMEMO DTLS verification on outgoing session initiate. falling back", e); Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to use OMEMO DTLS verification on outgoing session initiate. falling back", e);
return rtpContentMap; return rtpContentMap;
} },
this.omemoVerification.setSessionFingerprint(verifiedPayload.getFingerprint()); MoreExecutors.directExecutor()
return verifiedPayload.getPayload(); );
} else { } else {
return rtpContentMap; return Futures.immediateFuture(rtpContentMap);
} }
} }

View file

@ -6,6 +6,7 @@ import com.google.common.base.CaseFormat;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.CryptoFailedException;
import eu.siacs.conversations.xmpp.jingle.RtpContentMap; import eu.siacs.conversations.xmpp.jingle.RtpContentMap;
public enum Reason { public enum Reason {
@ -59,6 +60,9 @@ public enum Reason {
if (root instanceof RuntimeException) { if (root instanceof RuntimeException) {
return of((RuntimeException) root); return of((RuntimeException) root);
} }
if (root instanceof CryptoFailedException) {
return SECURITY_ERROR;
}
return FAILED_APPLICATION; return FAILED_APPLICATION;
} }
} }