X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/37ed02f4cea9b02431438025c816ca240b642678..416f43b225f20e6b40292d00e05ddd9733f3cc2d:/src/main/java/org/asamk/signal/storage/SignalAccount.java diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index 151835cb..e806794f 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -10,12 +10,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.contacts.JsonContactsStore; +import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.storage.groups.JsonGroupStore; import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; -import org.asamk.signal.storage.threads.JsonThreadStore; +import org.asamk.signal.storage.threads.LegacyJsonThreadStore; +import org.asamk.signal.storage.threads.ThreadInfo; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; +import org.signal.zkgroup.InvalidInputException; +import org.signal.zkgroup.profiles.ProfileKey; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord; @@ -30,6 +35,7 @@ import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.Collection; +import java.util.UUID; public class SignalAccount { @@ -37,12 +43,13 @@ public class SignalAccount { private FileChannel fileChannel; private FileLock lock; private String username; + private UUID uuid; private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; private boolean isMultiDevice = false; private String password; private String registrationLockPin; private String signalingKey; - private byte[] profileKey; + private ProfileKey profileKey; private int preKeyIdOffset; private int nextSignedPreKeyId; @@ -51,7 +58,6 @@ public class SignalAccount { private JsonSignalProtocolStore signalProtocolStore; private JsonGroupStore groupStore; private JsonContactsStore contactStore; - private JsonThreadStore threadStore; private SignalAccount() { jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect @@ -70,7 +76,7 @@ public class SignalAccount { return account; } - public static SignalAccount create(String dataPath, String username, IdentityKeyPair identityKey, int registrationId, byte[] profileKey) throws IOException { + public static SignalAccount create(String dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey) throws IOException { IOUtils.createPrivateDirectories(dataPath); SignalAccount account = new SignalAccount(); @@ -80,27 +86,26 @@ public class SignalAccount { account.profileKey = profileKey; account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); account.groupStore = new JsonGroupStore(); - account.threadStore = new JsonThreadStore(); account.contactStore = new JsonContactsStore(); account.registered = false; return account; } - public static SignalAccount createLinkedAccount(String dataPath, String username, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, byte[] profileKey) throws IOException { + public static SignalAccount createLinkedAccount(String dataPath, String username, UUID uuid, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, ProfileKey profileKey) throws IOException { IOUtils.createPrivateDirectories(dataPath); SignalAccount account = new SignalAccount(); account.openFileChannel(getFileName(dataPath, username)); account.username = username; + account.uuid = uuid; account.password = password; account.profileKey = profileKey; account.deviceId = deviceId; account.signalingKey = signalingKey; account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); account.groupStore = new JsonGroupStore(); - account.threadStore = new JsonThreadStore(); account.contactStore = new JsonContactsStore(); account.registered = true; account.isMultiDevice = true; @@ -136,6 +141,14 @@ public class SignalAccount { rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel)); } + JsonNode uuidNode = rootNode.get("uuid"); + if (uuidNode != null && !uuidNode.isNull()) { + try { + uuid = UUID.fromString(uuidNode.asText()); + } catch (IllegalArgumentException e) { + throw new IOException("Config file contains an invalid uuid, needs to be a valid UUID", e); + } + } JsonNode node = rootNode.get("deviceId"); if (node != null) { deviceId = node.asInt(); @@ -161,7 +174,11 @@ public class SignalAccount { nextSignedPreKeyId = 0; } if (rootNode.has("profileKey")) { - profileKey = Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText()); + try { + profileKey = new ProfileKey(Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText())); + } catch (InvalidInputException e) { + throw new IOException("Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes", e); + } } signalProtocolStore = jsonProcessor.convertValue(Util.getNotNullNode(rootNode, "axolotlStore"), JsonSignalProtocolStore.class); @@ -183,10 +200,27 @@ public class SignalAccount { } JsonNode threadStoreNode = rootNode.get("threadStore"); if (threadStoreNode != null) { - threadStore = jsonProcessor.convertValue(threadStoreNode, JsonThreadStore.class); - } - if (threadStore == null) { - threadStore = new JsonThreadStore(); + LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class); + // Migrate thread info to group and contact store + for (ThreadInfo thread : threadStore.getThreads()) { + if (thread.id == null || thread.id.isEmpty()) { + continue; + } + try { + ContactInfo contactInfo = contactStore.getContact(new SignalServiceAddress(null, thread.id)); + if (contactInfo != null) { + contactInfo.messageExpirationTime = thread.messageExpirationTime; + contactStore.updateContact(contactInfo); + } else { + GroupInfo groupInfo = groupStore.getGroup(Base64.decode(thread.id)); + if (groupInfo != null) { + groupInfo.messageExpirationTime = thread.messageExpirationTime; + groupStore.updateGroup(groupInfo); + } + } + } catch (Exception ignored) { + } + } } } @@ -196,6 +230,7 @@ public class SignalAccount { } ObjectNode rootNode = jsonProcessor.createObjectNode(); rootNode.put("username", username) + .put("uuid", uuid == null ? null : uuid.toString()) .put("deviceId", deviceId) .put("isMultiDevice", isMultiDevice) .put("password", password) @@ -203,12 +238,11 @@ public class SignalAccount { .put("signalingKey", signalingKey) .put("preKeyIdOffset", preKeyIdOffset) .put("nextSignedPreKeyId", nextSignedPreKeyId) - .put("profileKey", Base64.encodeBytes(profileKey)) + .put("profileKey", Base64.encodeBytes(profileKey.serialize())) .put("registered", registered) .putPOJO("axolotlStore", signalProtocolStore) .putPOJO("groupStore", groupStore) .putPOJO("contactStore", contactStore) - .putPOJO("threadStore", threadStore) ; try { synchronized (fileChannel) { @@ -263,16 +297,20 @@ public class SignalAccount { return contactStore; } - public JsonThreadStore getThreadStore() { - return threadStore; - } - public String getUsername() { return username; } + public UUID getUuid() { + return uuid; + } + + public void setUuid(final UUID uuid) { + this.uuid = uuid; + } + public SignalServiceAddress getSelfAddress() { - return new SignalServiceAddress(null, username); + return new SignalServiceAddress(uuid, username); } public int getDeviceId() { @@ -307,11 +345,11 @@ public class SignalAccount { this.signalingKey = signalingKey; } - public byte[] getProfileKey() { + public ProfileKey getProfileKey() { return profileKey; } - public void setProfileKey(final byte[] profileKey) { + public void setProfileKey(final ProfileKey profileKey) { this.profileKey = profileKey; }