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:
parent
48bd845323
commit
d3d582759f
|
@ -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) {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue