Support for push preauth

This commit is contained in:
Moxie Marlinspike
2019-06-06 17:31:07 -07:00
parent 18037bb484
commit 4fdbe9b9ff
22 changed files with 391 additions and 103 deletions

View File

@@ -94,6 +94,8 @@ public class APNSender implements Managed {
Futures.addCallback(future, new FutureCallback<ApnResult>() {
@Override
public void onSuccess(@Nullable ApnResult result) {
if (message.getChallengeData().isPresent()) return;
if (result == null) {
logger.warn("*** RECEIVED NULL APN RESULT ***");
} else if (result.getStatus() == ApnResult.Status.NO_SUCH_USER) {

View File

@@ -157,7 +157,7 @@ public class ApnFallbackManager implements Managed, Runnable {
continue;
}
apnSender.sendMessage(new ApnMessage(apnId, separated.get().first(), separated.get().second(), true));
apnSender.sendMessage(new ApnMessage(apnId, separated.get().first(), separated.get().second(), true, Optional.empty()));
retry.mark();
}

View File

@@ -1,20 +1,28 @@
package org.whispersystems.textsecuregcm.push;
import com.google.common.annotations.VisibleForTesting;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class ApnMessage {
public static final String APN_PAYLOAD = "{\"aps\":{\"sound\":\"default\",\"alert\":{\"loc-key\":\"APN_Message\"}}}";
public static final long MAX_EXPIRATION = Integer.MAX_VALUE * 1000L;
public static final String APN_NOTIFICATION_PAYLOAD = "{\"aps\":{\"sound\":\"default\",\"alert\":{\"loc-key\":\"APN_Message\"}}}";
public static final String APN_CHALLENGE_PAYLOAD = "{\"aps\":{\"sound\":\"default\",\"alert\":{\"loc-key\":\"APN_Message\"}}, \"challenge\" : \"%s\"}";
public static final long MAX_EXPIRATION = Integer.MAX_VALUE * 1000L;
private final String apnId;
private final String number;
private final long deviceId;
private final boolean isVoip;
private final String apnId;
private final String number;
private final long deviceId;
private final boolean isVoip;
private final Optional<String> challengeData;
public ApnMessage(String apnId, String number, long deviceId, boolean isVoip) {
this.apnId = apnId;
this.number = number;
this.deviceId = deviceId;
this.isVoip = isVoip;
public ApnMessage(String apnId, String number, long deviceId, boolean isVoip, Optional<String> challengeData) {
this.apnId = apnId;
this.number = number;
this.deviceId = deviceId;
this.isVoip = isVoip;
this.challengeData = challengeData;
}
public boolean isVoip() {
@@ -26,7 +34,13 @@ public class ApnMessage {
}
public String getMessage() {
return APN_PAYLOAD;
if (!challengeData.isPresent()) return APN_NOTIFICATION_PAYLOAD;
else return String.format(APN_CHALLENGE_PAYLOAD, challengeData.get());
}
@VisibleForTesting
public Optional<String> getChallengeData() {
return challengeData;
}
public long getExpirationTime() {

View File

@@ -66,14 +66,22 @@ public class GCMSender implements Managed {
.withDestination(message.getGcmId())
.withPriority("high");
String key = message.isReceipt() ? "receipt" : "notification";
Message request = builder.withDataPart(key, "").build();
String key;
switch (message.getType()) {
case RECEIPT: key = "receipt"; break;
case NOTIFICATION: key = "notification"; break;
case CHALLENGE: key = "challenge"; break;
default: throw new AssertionError();
}
Message request = builder.withDataPart(key, message.getData().orElse("")).build();
CompletableFuture<Result> future = signalSender.send(request);
markOutboundMeter(key);
future.handle((result, throwable) -> {
if (result != null) {
if (result != null && message.getType() != GcmMessage.Type.CHALLENGE) {
if (result.isUnregistered() || result.isInvalidRegistrationId()) {
executor.submit(() -> handleBadRegistration(message));
} else if (result.hasCanonicalRegistrationId()) {

View File

@@ -1,17 +1,27 @@
package org.whispersystems.textsecuregcm.push;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class GcmMessage {
private final String gcmId;
private final String number;
private final int deviceId;
private final boolean receipt;
public enum Type {
RECEIPT, NOTIFICATION, CHALLENGE
}
public GcmMessage(String gcmId, String number, int deviceId, boolean receipt) {
this.gcmId = gcmId;
this.number = number;
this.deviceId = deviceId;
this.receipt = receipt;
private final String gcmId;
private final String number;
private final int deviceId;
private final Type type;
private final Optional<String> data;
public GcmMessage(String gcmId, String number, int deviceId, Type type, Optional<String> data) {
this.gcmId = gcmId;
this.number = number;
this.deviceId = deviceId;
this.type = type;
this.data = data;
}
public String getGcmId() {
@@ -22,11 +32,16 @@ public class GcmMessage {
return number;
}
public boolean isReceipt() {
return receipt;
public Type getType() {
return type;
}
public int getDeviceId() {
return deviceId;
}
public Optional<String> getData() {
return data;
}
}

View File

@@ -28,6 +28,7 @@ import org.whispersystems.textsecuregcm.util.BlockingThreadPoolExecutor;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.Util;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static com.codahale.metrics.MetricRegistry.name;
@@ -105,7 +106,7 @@ public class PushSender implements Managed {
private void sendGcmNotification(Account account, Device device) {
GcmMessage gcmMessage = new GcmMessage(device.getGcmId(), account.getNumber(),
(int)device.getId(), false);
(int)device.getId(), GcmMessage.Type.NOTIFICATION, Optional.empty());
gcmSender.sendMessage(gcmMessage);
}
@@ -126,10 +127,10 @@ public class PushSender implements Managed {
}
if (!Util.isEmpty(device.getVoipApnId())) {
apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), device.getId(), true);
apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), device.getId(), true, Optional.empty());
RedisOperation.unchecked(() -> apnFallbackManager.schedule(account, device));
} else {
apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), device.getId(), false);
apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), device.getId(), false, Optional.empty());
}
apnSender.sendMessage(apnMessage);