mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 03:48:05 +01:00
Read registration recovery passwords exclusively by PNI
This commit is contained in:
committed by
Jon Chambers
parent
6967e4e54b
commit
5b9f8177f2
@@ -1076,7 +1076,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
|
||||
|
||||
final PhoneVerificationTokenManager phoneVerificationTokenManager = new PhoneVerificationTokenManager(
|
||||
registrationServiceClient, registrationRecoveryPasswordsManager, registrationRecoveryChecker);
|
||||
phoneNumberIdentifiers, registrationServiceClient, registrationRecoveryPasswordsManager, registrationRecoveryChecker);
|
||||
final List<Object> commonControllers = Lists.newArrayList(
|
||||
new AccountController(accountsManager, rateLimiters, turnTokenGenerator, registrationRecoveryPasswordsManager,
|
||||
usernameHashZkProofVerifier),
|
||||
@@ -1119,8 +1119,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
config.getCdnConfiguration().credentials().secretAccessKey().value(), config.getCdnConfiguration().region(),
|
||||
config.getCdnConfiguration().bucket()),
|
||||
new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions),
|
||||
pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters,
|
||||
accountsManager, registrationFraudChecker, dynamicConfigurationManager, clock)
|
||||
pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager,
|
||||
phoneNumberIdentifiers, rateLimiters, accountsManager, registrationFraudChecker,
|
||||
dynamicConfigurationManager, clock)
|
||||
);
|
||||
if (config.getSubscription() != null && config.getOneTimeDonations() != null) {
|
||||
SubscriptionManager subscriptionManager = new SubscriptionManager(subscriptions,
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
|
||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
||||
import org.whispersystems.textsecuregcm.spam.RegistrationRecoveryChecker;
|
||||
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
|
||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
||||
|
||||
public class PhoneVerificationTokenManager {
|
||||
@@ -33,13 +34,17 @@ public class PhoneVerificationTokenManager {
|
||||
private static final Duration REGISTRATION_RPC_TIMEOUT = Duration.ofSeconds(15);
|
||||
private static final long VERIFICATION_TIMEOUT_SECONDS = REGISTRATION_RPC_TIMEOUT.plusSeconds(1).getSeconds();
|
||||
|
||||
private final PhoneNumberIdentifiers phoneNumberIdentifiers;
|
||||
|
||||
private final RegistrationServiceClient registrationServiceClient;
|
||||
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager;
|
||||
private final RegistrationRecoveryChecker registrationRecoveryChecker;
|
||||
|
||||
public PhoneVerificationTokenManager(final RegistrationServiceClient registrationServiceClient,
|
||||
public PhoneVerificationTokenManager(final PhoneNumberIdentifiers phoneNumberIdentifiers,
|
||||
final RegistrationServiceClient registrationServiceClient,
|
||||
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager,
|
||||
final RegistrationRecoveryChecker registrationRecoveryChecker) {
|
||||
this.phoneNumberIdentifiers = phoneNumberIdentifiers;
|
||||
this.registrationServiceClient = registrationServiceClient;
|
||||
this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager;
|
||||
this.registrationRecoveryChecker = registrationRecoveryChecker;
|
||||
@@ -109,7 +114,7 @@ public class PhoneVerificationTokenManager {
|
||||
throw new ForbiddenException("recoveryPassword couldn't be verified");
|
||||
}
|
||||
try {
|
||||
final boolean verified = registrationRecoveryPasswordsManager.verify(number, recoveryPassword)
|
||||
final boolean verified = registrationRecoveryPasswordsManager.verify(phoneNumberIdentifiers.getPhoneNumberIdentifier(number).join(), recoveryPassword)
|
||||
.get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
if (!verified) {
|
||||
throw new ForbiddenException("recoveryPassword couldn't be verified");
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
|
||||
import org.whispersystems.textsecuregcm.entities.Svr3Credentials;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
|
||||
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
|
||||
|
||||
@@ -83,6 +83,7 @@ import org.whispersystems.textsecuregcm.spam.RegistrationFraudChecker;
|
||||
import org.whispersystems.textsecuregcm.spam.RegistrationFraudChecker.VerificationCheck;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
|
||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
|
||||
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
|
||||
@@ -116,6 +117,7 @@ public class VerificationController {
|
||||
private final PushNotificationManager pushNotificationManager;
|
||||
private final RegistrationCaptchaManager registrationCaptchaManager;
|
||||
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager;
|
||||
private final PhoneNumberIdentifiers phoneNumberIdentifiers;
|
||||
private final RateLimiters rateLimiters;
|
||||
private final AccountsManager accountsManager;
|
||||
private final RegistrationFraudChecker registrationFraudChecker;
|
||||
@@ -127,6 +129,7 @@ public class VerificationController {
|
||||
final PushNotificationManager pushNotificationManager,
|
||||
final RegistrationCaptchaManager registrationCaptchaManager,
|
||||
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager,
|
||||
final PhoneNumberIdentifiers phoneNumberIdentifiers,
|
||||
final RateLimiters rateLimiters,
|
||||
final AccountsManager accountsManager,
|
||||
final RegistrationFraudChecker registrationFraudChecker,
|
||||
@@ -137,6 +140,7 @@ public class VerificationController {
|
||||
this.pushNotificationManager = pushNotificationManager;
|
||||
this.registrationCaptchaManager = registrationCaptchaManager;
|
||||
this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager;
|
||||
this.phoneNumberIdentifiers = phoneNumberIdentifiers;
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.accountsManager = accountsManager;
|
||||
this.registrationFraudChecker = registrationFraudChecker;
|
||||
@@ -626,8 +630,8 @@ public class VerificationController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ClientErrorException with {@code 422} status if the ID cannot be decoded
|
||||
* @throws javax.ws.rs.NotFoundException if the ID cannot be found
|
||||
* @throws ClientErrorException with {@code 422} status if the ID cannot be decoded
|
||||
* @throws NotFoundException if the ID cannot be found
|
||||
*/
|
||||
private RegistrationServiceSession retrieveRegistrationServiceSession(final String encodedSessionId) {
|
||||
final byte[] sessionId;
|
||||
|
||||
@@ -9,8 +9,11 @@ import java.util.Base64;
|
||||
import javax.annotation.Nullable;
|
||||
import org.signal.registration.rpc.RegistrationSessionMetadata;
|
||||
|
||||
public record RegistrationServiceSession(byte[] id, String number, boolean verified,
|
||||
@Nullable Long nextSms, @Nullable Long nextVoiceCall,
|
||||
public record RegistrationServiceSession(byte[] id,
|
||||
String number,
|
||||
boolean verified,
|
||||
@Nullable Long nextSms,
|
||||
@Nullable Long nextVoiceCall,
|
||||
@Nullable Long nextVerificationAttempt,
|
||||
long expiration) {
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.entities.EncryptedUsername;
|
||||
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
|
||||
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||
@@ -28,9 +27,8 @@ import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest;
|
||||
|
||||
public class RegistrationRecoveryPasswords {
|
||||
|
||||
// As a temporary transitional measure, this can be either a string representation of an E164-formatted phone number
|
||||
// or a UUID (PNI) string
|
||||
static final String KEY_E164 = "P";
|
||||
// For historical reasons, we record the PNI as a UUID string rather than a compact byte array
|
||||
static final String KEY_PNI = "P";
|
||||
static final String ATTR_EXP = "E";
|
||||
static final String ATTR_SALT = "S";
|
||||
static final String ATTR_HASH = "H";
|
||||
@@ -54,10 +52,10 @@ public class RegistrationRecoveryPasswords {
|
||||
this.clock = requireNonNull(clock);
|
||||
}
|
||||
|
||||
public CompletableFuture<Optional<SaltedTokenHash>> lookup(final String number) {
|
||||
public CompletableFuture<Optional<SaltedTokenHash>> lookup(final UUID phoneNumberIdentifier) {
|
||||
return asyncClient.getItem(GetItemRequest.builder()
|
||||
.tableName(tableName)
|
||||
.key(Map.of(KEY_E164, AttributeValues.fromString(number)))
|
||||
.key(Map.of(KEY_PNI, AttributeValues.fromString(phoneNumberIdentifier.toString())))
|
||||
.consistentRead(true)
|
||||
.build())
|
||||
.thenApply(getItemResponse -> Optional.ofNullable(getItemResponse.item())
|
||||
@@ -66,10 +64,6 @@ public class RegistrationRecoveryPasswords {
|
||||
.map(RegistrationRecoveryPasswords::saltedTokenHashFromItem));
|
||||
}
|
||||
|
||||
public CompletableFuture<Optional<SaltedTokenHash>> lookup(final UUID phoneNumberIdentifier) {
|
||||
return lookup(phoneNumberIdentifier.toString());
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> addOrReplace(final String number, final UUID phoneNumberIdentifier, final SaltedTokenHash data) {
|
||||
final long expirationSeconds = expirationSeconds();
|
||||
|
||||
@@ -90,7 +84,7 @@ public class RegistrationRecoveryPasswords {
|
||||
.put(Put.builder()
|
||||
.tableName(tableName)
|
||||
.item(Map.of(
|
||||
KEY_E164, AttributeValues.fromString(key),
|
||||
KEY_PNI, AttributeValues.fromString(key),
|
||||
ATTR_EXP, AttributeValues.fromLong(expirationSeconds),
|
||||
ATTR_SALT, AttributeValues.fromString(salt),
|
||||
ATTR_HASH, AttributeValues.fromString(hash)))
|
||||
@@ -111,7 +105,7 @@ public class RegistrationRecoveryPasswords {
|
||||
return TransactWriteItem.builder()
|
||||
.delete(Delete.builder()
|
||||
.tableName(tableName)
|
||||
.key(Map.of(KEY_E164, AttributeValues.fromString(key)))
|
||||
.key(Map.of(KEY_PNI, AttributeValues.fromString(key)))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import static java.util.Objects.requireNonNull;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -30,8 +31,8 @@ public class RegistrationRecoveryPasswordsManager {
|
||||
this.phoneNumberIdentifiers = phoneNumberIdentifiers;
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> verify(final String number, final byte[] password) {
|
||||
return registrationRecoveryPasswords.lookup(number)
|
||||
public CompletableFuture<Boolean> verify(final UUID phoneNumberIdentifier, final byte[] password) {
|
||||
return registrationRecoveryPasswords.lookup(phoneNumberIdentifier)
|
||||
.thenApply(maybeHash -> maybeHash.filter(hash -> hash.verify(bytesToString(password))))
|
||||
.whenComplete((result, error) -> {
|
||||
if (error != null) {
|
||||
|
||||
Reference in New Issue
Block a user