mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 12:58:07 +01:00
Don't immediately require PNI-associated keys for "atomic" device linking
This commit is contained in:
committed by
Jon Chambers
parent
d51c6fd2f8
commit
4ec97cf006
@@ -21,6 +21,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -354,23 +355,24 @@ public class DeviceController {
|
||||
final Optional<DeviceActivationRequest> maybeDeviceActivationRequest)
|
||||
throws RateLimitExceededException, DeviceLimitExceededException {
|
||||
|
||||
final Optional<UUID> maybeAciFromToken = checkVerificationToken(verificationCode);
|
||||
|
||||
final Account account = maybeAciFromToken.flatMap(accounts::getByAccountIdentifier)
|
||||
final Account account = checkVerificationToken(verificationCode)
|
||||
.flatMap(accounts::getByAccountIdentifier)
|
||||
.orElseThrow(ForbiddenException::new);
|
||||
|
||||
rateLimiters.getVerifyDeviceLimiter().validate(account.getUuid());
|
||||
|
||||
maybeDeviceActivationRequest.ifPresent(deviceActivationRequest -> {
|
||||
assert deviceActivationRequest.aciSignedPreKey().isPresent();
|
||||
assert deviceActivationRequest.pniSignedPreKey().isPresent();
|
||||
assert deviceActivationRequest.aciPqLastResortPreKey().isPresent();
|
||||
assert deviceActivationRequest.pniPqLastResortPreKey().isPresent();
|
||||
|
||||
final boolean allKeysValid = PreKeySignatureValidator.validatePreKeySignatures(account.getIdentityKey(),
|
||||
List.of(deviceActivationRequest.aciSignedPreKey().get(), deviceActivationRequest.aciPqLastResortPreKey().get()))
|
||||
&& PreKeySignatureValidator.validatePreKeySignatures(account.getPhoneNumberIdentityKey(),
|
||||
List.of(deviceActivationRequest.pniSignedPreKey().get(), deviceActivationRequest.pniPqLastResortPreKey().get()));
|
||||
List.of(deviceActivationRequest.aciSignedPreKey().get(), deviceActivationRequest.aciPqLastResortPreKey().get())) &&
|
||||
deviceActivationRequest.pniSignedPreKey().map(pniSignedPreKey ->
|
||||
PreKeySignatureValidator.validatePreKeySignatures(account.getPhoneNumberIdentityKey(), List.of(pniSignedPreKey)))
|
||||
.orElse(true) &&
|
||||
deviceActivationRequest.pniPqLastResortPreKey().map(pniPqLastResortPreKey ->
|
||||
PreKeySignatureValidator.validatePreKeySignatures(account.getPhoneNumberIdentityKey(), List.of(pniPqLastResortPreKey)))
|
||||
.orElse(true);
|
||||
|
||||
if (!allKeysValid) {
|
||||
throw new WebApplicationException(Response.status(422).build());
|
||||
@@ -409,7 +411,8 @@ public class DeviceController {
|
||||
|
||||
maybeDeviceActivationRequest.ifPresent(deviceActivationRequest -> {
|
||||
device.setSignedPreKey(deviceActivationRequest.aciSignedPreKey().get());
|
||||
device.setPhoneNumberIdentitySignedPreKey(deviceActivationRequest.pniSignedPreKey().get());
|
||||
|
||||
deviceActivationRequest.pniSignedPreKey().ifPresent(device::setPhoneNumberIdentitySignedPreKey);
|
||||
|
||||
deviceActivationRequest.apnToken().ifPresent(apnRegistrationId -> {
|
||||
device.setApnId(apnRegistrationId.apnRegistrationId());
|
||||
@@ -431,24 +434,31 @@ public class DeviceController {
|
||||
|
||||
deleteKeysFuture.join();
|
||||
|
||||
maybeDeviceActivationRequest.ifPresent(deviceActivationRequest -> CompletableFuture.allOf(
|
||||
keys.storeEcSignedPreKeys(a.getUuid(),
|
||||
Map.of(device.getId(), deviceActivationRequest.aciSignedPreKey().get())),
|
||||
keys.storePqLastResort(a.getUuid(),
|
||||
Map.of(device.getId(), deviceActivationRequest.aciPqLastResortPreKey().get())),
|
||||
keys.storeEcSignedPreKeys(a.getPhoneNumberIdentifier(),
|
||||
Map.of(device.getId(), deviceActivationRequest.pniSignedPreKey().get())),
|
||||
keys.storePqLastResort(a.getPhoneNumberIdentifier(),
|
||||
Map.of(device.getId(), deviceActivationRequest.pniPqLastResortPreKey().get())))
|
||||
.join());
|
||||
maybeDeviceActivationRequest.ifPresent(deviceActivationRequest -> {
|
||||
final List<CompletableFuture<Void>> storeKeyFutures = new ArrayList<>(4);
|
||||
|
||||
storeKeyFutures.add(keys.storeEcSignedPreKeys(a.getUuid(),
|
||||
Map.of(device.getId(), deviceActivationRequest.aciSignedPreKey().get())));
|
||||
|
||||
storeKeyFutures.add(keys.storePqLastResort(a.getUuid(),
|
||||
Map.of(device.getId(), deviceActivationRequest.aciPqLastResortPreKey().get())));
|
||||
|
||||
deviceActivationRequest.pniSignedPreKey().ifPresent(pniSignedPreKey ->
|
||||
storeKeyFutures.add(keys.storeEcSignedPreKeys(a.getPhoneNumberIdentifier(),
|
||||
Map.of(device.getId(), pniSignedPreKey))));
|
||||
|
||||
deviceActivationRequest.pniPqLastResortPreKey().ifPresent(pniPqLastResortPreKey ->
|
||||
storeKeyFutures.add(keys.storePqLastResort(a.getPhoneNumberIdentifier(),
|
||||
Map.of(device.getId(), pniPqLastResortPreKey))));
|
||||
|
||||
CompletableFuture.allOf(storeKeyFutures.toArray(new CompletableFuture[0])).join();
|
||||
});
|
||||
|
||||
a.addDevice(device);
|
||||
});
|
||||
|
||||
if (maybeAciFromToken.isPresent()) {
|
||||
usedTokenCluster.useCluster(connection ->
|
||||
connection.sync().set(getUsedTokenKey(verificationCode), "", new SetArgs().ex(TOKEN_EXPIRATION_DURATION)));
|
||||
}
|
||||
usedTokenCluster.useCluster(connection ->
|
||||
connection.sync().set(getUsedTokenKey(verificationCode), "", new SetArgs().ex(TOKEN_EXPIRATION_DURATION)));
|
||||
|
||||
return new Pair<>(updatedAccount, device);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.util.Optional;
|
||||
|
||||
public record LinkDeviceRequest(@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = """
|
||||
@@ -23,8 +24,8 @@ public record LinkDeviceRequest(@Schema(requiredMode = Schema.RequiredMode.REQUI
|
||||
|
||||
@JsonCreator
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public LinkDeviceRequest(@JsonProperty("verificationCode") String verificationCode,
|
||||
@JsonProperty("accountAttributes") AccountAttributes accountAttributes,
|
||||
public LinkDeviceRequest(@JsonProperty("verificationCode") @NotBlank String verificationCode,
|
||||
@JsonProperty("accountAttributes") @Valid AccountAttributes accountAttributes,
|
||||
@JsonProperty("aciSignedPreKey") Optional<@Valid ECSignedPreKey> aciSignedPreKey,
|
||||
@JsonProperty("pniSignedPreKey") Optional<@Valid ECSignedPreKey> pniSignedPreKey,
|
||||
@JsonProperty("aciPqLastResortPreKey") Optional<@Valid KEMSignedPreKey> aciPqLastResortPreKey,
|
||||
@@ -38,10 +39,14 @@ public record LinkDeviceRequest(@Schema(requiredMode = Schema.RequiredMode.REQUI
|
||||
|
||||
@AssertTrue
|
||||
public boolean hasAllRequiredFields() {
|
||||
// PNI-associated credentials are not yet required, but will be when all devices are assumed to have a PNI identity
|
||||
// key.
|
||||
final boolean mismatchedPniKeys = deviceActivationRequest().pniSignedPreKey().isPresent()
|
||||
^ deviceActivationRequest().pniPqLastResortPreKey().isPresent();
|
||||
|
||||
return deviceActivationRequest().aciSignedPreKey().isPresent()
|
||||
&& deviceActivationRequest().pniSignedPreKey().isPresent()
|
||||
&& deviceActivationRequest().aciPqLastResortPreKey().isPresent()
|
||||
&& deviceActivationRequest().pniPqLastResortPreKey().isPresent();
|
||||
&& !mismatchedPniKeys;
|
||||
}
|
||||
|
||||
@AssertTrue
|
||||
|
||||
Reference in New Issue
Block a user