Avoid reading modified account when generating backup credentials

This commit is contained in:
Ravi Khadiwala
2026-02-12 14:23:27 -06:00
committed by ravi-signal
parent 368e705b68
commit 5850eeb87b
7 changed files with 70 additions and 78 deletions

View File

@@ -14,7 +14,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -184,13 +186,11 @@ public class BackupAuthManager {
* method will also remove the expired voucher from the account.
*
* @param account The account to create the credentials for
* @param credentialType The type of backup credentials to create
* @param redemptionRange The time range to return credentials for
* @return Credentials and the day on which they may be redeemed
*/
public List<Credential> getBackupAuthCredentials(
public Map<BackupCredentialType, List<Credential>> getBackupAuthCredentials(
final Account account,
final BackupCredentialType credentialType,
final RedemptionRange redemptionRange) throws BackupNotFoundException {
// If the account has an expired payment, clear it before continuing
@@ -201,31 +201,35 @@ public class BackupAuthManager {
a.setBackupVoucher(null);
}
});
return getBackupAuthCredentials(updated, credentialType, redemptionRange);
}
// fetch the blinded backup-id the account should have previously committed to
final byte[] committedBytes = account.getBackupCredentialRequest(credentialType)
.orElseThrow(() -> new BackupNotFoundException("No blinded backup-id has been added to the account"));
final Map<BackupCredentialType, List<Credential>> credentials = new HashMap<>();
for (BackupCredentialType credentialType : BackupCredentialType.values()) {
// fetch the blinded backup-id the account should have previously committed to
final byte[] committedBytes = account.getBackupCredentialRequest(credentialType)
.orElseThrow(() -> new BackupNotFoundException("No blinded backup-id has been added to the account"));
try {
final BackupLevel defaultBackupLevel = configuredBackupLevel(account);
try {
final BackupLevel defaultBackupLevel = configuredBackupLevel(account);
// create a credential for every day in the requested period
final BackupAuthCredentialRequest credentialReq = new BackupAuthCredentialRequest(committedBytes);
return StreamSupport.stream(redemptionRange.spliterator(), false)
.map(redemptionTime -> {
// Check if the account has a voucher that's good for a certain receiptLevel at redemption time, otherwise
// use the default receipt level
final BackupLevel backupLevel = storedBackupLevel(account, redemptionTime).orElse(defaultBackupLevel);
return new Credential(
credentialReq.issueCredential(redemptionTime, backupLevel, credentialType, serverSecretParams),
redemptionTime);
})
.toList();
} catch (InvalidInputException e) {
throw new UncheckedIOException(new IOException("Could not deserialize stored request credential", e));
// create a credential for every day in the requested period
final BackupAuthCredentialRequest credentialReq = new BackupAuthCredentialRequest(committedBytes);
final List<Credential> credentialList = StreamSupport.stream(redemptionRange.spliterator(), false)
.map(redemptionTime -> {
// Check if the account has a voucher that's good for a certain receiptLevel at redemption time, otherwise
// use the default receipt level
final BackupLevel backupLevel = storedBackupLevel(account, redemptionTime).orElse(defaultBackupLevel);
return new Credential(
credentialReq.issueCredential(redemptionTime, backupLevel, credentialType, serverSecretParams),
redemptionTime);
})
.toList();
credentials.put(credentialType, credentialList);
} catch (InvalidInputException e) {
throw new UncheckedIOException(new IOException("Could not deserialize stored request credential", e));
}
}
return credentials;
}
/**

View File

@@ -15,6 +15,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.HttpHeaders;
import io.dropwizard.auth.Auth;
import io.micrometer.core.instrument.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
@@ -48,7 +49,6 @@ import java.lang.annotation.Target;
import java.time.Clock;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -312,9 +312,6 @@ public class ArchiveController {
@NotNull @QueryParam("redemptionStartSeconds") Long startSeconds,
@NotNull @QueryParam("redemptionEndSeconds") Long endSeconds) throws BackupNotFoundException {
final Map<BackupCredentialType, List<BackupAuthCredentialsResponse.BackupAuthCredential>> credentialsByType =
new HashMap<>();
final RedemptionRange redemptionRange;
try {
redemptionRange = RedemptionRange.inclusive(Clock.systemUTC(), Instant.ofEpochSecond(startSeconds), Instant.ofEpochSecond(endSeconds));
@@ -325,23 +322,20 @@ public class ArchiveController {
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
for (BackupCredentialType credentialType : BackupCredentialType.values()) {
final List<BackupAuthManager.Credential> credentials =
backupAuthManager.getBackupAuthCredentials(account, credentialType, redemptionRange);
backupMetrics.updateGetCredentialCounter(
UserAgentTagUtil.getPlatformTag(userAgent),
credentialType,
credentials.size());
credentialsByType.put(credentialType, credentials.stream()
.map(credential -> new BackupAuthCredentialsResponse.BackupAuthCredential(
credential.credential().serialize(),
credential.redemptionTime().getEpochSecond()))
.toList());
}
return new BackupAuthCredentialsResponse(credentialsByType.entrySet().stream()
.collect(Collectors.toMap(
final Map<BackupCredentialType, List<BackupAuthManager.Credential>> credentials =
backupAuthManager.getBackupAuthCredentials(account, redemptionRange);
final Tag platformTag = UserAgentTagUtil.getPlatformTag(userAgent);
credentials.forEach((type, credentialList) ->
backupMetrics.updateGetCredentialCounter(platformTag, type, credentialList.size()));
return new BackupAuthCredentialsResponse(credentials.entrySet().stream().collect(Collectors.toMap(
e -> BackupAuthCredentialsResponse.CredentialType.fromLibsignalType(e.getKey()),
Map.Entry::getValue)));
e -> e.getValue().stream()
.map(credential -> new BackupAuthCredentialsResponse.BackupAuthCredential(
credential.credential().serialize(),
credential.redemptionTime().getEpochSecond()))
.toList())));
}

View File

@@ -11,6 +11,7 @@ import io.micrometer.core.instrument.Tag;
import java.time.Clock;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.signal.chat.backup.GetBackupAuthCredentialsRequest;
@@ -106,19 +107,14 @@ public class BackupsGrpcService extends SimpleBackupsGrpc.BackupsImplBase {
}
final Account account = authenticatedAccount();
try {
final List<BackupAuthManager.Credential> messageCredentials =
backupAuthManager.getBackupAuthCredentials(
account,
BackupCredentialType.MESSAGES,
redemptionRange);
backupMetrics.updateGetCredentialCounter(platformTag, BackupCredentialType.MESSAGES, messageCredentials.size());
final List<BackupAuthManager.Credential> mediaCredentials =
backupAuthManager.getBackupAuthCredentials(
account,
BackupCredentialType.MEDIA,
redemptionRange);
backupMetrics.updateGetCredentialCounter(platformTag, BackupCredentialType.MEDIA, mediaCredentials.size());
final Map<BackupCredentialType, List<BackupAuthManager.Credential>> credentials =
backupAuthManager.getBackupAuthCredentials(account, redemptionRange);
credentials.forEach((type, credentialList) ->
backupMetrics.updateGetCredentialCounter(platformTag, type, credentialList.size()));
final List<BackupAuthManager.Credential> messageCredentials = credentials.get(BackupCredentialType.MESSAGES);
final List<BackupAuthManager.Credential> mediaCredentials = credentials.get(BackupCredentialType.MEDIA);
return GetBackupAuthCredentialsResponse.newBuilder()
.setCredentials(GetBackupAuthCredentialsResponse.Credentials.newBuilder()