mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 04:58:06 +01:00
Represent device names as byte arrays
This commit is contained in:
committed by
Jon Chambers
parent
34a943832a
commit
5ae2e5281a
@@ -8,6 +8,7 @@ import static org.whispersystems.textsecuregcm.util.RegistrationIdValidator.vali
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -30,8 +31,10 @@ public class AccountAttributes {
|
||||
private int phoneNumberIdentityRegistrationId;
|
||||
|
||||
@JsonProperty
|
||||
@Size(max = 204, message = "This field must be less than 50 characters")
|
||||
private String name;
|
||||
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||
@Size(max = 225)
|
||||
private byte[] name;
|
||||
|
||||
@JsonProperty
|
||||
private String registrationLock;
|
||||
@@ -62,7 +65,7 @@ public class AccountAttributes {
|
||||
final boolean fetchesMessages,
|
||||
final int registrationId,
|
||||
final int phoneNumberIdentifierRegistrationId,
|
||||
final String name,
|
||||
final byte[] name,
|
||||
final String registrationLock,
|
||||
final boolean discoverableByPhoneNumber,
|
||||
final DeviceCapabilities capabilities) {
|
||||
@@ -87,7 +90,7 @@ public class AccountAttributes {
|
||||
return phoneNumberIdentityRegistrationId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
public byte[] getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,5 +5,16 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
public record DeviceInfo(long id, String name, long lastSeen, long created) {
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||
|
||||
public record DeviceInfo(long id,
|
||||
|
||||
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||
byte[] name,
|
||||
|
||||
long lastSeen,
|
||||
long created) {
|
||||
}
|
||||
|
||||
@@ -6,19 +6,24 @@
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
public class DeviceName {
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||
@NotEmpty
|
||||
@Size(max = 300, message = "This field must be less than 300 characters")
|
||||
private String deviceName;
|
||||
@Size(max = 225)
|
||||
private byte[] deviceName;
|
||||
|
||||
public DeviceName() {}
|
||||
|
||||
public String getDeviceName() {
|
||||
public byte[] getDeviceName() {
|
||||
return deviceName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ public class DevicesGrpcService extends ReactorDevicesGrpc.DevicesImplBase {
|
||||
.reduce(GetDevicesResponse.newBuilder(), (builder, device) -> {
|
||||
final GetDevicesResponse.LinkedDevice.Builder linkedDeviceBuilder = GetDevicesResponse.LinkedDevice.newBuilder();
|
||||
|
||||
if (StringUtils.isNotBlank(device.getName())) {
|
||||
linkedDeviceBuilder.setName(ByteString.copyFrom(Base64.getDecoder().decode(device.getName())));
|
||||
if (device.getName() != null) {
|
||||
linkedDeviceBuilder.setName(ByteString.copyFrom(device.getName()));
|
||||
}
|
||||
|
||||
return builder.addDevices(linkedDeviceBuilder
|
||||
@@ -105,7 +105,7 @@ public class DevicesGrpcService extends ReactorDevicesGrpc.DevicesImplBase {
|
||||
return Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier()))
|
||||
.map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
|
||||
.flatMap(account -> Mono.fromFuture(() -> accountsManager.updateDeviceAsync(account, authenticatedDevice.deviceId(),
|
||||
device -> device.setName(Base64.getEncoder().encodeToString(request.getName().toByteArray())))))
|
||||
device -> device.setName(request.getName().toByteArray()))))
|
||||
.thenReturn(SetDeviceNameResponse.newBuilder().build());
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import java.util.List;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -17,6 +18,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
|
||||
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.util.DeviceNameByteArrayAdapter;
|
||||
|
||||
public class Device {
|
||||
|
||||
@@ -31,7 +33,9 @@ public class Device {
|
||||
private byte id;
|
||||
|
||||
@JsonProperty
|
||||
private String name;
|
||||
@JsonSerialize(using = DeviceNameByteArrayAdapter.Serializer.class)
|
||||
@JsonDeserialize(using = DeviceNameByteArrayAdapter.Deserializer.class)
|
||||
private byte[] name;
|
||||
|
||||
@JsonProperty
|
||||
private String authToken;
|
||||
@@ -146,11 +150,11 @@ public class Device {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
public byte[] getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
public void setName(byte[] name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Serializes a byte array as a standard Base64-encoded string and deserializes Base64-encoded strings to byte arrays,
|
||||
* but treats any string that cannot be parsed as Base64 to {@code null}.
|
||||
* <p/>
|
||||
* Historically, device names were passed around as weakly-typed strings with the expectation that clients would provide
|
||||
* Base64 strings, but nothing in the server ever verified that was the case. In the absence of strict validation, some
|
||||
* third-party clients started submitting unencrypted names for devices, and so device names in persistent storage are a
|
||||
* mix of Base64-encoded device name ciphertexts from first-party clients and plaintext device names from third-party
|
||||
* clients. This adapter will discard the latter.
|
||||
*/
|
||||
public class DeviceNameByteArrayAdapter {
|
||||
|
||||
private static final Counter UNPARSEABLE_DEVICE_NAME_COUNTER =
|
||||
Metrics.counter(MetricsUtil.name(DeviceNameByteArrayAdapter.class, "unparseableDeviceName"));
|
||||
|
||||
public static class Serializer extends JsonSerializer<byte[]> {
|
||||
@Override
|
||||
public void serialize(final byte[] bytes,
|
||||
final JsonGenerator jsonGenerator,
|
||||
final SerializerProvider serializerProvider) throws IOException {
|
||||
|
||||
jsonGenerator.writeString(Base64.getEncoder().encodeToString(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Deserializer extends JsonDeserializer<byte[]> {
|
||||
@Override
|
||||
public byte[] deserialize(final JsonParser jsonParser,
|
||||
final DeserializationContext deserializationContext) throws IOException {
|
||||
|
||||
try {
|
||||
return Base64.getDecoder().decode(jsonParser.getValueAsString());
|
||||
} catch (final IllegalArgumentException e) {
|
||||
UNPARSEABLE_DEVICE_NAME_COUNTER.increment();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user