encrypt rtp map as future
This commit is contained in:
parent
337aa4a110
commit
8d391753d7
|
@ -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<>();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue