support omemo verification in non stub transport content modifications

Dino (and this is probably correct behaviour) expects a fingerprint in the
content-add message. (and not a stub transport as indicated in the examples).

however if we start to include them we also need to encrypt and verify them
properly.
This commit is contained in:
Daniel Gultsch 2023-10-09 13:17:14 +02:00
parent 48bd845323
commit d3d582759f
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
3 changed files with 126 additions and 28 deletions

View file

@ -370,6 +370,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private void receiveContentAdd(final JinglePacket jinglePacket) { private void receiveContentAdd(final JinglePacket jinglePacket) {
// TODO check if in session accepted
final RtpContentMap modification; final RtpContentMap modification;
try { try {
modification = RtpContentMap.of(jinglePacket); modification = RtpContentMap.of(jinglePacket);
@ -385,7 +386,29 @@ public class JingleRtpConnection extends AbstractJingleConnection
return; return;
} }
if (isInState(State.SESSION_ACCEPTED)) { if (isInState(State.SESSION_ACCEPTED)) {
receiveContentAdd(jinglePacket, modification); final boolean hasFullTransportInfo = modification.hasFullTransportInfo();
final ListenableFuture<RtpContentMap> future =
receiveRtpContentMap(
modification, this.omemoVerification.hasFingerprint() && hasFullTransportInfo);
Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
@Override
public void onSuccess(final RtpContentMap rtpContentMap) {
receiveContentAdd(jinglePacket, rtpContentMap);
}
@Override
public void onFailure(@NonNull Throwable throwable) {
respondOk(jinglePacket);
final Throwable rootCause = Throwables.getRootCause(throwable);
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid()
+ ": improperly formatted contents in content-add",
throwable);
webRTCWrapper.close();
sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage());
}
}, MoreExecutors.directExecutor());
} else { } else {
terminateWithOutOfOrder(jinglePacket); terminateWithOutOfOrder(jinglePacket);
} }
@ -470,7 +493,22 @@ public class JingleRtpConnection extends AbstractJingleConnection
if (ourSummary.equals(ContentAddition.summary(receivedContentAccept))) { if (ourSummary.equals(ContentAddition.summary(receivedContentAccept))) {
this.outgoingContentAdd = null; this.outgoingContentAdd = null;
respondOk(jinglePacket); respondOk(jinglePacket);
receiveContentAccept(receivedContentAccept); final boolean hasFullTransportInfo = receivedContentAccept.hasFullTransportInfo();
final ListenableFuture<RtpContentMap> future =
receiveRtpContentMap(
receivedContentAccept, this.omemoVerification.hasFingerprint() && hasFullTransportInfo);
Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
@Override
public void onSuccess(final RtpContentMap result) {
receiveContentAccept(result);
}
@Override
public void onFailure(@NonNull final Throwable throwable) {
webRTCWrapper.close();
sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
}
}, MoreExecutors.directExecutor());
} else { } else {
Log.d(Config.LOGTAG, "received content-accept did not match our outgoing content-add"); Log.d(Config.LOGTAG, "received content-accept did not match our outgoing content-add");
terminateWithOutOfOrder(jinglePacket); terminateWithOutOfOrder(jinglePacket);
@ -759,14 +797,29 @@ public class JingleRtpConnection extends AbstractJingleConnection
final RtpContentMap contentAcceptMap = final RtpContentMap contentAcceptMap =
rtpContentMap.toContentModification( rtpContentMap.toContentModification(
Collections2.transform(contentAddition, ca -> ca.name)); Collections2.transform(contentAddition, ca -> ca.name));
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
id.getAccount().getJid().asBareJid() id.getAccount().getJid().asBareJid()
+ ": sending content-accept " + ": sending content-accept "
+ ContentAddition.summary(contentAcceptMap)); + ContentAddition.summary(contentAcceptMap));
modifyLocalContentMap(rtpContentMap); modifyLocalContentMap(rtpContentMap);
sendContentAccept(contentAcceptMap); final ListenableFuture<RtpContentMap> future = prepareOutgoingContentMap(contentAcceptMap);
this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true); Futures.addCallback(
future,
new FutureCallback<RtpContentMap>() {
@Override
public void onSuccess(final RtpContentMap rtpContentMap) {
sendContentAccept(rtpContentMap);
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
}
@Override
public void onFailure(@NonNull final Throwable throwable) {
failureToPerformAction(JinglePacket.Action.CONTENT_ACCEPT, throwable);
}
},
MoreExecutors.directExecutor());
} catch (final Exception e) { } catch (final Exception e) {
Log.d(Config.LOGTAG, "unable to accept content add", Throwables.getRootCause(e)); Log.d(Config.LOGTAG, "unable to accept content add", Throwables.getRootCause(e));
webRTCWrapper.close(); webRTCWrapper.close();
@ -979,12 +1032,20 @@ public class JingleRtpConnection extends AbstractJingleConnection
private ListenableFuture<RtpContentMap> receiveRtpContentMap( private ListenableFuture<RtpContentMap> receiveRtpContentMap(
final JinglePacket jinglePacket, final boolean expectVerification) { final JinglePacket jinglePacket, final boolean expectVerification) {
final RtpContentMap receivedContentMap;
try { try {
receivedContentMap = RtpContentMap.of(jinglePacket); return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification);
} catch (final Exception e) { } catch (final Exception e) {
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);
} }
}
private ListenableFuture<RtpContentMap> receiveRtpContentMap(final RtpContentMap receivedContentMap, final boolean expectVerification) {
Log.d(
Config.LOGTAG,
"receiveRtpContentMap("
+ receivedContentMap.getClass().getSimpleName()
+ ",expectVerification="
+ expectVerification
+ ")");
if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) { if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) {
final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future = final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future =
id.account id.account
@ -1287,6 +1348,16 @@ public class JingleRtpConnection extends AbstractJingleConnection
sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage()); sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage());
} }
private void failureToPerformAction(final JinglePacket.Action action, final Throwable throwable) {
if (isTerminated()) {
return;
}
final Throwable rootCause = Throwables.getRootCause(throwable);
Log.d(Config.LOGTAG, "unable to send " + action, rootCause);
webRTCWrapper.close();
sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage());
}
private void addIceCandidatesFromBlackLog() { private void addIceCandidatesFromBlackLog() {
Map.Entry<String, RtpContentMap.DescriptionTransport> foo; Map.Entry<String, RtpContentMap.DescriptionTransport> foo;
while ((foo = this.pendingIceCandidates.poll()) != null) { while ((foo = this.pendingIceCandidates.poll()) != null) {
@ -2486,6 +2557,27 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void sendContentAdd(final RtpContentMap rtpContentMap, final Collection<String> added) { private void sendContentAdd(final RtpContentMap rtpContentMap, final Collection<String> added) {
final RtpContentMap contentAdd = rtpContentMap.toContentModification(added); final RtpContentMap contentAdd = rtpContentMap.toContentModification(added);
this.outgoingContentAdd = contentAdd; this.outgoingContentAdd = contentAdd;
final ListenableFuture<RtpContentMap> outgoingContentMapFuture =
prepareOutgoingContentMap(contentAdd);
Futures.addCallback(
outgoingContentMapFuture,
new FutureCallback<RtpContentMap>() {
@Override
public void onSuccess(final RtpContentMap outgoingContentMap) {
sendContentAdd(outgoingContentMap);
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
}
@Override
public void onFailure(@NonNull Throwable throwable) {
failureToPerformAction(JinglePacket.Action.CONTENT_ADD, throwable);
}
},
MoreExecutors.directExecutor());
}
private void sendContentAdd(final RtpContentMap contentAdd) {
final JinglePacket jinglePacket = final JinglePacket jinglePacket =
contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId); contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId);
jinglePacket.setTo(id.with); jinglePacket.setTo(id.with);
@ -2512,7 +2604,6 @@ public class JingleRtpConnection extends AbstractJingleConnection
handleIqTimeoutResponse(response); handleIqTimeoutResponse(response);
} }
}); });
this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
} }
private void setLocalContentMap(final RtpContentMap rtpContentMap) { private void setLocalContentMap(final RtpContentMap rtpContentMap) {

View file

@ -23,7 +23,6 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportIn
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -275,6 +274,11 @@ public class RtpContentMap {
return count == 0; return count == 0;
} }
public boolean hasFullTransportInfo() {
return Collections2.transform(this.contents.values(), dt -> dt.transport.isStub())
.contains(false);
}
public RtpContentMap modifiedCredentials( public RtpContentMap modifiedCredentials(
IceUdpTransportInfo.Credentials credentials, final IceUdpTransportInfo.Setup setup) { IceUdpTransportInfo.Credentials credentials, final IceUdpTransportInfo.Setup setup) {
final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder = final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder =
@ -354,12 +358,7 @@ public class RtpContentMap {
public RtpContentMap toContentModification(final Collection<String> modifications) { public RtpContentMap toContentModification(final Collection<String> modifications) {
return new RtpContentMap( return new RtpContentMap(
this.group, this.group, Maps.filterKeys(contents, Predicates.in(modifications)));
Maps.transformValues(
Maps.filterKeys(contents, Predicates.in(modifications)),
dt ->
new DescriptionTransport(
dt.senders, dt.description, IceUdpTransportInfo.STUB)));
} }
public RtpContentMap toStub() { public RtpContentMap toStub() {
@ -396,37 +395,43 @@ public class RtpContentMap {
} }
public RtpContentMap addContent( public RtpContentMap addContent(
final RtpContentMap modification, final IceUdpTransportInfo.Setup setup) { final RtpContentMap modification, final IceUdpTransportInfo.Setup setupOverwrite) {
final IceUdpTransportInfo.Credentials credentials = getDistinctCredentials();
final Collection<String> iceOptions = getCombinedIceOptions();
final DTLS dtls = getDistinctDtls();
final Map<String, DescriptionTransport> combined = merge(contents, modification.contents); final Map<String, DescriptionTransport> combined = merge(contents, modification.contents);
final Map<String, DescriptionTransport> combinedFixedTransport = final Map<String, DescriptionTransport> combinedFixedTransport =
Maps.transformValues( Maps.transformValues(
combined, combined,
dt -> { dt -> {
final IceUdpTransportInfo iceUdpTransportInfo; final IceUdpTransportInfo iceUdpTransportInfo;
if (dt.transport.emptyCredentials()) { if (dt.transport.isStub()) {
final IceUdpTransportInfo.Credentials credentials =
getDistinctCredentials();
final Collection<String> iceOptions = getCombinedIceOptions();
final DTLS dtls = getDistinctDtls();
iceUdpTransportInfo = iceUdpTransportInfo =
IceUdpTransportInfo.of( IceUdpTransportInfo.of(
credentials, credentials,
iceOptions, iceOptions,
setup, setupOverwrite,
dtls.hash, dtls.hash,
dtls.fingerprint); dtls.fingerprint);
} else { } else {
final IceUdpTransportInfo.Fingerprint fp =
dt.transport.getFingerprint();
final IceUdpTransportInfo.Setup setup = fp.getSetup();
iceUdpTransportInfo = iceUdpTransportInfo =
IceUdpTransportInfo.of( IceUdpTransportInfo.of(
dt.transport.getCredentials(), dt.transport.getCredentials(),
iceOptions, dt.transport.getIceOptions(),
setup, setup == IceUdpTransportInfo.Setup.ACTPASS
dtls.hash, ? setupOverwrite
dtls.fingerprint); : setup,
fp.getHash(),
fp.getContent());
} }
return new DescriptionTransport( return new DescriptionTransport(
dt.senders, dt.description, iceUdpTransportInfo); dt.senders, dt.description, iceUdpTransportInfo);
}); });
return new RtpContentMap(modification.group, combinedFixedTransport); return new RtpContentMap(modification.group, ImmutableMap.copyOf(combinedFixedTransport));
} }
private static Map<String, DescriptionTransport> merge( private static Map<String, DescriptionTransport> merge(

View file

@ -110,8 +110,10 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
return new Credentials(ufrag, password); return new Credentials(ufrag, password);
} }
public boolean emptyCredentials() { public boolean isStub() {
return Strings.isNullOrEmpty(this.getAttribute("ufrag")) || Strings.isNullOrEmpty(this.getAttribute("pwd")); return Strings.isNullOrEmpty(this.getAttribute("ufrag"))
&& Strings.isNullOrEmpty(this.getAttribute("pwd"))
&& this.children.isEmpty();
} }
public List<Candidate> getCandidates() { public List<Candidate> getCandidates() {