Encrypt/decrypt device names

This commit is contained in:
AsamK 2021-05-08 19:31:09 +02:00
parent 56ac648922
commit 6cb867cbeb
6 changed files with 70 additions and 21 deletions

View file

@ -14,7 +14,7 @@ repositories {
} }
dependencies { dependencies {
api("com.github.turasa:signal-service-java:2.15.3_unofficial_20") api("com.github.turasa:signal-service-java:2.15.3_unofficial_21")
implementation("com.google.protobuf:protobuf-javalite:3.10.0") implementation("com.google.protobuf:protobuf-javalite:3.10.0")
implementation("org.bouncycastle:bcprov-jdk15on:1.68") implementation("org.bouncycastle:bcprov-jdk15on:1.68")
implementation("org.slf4j:slf4j-api:1.7.30") implementation("org.slf4j:slf4j-api:1.7.30")

View file

@ -16,6 +16,7 @@
*/ */
package org.asamk.signal.manager; package org.asamk.signal.manager;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
@ -108,7 +109,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsO
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
@ -119,6 +119,7 @@ import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.SleepTimer;
@ -341,7 +342,7 @@ public class Manager implements Closeable {
} }
public void updateAccountAttributes() throws IOException { public void updateAccountAttributes() throws IOException {
accountManager.setAccountAttributes(account.getDeviceName(), accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
null, null,
account.getLocalRegistrationId(), account.getLocalRegistrationId(),
true, true,
@ -422,10 +423,21 @@ public class Manager implements Closeable {
account.setRegistered(false); account.setRegistered(false);
} }
public List<DeviceInfo> getLinkedDevices() throws IOException { public List<Device> getLinkedDevices() throws IOException {
var devices = accountManager.getDevices(); var devices = accountManager.getDevices();
account.setMultiDevice(devices.size() > 1); account.setMultiDevice(devices.size() > 1);
return devices; var identityKey = account.getIdentityKeyPair().getPrivateKey();
return devices.stream().map(d -> {
String deviceName = d.getName();
if (deviceName != null) {
try {
deviceName = DeviceNameUtil.decryptDeviceName(deviceName, identityKey);
} catch (IOException e) {
logger.debug("Failed to decrypt device name, maybe plain text?", e);
}
}
return new Device(d.getId(), deviceName, d.getCreated(), d.getLastSeen());
}).collect(Collectors.toList());
} }
public void removeLinkedDevices(int deviceId) throws IOException { public void removeLinkedDevices(int deviceId) throws IOException {

View file

@ -29,6 +29,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
@ -47,7 +48,7 @@ public class ProvisioningManager {
private final String userAgent; private final String userAgent;
private final SignalServiceAccountManager accountManager; private final SignalServiceAccountManager accountManager;
private final IdentityKeyPair identityKey; private final IdentityKeyPair tempIdentityKey;
private final int registrationId; private final int registrationId;
private final String password; private final String password;
@ -56,7 +57,7 @@ public class ProvisioningManager {
this.serviceEnvironmentConfig = serviceEnvironmentConfig; this.serviceEnvironmentConfig = serviceEnvironmentConfig;
this.userAgent = userAgent; this.userAgent = userAgent;
identityKey = KeyUtils.generateIdentityKeyPair(); tempIdentityKey = KeyUtils.generateIdentityKeyPair();
registrationId = KeyHelper.generateRegistrationId(false); registrationId = KeyHelper.generateRegistrationId(false);
password = KeyUtils.createPassword(); password = KeyUtils.createPassword();
final SleepTimer timer = new UptimeSleepTimer(); final SleepTimer timer = new UptimeSleepTimer();
@ -87,22 +88,26 @@ public class ProvisioningManager {
public URI getDeviceLinkUri() throws TimeoutException, IOException { public URI getDeviceLinkUri() throws TimeoutException, IOException {
var deviceUuid = accountManager.getNewDeviceUuid(); var deviceUuid = accountManager.getNewDeviceUuid();
return new DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()).createDeviceLinkUri(); return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
} }
public Manager finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists { public Manager finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists {
var ret = accountManager.getNewDeviceRegistration(identityKey); var ret = accountManager.getNewDeviceRegistration(tempIdentityKey);
var number = ret.getNumber(); var number = ret.getNumber();
if (SignalAccount.userExists(pathConfig.getDataPath(), number)) { if (SignalAccount.userExists(pathConfig.getDataPath(), number)) {
throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.getDataPath(), number)); throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.getDataPath(), number));
} }
var encryptedDeviceName = deviceName == null
? null
: DeviceNameUtil.encryptDeviceName(deviceName, ret.getIdentity().getPrivateKey());
var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(), var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
false, false,
true, true,
registrationId, registrationId,
deviceName); encryptedDeviceName);
// Create new account with the synced identity // Create new account with the synced identity
var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey(); var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey();
@ -113,7 +118,7 @@ public class ProvisioningManager {
number, number,
ret.getUuid(), ret.getUuid(),
password, password,
deviceName, encryptedDeviceName,
deviceId, deviceId,
ret.getIdentity(), ret.getIdentity(),
registrationId, registrationId,

View file

@ -0,0 +1,32 @@
package org.asamk.signal.manager.api;
public class Device {
private final long id;
private final String name;
private final long created;
private final long lastSeen;
public Device(long id, String name, long created, long lastSeen) {
this.id = id;
this.name = name;
this.created = created;
this.lastSeen = lastSeen;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public long getCreated() {
return created;
}
public long getLastSeen() {
return lastSeen;
}
}

View file

@ -74,7 +74,7 @@ public class SignalAccount implements Closeable {
private String username; private String username;
private UUID uuid; private UUID uuid;
private String deviceName; private String encryptedDeviceName;
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
private boolean isMultiDevice = false; private boolean isMultiDevice = false;
private String password; private String password;
@ -172,7 +172,7 @@ public class SignalAccount implements Closeable {
String username, String username,
UUID uuid, UUID uuid,
String password, String password,
String deviceName, String encryptedDeviceName,
int deviceId, int deviceId,
IdentityKeyPair identityKey, IdentityKeyPair identityKey,
int registrationId, int registrationId,
@ -191,7 +191,7 @@ public class SignalAccount implements Closeable {
account.uuid = uuid; account.uuid = uuid;
account.password = password; account.password = password;
account.profileKey = profileKey; account.profileKey = profileKey;
account.deviceName = deviceName; account.encryptedDeviceName = encryptedDeviceName;
account.deviceId = deviceId; account.deviceId = deviceId;
account.initStores(dataPath, identityKey, registrationId); account.initStores(dataPath, identityKey, registrationId);
@ -307,7 +307,7 @@ public class SignalAccount implements Closeable {
} }
} }
if (rootNode.hasNonNull("deviceName")) { if (rootNode.hasNonNull("deviceName")) {
deviceName = rootNode.get("deviceName").asText(); encryptedDeviceName = rootNode.get("deviceName").asText();
} }
if (rootNode.hasNonNull("deviceId")) { if (rootNode.hasNonNull("deviceId")) {
deviceId = rootNode.get("deviceId").asInt(); deviceId = rootNode.get("deviceId").asInt();
@ -579,7 +579,7 @@ public class SignalAccount implements Closeable {
rootNode.put("version", CURRENT_STORAGE_VERSION) rootNode.put("version", CURRENT_STORAGE_VERSION)
.put("username", username) .put("username", username)
.put("uuid", uuid == null ? null : uuid.toString()) .put("uuid", uuid == null ? null : uuid.toString())
.put("deviceName", deviceName) .put("deviceName", encryptedDeviceName)
.put("deviceId", deviceId) .put("deviceId", deviceId)
.put("isMultiDevice", isMultiDevice) .put("isMultiDevice", isMultiDevice)
.put("password", password) .put("password", password)
@ -708,8 +708,8 @@ public class SignalAccount implements Closeable {
return recipientStore.resolveRecipientTrusted(getSelfAddress()); return recipientStore.resolveRecipientTrusted(getSelfAddress());
} }
public String getDeviceName() { public String getEncryptedDeviceName() {
return deviceName; return encryptedDeviceName;
} }
public int getDeviceId() { public int getDeviceId() {
@ -823,7 +823,7 @@ public class SignalAccount implements Closeable {
public void finishRegistration(final UUID uuid, final MasterKey masterKey, final String pin) { public void finishRegistration(final UUID uuid, final MasterKey masterKey, final String pin) {
this.pinMasterKey = masterKey; this.pinMasterKey = masterKey;
this.deviceName = null; this.encryptedDeviceName = null;
this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
this.isMultiDevice = false; this.isMultiDevice = false;
this.registered = true; this.registered = true;

View file

@ -7,10 +7,10 @@ import org.asamk.signal.PlainTextWriterImpl;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.util.DateUtils; import org.asamk.signal.util.DateUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -27,7 +27,7 @@ public class ListDevicesCommand implements LocalCommand {
public void handleCommand(final Namespace ns, final Manager m) throws CommandException { public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
final var writer = new PlainTextWriterImpl(System.out); final var writer = new PlainTextWriterImpl(System.out);
List<DeviceInfo> devices; List<Device> devices;
try { try {
devices = m.getLinkedDevices(); devices = m.getLinkedDevices();
} catch (IOException e) { } catch (IOException e) {