Generate request credential presentation before submitting subscription job.

This commit is contained in:
Alex Hart
2021-12-22 13:26:25 -04:00
committed by Greyson Parrelli
parent a5c2595796
commit a0d70a955a

View File

@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.subscription.Subscriber;
import org.thoughtcrime.securesms.subscription.DonorBadgeNotifications;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription;
import org.whispersystems.signalservice.api.subscriptions.SubscriberId;
import org.whispersystems.signalservice.internal.ServiceResponse;
@@ -45,10 +45,9 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
public static final Object MUTEX = new Object();
private ReceiptCredentialRequestContext requestContext;
private final SubscriberId subscriberId;
private final boolean isForKeepAlive;
private final ReceiptCredentialRequestContext requestContext;
private final SubscriberId subscriberId;
private final boolean isForKeepAlive;
private static SubscriptionReceiptRequestResponseJob createJob(SubscriberId subscriberId, boolean isForKeepAlive) {
return new SubscriptionReceiptRequestResponseJob(
@@ -60,12 +59,28 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
null,
generateRequestContext(),
subscriberId,
isForKeepAlive
);
}
private static ReceiptCredentialRequestContext generateRequestContext() {
Log.d(TAG, "Generating request credentials context for token redemption...", true);
SecureRandom secureRandom = new SecureRandom();
byte[] randomBytes = Util.getSecretBytes(ReceiptSerial.SIZE);
try {
ReceiptSerial receiptSerial = new ReceiptSerial(randomBytes);
ClientZkReceiptOperations operations = ApplicationDependencies.getClientZkReceiptOperations();
return operations.createReceiptCredentialRequestContext(secureRandom, receiptSerial);
} catch (InvalidInputException | VerificationFailedException e) {
Log.e(TAG, "Failed to create credential.", e);
throw new AssertionError(e);
}
}
public static JobManager.Chain createSubscriptionContinuationJobChain() {
return createSubscriptionContinuationJobChain(false);
}
@@ -83,7 +98,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
}
private SubscriptionReceiptRequestResponseJob(@NonNull Parameters parameters,
@Nullable ReceiptCredentialRequestContext requestContext,
@NonNull ReceiptCredentialRequestContext requestContext,
@NonNull SubscriberId subscriberId,
boolean isForKeepAlive)
{
@@ -96,11 +111,8 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
@Override
public @NonNull Data serialize() {
Data.Builder builder = new Data.Builder().putBlobAsString(DATA_SUBSCRIBER_ID, subscriberId.getBytes())
.putBoolean(DATA_IS_FOR_KEEP_ALIVE, isForKeepAlive);
if (requestContext != null) {
builder.putBlobAsString(DATA_REQUEST_BYTES, requestContext.serialize());
}
.putBoolean(DATA_IS_FOR_KEEP_ALIVE, isForKeepAlive)
.putBlobAsString(DATA_REQUEST_BYTES, requestContext.serialize());
return builder.build();
}
@@ -139,21 +151,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
MultiDeviceSubscriptionSyncRequestJob.enqueue();
}
if (requestContext == null) {
Log.d(TAG, "Generating request credentials context for token redemption...", true);
SecureRandom secureRandom = new SecureRandom();
byte[] randomBytes = new byte[ReceiptSerial.SIZE];
secureRandom.nextBytes(randomBytes);
ReceiptSerial receiptSerial = new ReceiptSerial(randomBytes);
ClientZkReceiptOperations operations = ApplicationDependencies.getClientZkReceiptOperations();
requestContext = operations.createReceiptCredentialRequestContext(secureRandom, receiptSerial);
} else {
Log.d(TAG, "Already have credentials generated for this request. Retrying web service call...", true);
}
Log.d(TAG, "Submitting receipt credential request.");
ServiceResponse<ReceiptCredentialResponse> response = ApplicationDependencies.getDonationsService()
.submitReceiptCredentialRequest(subscriberId, requestContext.getRequest())
@@ -201,7 +198,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
return operations.createReceiptCredentialPresentation(receiptCredential);
} catch (VerificationFailedException e) {
Log.w(TAG, "getReceiptCredentialPresentation: encountered a verification failure in zk", e, true);
requestContext = null;
throw new RetryableException();
}
}
@@ -213,7 +209,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
return operations.receiveReceiptCredential(requestContext, response);
} catch (VerificationFailedException e) {
Log.w(TAG, "getReceiptCredential: encountered a verification failure in zk", e, true);
requestContext = null;
throw new RetryableException();
}
}
@@ -269,7 +264,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
* - expiration_time mod 86400 == 0
* - expiration_time is between now and 60 days from now
*/
private boolean isCredentialValid(@NonNull ActiveSubscription.Subscription subscription, @NonNull ReceiptCredential receiptCredential) {
private static boolean isCredentialValid(@NonNull ActiveSubscription.Subscription subscription, @NonNull ReceiptCredential receiptCredential) {
long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
long maxExpirationTime = now + TimeUnit.DAYS.toSeconds(60);
boolean isSameLevel = subscription.getLevel() == receiptCredential.getReceiptLevel();
@@ -292,28 +287,30 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
return e instanceof RetryableException;
}
@VisibleForTesting
final static class RetryableException extends Exception {
@VisibleForTesting final static class RetryableException extends Exception {
}
public static class Factory implements Job.Factory<SubscriptionReceiptRequestResponseJob> {
@Override
public @NonNull SubscriptionReceiptRequestResponseJob create(@NonNull Parameters parameters, @NonNull Data data) {
SubscriberId subscriberId = SubscriberId.fromBytes(data.getStringAsBlob(DATA_SUBSCRIBER_ID));
boolean isForKeepAlive = data.getBooleanOrDefault(DATA_IS_FOR_KEEP_ALIVE, false);
SubscriberId subscriberId = SubscriberId.fromBytes(data.getStringAsBlob(DATA_SUBSCRIBER_ID));
boolean isForKeepAlive = data.getBooleanOrDefault(DATA_IS_FOR_KEEP_ALIVE, false);
byte[] requestContextBytes = data.getStringAsBlob(DATA_REQUEST_BYTES);
try {
if (data.hasString(DATA_REQUEST_BYTES)) {
byte[] blob = data.getStringAsBlob(DATA_REQUEST_BYTES);
ReceiptCredentialRequestContext requestContext = new ReceiptCredentialRequestContext(blob);
return new SubscriptionReceiptRequestResponseJob(parameters, requestContext, subscriberId, isForKeepAlive);
} else {
return new SubscriptionReceiptRequestResponseJob(parameters, null, subscriberId, isForKeepAlive);
ReceiptCredentialRequestContext requestContext;
if (requestContextBytes == null) {
Log.i(TAG, "Generating a request context for a legacy instance of SubscriptionReceiptRequestResponseJob", true);
requestContext = generateRequestContext();
} else {
try {
requestContext = new ReceiptCredentialRequestContext(requestContextBytes);
} catch (InvalidInputException e) {
Log.e(TAG, "Failed to generate request context from bytes", e);
throw new AssertionError(e);
}
} catch (InvalidInputException e) {
throw new IllegalStateException(e);
}
return new SubscriptionReceiptRequestResponseJob(parameters, requestContext, subscriberId, isForKeepAlive);
}
}
}