~singpolyma/cheogram-android

8d391753d74a441099b3b2372f3cacc35cb382d1 — Daniel Gultsch 2 years ago 337aa4a
encrypt rtp map as future
M src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java => src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +32 -13
@@ 8,7 8,6 @@ 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;


@@ 1238,31 1237,51 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
    }


    public OmemoVerifiedPayload<OmemoVerifiedRtpContentMap> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) throws CryptoFailedException {
        final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
        final XmppAxolotlSession session = sessions.get(address);
        if (session == null) {
            throw new CryptoFailedException(String.format("No session found for %d", deviceId));
        }
    public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
        return Futures.transformAsync(
                getSession(jid, deviceId),
                session -> encrypt(rtpContentMap, session),
                MoreExecutors.directExecutor()
        );
    }

    private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
        if (Config.REQUIRE_RTP_VERIFICATION) {
            requireVerification(session);
        }
        final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
        final OmemoVerification omemoVerification = new OmemoVerification();
        omemoVerification.setDeviceId(deviceId);
        omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
        omemoVerification.setSessionFingerprint(session.getFingerprint());
        for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) {
            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(
                    content.getKey(),
                    new RtpContentMap.DescriptionTransport(descriptionTransport.description, encryptedTransportInfo)
            );
        }
        return new OmemoVerifiedPayload<>(
                omemoVerification,
                new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
        );
        return Futures.immediateFuture(
                new OmemoVerifiedPayload<>(
                        omemoVerification,
                        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) {

M src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java => src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +105 -47
@@ 318,6 318,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
        if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) {
            final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with);
            return Futures.transform(future, omemoVerifiedPayload -> {
                //TODO test if an exception here triggers a correct abort
                omemoVerification.setOrEnsureEqual(omemoVerifiedPayload);
                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification);
                return omemoVerifiedPayload.getPayload();


@@ 532,17 533,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
            this.webRTCWrapper.setRemoteDescription(sdp).get();
            addIceCandidatesFromBlackLog();
            org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get();
            final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
            final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
            sendSessionAccept(respondingRtpContentMap);
            this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
            prepareSessionAccept(webRTCSessionDescription);
        } catch (final Exception e) {
            Log.d(Config.LOGTAG, "unable to send session accept", Throwables.getRootCause(e));
            webRTCWrapper.close();
            sendSessionTerminate(Reason.FAILED_APPLICATION);
            failureToAcceptSession(e);
        }
    }

    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() {
        while (!this.pendingIceCandidates.isEmpty()) {
            processCandidates(this.pendingIceCandidates.poll());


@@ 550,24 552,49 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
        }
    }

    private void sendSessionAccept(final RtpContentMap rtpContentMap) {
        this.responderRtpContentMap = rtpContentMap;
        this.transitionOrThrow(State.SESSION_ACCEPTED);
        final RtpContentMap outgoingContentMap;
    private void prepareSessionAccept(final org.webrtc.SessionDescription webRTCSessionDescription) {
        final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
        final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
        this.responderRtpContentMap = respondingRtpContentMap;
        final ListenableFuture<RtpContentMap> outgoingContentMapFuture = prepareOutgoingContentMap(respondingRtpContentMap);
        Futures.addCallback(outgoingContentMapFuture,
                new FutureCallback<RtpContentMap>() {
                    @Override
                    public void onSuccess(final RtpContentMap outgoingContentMap) {
                        sendSessionAccept(outgoingContentMap, webRTCSessionDescription);
                    }

                    @Override
                    public void onFailure(@NonNull Throwable throwable) {
                        failureToAcceptSession(throwable);
                    }
                },
                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);
        try {
            webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
        } catch (Exception e) {
            failureToAcceptSession(e);
        }
    }

    private ListenableFuture<RtpContentMap> prepareOutgoingContentMap(final RtpContentMap rtpContentMap) {
        if (this.omemoVerification.hasDeviceId()) {
            final AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap> verifiedPayload;
            try {
                verifiedPayload = id.account.getAxolotlService().encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId());
                outgoingContentMap = verifiedPayload.getPayload();
                this.omemoVerification.setOrEnsureEqual(verifiedPayload);
            } catch (final Exception e) {
                throw new SecurityException("Unable to verify DTLS Fingerprint with OMEMO", e);
            }
            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 {
            outgoingContentMap = rtpContentMap;
            return Futures.immediateFuture(rtpContentMap);
        }
        final JinglePacket sessionAccept = outgoingContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
        send(sessionAccept);
    }

    synchronized void deliveryMessage(final Jid from, final Element message, final String serverMessageId, final long timestamp) {


@@ 803,19 830,20 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
        }
        try {
            org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get();
            final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
            final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
            sendSessionInitiate(rtpContentMap, targetState);
            this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
            prepareSessionInitiate(webRTCSessionDescription, targetState);
        } catch (final Exception e) {
            Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(e));
            webRTCWrapper.close();
            final Reason reason = Reason.ofThrowable(e);
            if (isInState(targetState)) {
                sendSessionTerminate(reason);
            } else {
                sendRetract(reason);
            }
            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();
        final Reason reason = Reason.ofThrowable(throwable);
        if (isInState(targetState)) {
            sendSessionTerminate(reason);
        } else {
            sendRetract(reason);
        }
    }



@@ 826,27 854,57 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
        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;
        final RtpContentMap outgoingContentMap = encryptSessionInitiate(rtpContentMap);
        final JinglePacket sessionInitiate = outgoingContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
        final ListenableFuture<RtpContentMap> outgoingContentMapFuture = encryptSessionInitiate(rtpContentMap);
        Futures.addCallback(outgoingContentMapFuture, new FutureCallback<RtpContentMap>() {
            @Override
            public void onSuccess(final RtpContentMap outgoingContentMap) {
                sendSessionInitiate(outgoingContentMap, webRTCSessionDescription, targetState);
            }

            @Override
            public void onFailure(@NonNull final Throwable throwable) {
                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 {
            this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get();
        } catch (Exception e) {
            failureToInitiateSession(e, targetState);
        }
    }

    private RtpContentMap encryptSessionInitiate(final RtpContentMap rtpContentMap) {
    private ListenableFuture<RtpContentMap> encryptSessionInitiate(final RtpContentMap rtpContentMap) {
        if (this.omemoVerification.hasDeviceId()) {
            final AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap> verifiedPayload;
            try {
                verifiedPayload = id.account.getAxolotlService().encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId());
            } catch (final CryptoFailedException e) {
                Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to use OMEMO DTLS verification on outgoing session initiate. falling back", e);
                return rtpContentMap;
            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;
            }
            this.omemoVerification.setSessionFingerprint(verifiedPayload.getFingerprint());
            return verifiedPayload.getPayload();
            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);
                        return rtpContentMap;
                    },
                    MoreExecutors.directExecutor()
            );
        } else {
            return rtpContentMap;
            return Futures.immediateFuture(rtpContentMap);
        }
    }


M src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java => src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java +4 -0
@@ 6,6 6,7 @@ import com.google.common.base.CaseFormat;
import com.google.common.base.Throwables;

import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.CryptoFailedException;
import eu.siacs.conversations.xmpp.jingle.RtpContentMap;

public enum Reason {


@@ 59,6 60,9 @@ public enum Reason {
        if (root instanceof RuntimeException) {
            return of((RuntimeException) root);
        }
        if (root instanceof CryptoFailedException) {
            return SECURITY_ERROR;
        }
        return FAILED_APPLICATION;
    }
}
\ No newline at end of file