From b5fd2f920d571c9e2cf7e1ac2691be28b8c3d03d Mon Sep 17 00:00:00 2001 From: technillogue Date: Sun, 25 Apr 2021 19:05:59 -0400 Subject: [PATCH] hopefully fixed some stuff --- build.gradle | 76 - data/signal.service | 16 - .../org/asamk/signal/manager/Manager.java | 4 - settings.gradle | 18 - .../org/asamk/signal/commands/Commands.java | 1 + .../asamk/signal/json/JsonDataMessage.java | 57 +- .../manager/AttachmentInvalidException.java | 12 - .../org/asamk/signal/manager/GroupId.java | 63 - .../manager/GroupIdFormatException.java | 8 - .../org/asamk/signal/manager/GroupIdV1.java | 14 - .../org/asamk/signal/manager/GroupIdV2.java | 14 - .../signal/manager/GroupInviteLinkUrl.java | 140 - .../signal/manager/GroupLinkPassword.java | 40 - .../manager/GroupNotFoundException.java | 8 - .../org/asamk/signal/manager/GroupUtils.java | 68 - .../asamk/signal/manager/HandleAction.java | 158 - .../asamk/signal/manager/IasTrustStore.java | 18 - .../asamk/signal/manager/JsonStickerPack.java | 29 - .../org/asamk/signal/manager/KeyUtils.java | 43 - .../org/asamk/signal/manager/Manager.java | 2765 ----------------- .../manager/NotAGroupMemberException.java | 8 - .../org/asamk/signal/manager/PathConfig.java | 34 - .../signal/manager/ProvisioningManager.java | 133 - .../asamk/signal/manager/ServiceConfig.java | 117 - .../manager/StickerPackInvalidException.java | 8 - .../org/asamk/signal/manager/TrustLevel.java | 42 - .../signal/manager/UserAlreadyExists.java | 22 - .../java/org/asamk/signal/manager/Utils.java | 304 -- .../signal/manager/WhisperTrustStore.java | 18 - .../helper/GroupAuthorizationProvider.java | 11 - .../signal/manager/helper/GroupHelper.java | 398 --- .../manager/helper/MessagePipeProvider.java | 8 - .../helper/MessageReceiverProvider.java | 8 - .../signal/manager/helper/ProfileHelper.java | 123 - .../helper/ProfileKeyCredentialProvider.java | 9 - .../manager/helper/ProfileKeyProvider.java | 9 - .../manager/helper/ProfileProvider.java | 9 - .../manager/helper/SelfAddressProvider.java | 8 - .../helper/SelfProfileKeyProvider.java | 8 - .../helper/UnidentifiedAccessHelper.java | 105 - .../helper/UnidentifiedAccessProvider.java | 10 - ...tifiedAccessSenderCertificateProvider.java | 6 - .../asamk/signal/storage/SignalAccount.java | 517 --- .../signal/storage/contacts/ContactInfo.java | 53 - .../storage/contacts/JsonContactsStore.java | 52 - .../signal/storage/groups/GroupInfo.java | 77 - .../signal/storage/groups/GroupInfoV1.java | 212 -- .../signal/storage/groups/GroupInfoV2.java | 115 - .../signal/storage/groups/JsonGroupStore.java | 205 -- .../signal/storage/profiles/ProfileStore.java | 160 - .../storage/profiles/SignalProfile.java | 122 - .../storage/profiles/SignalProfileEntry.java | 62 - .../protocol/JsonIdentityKeyStore.java | 316 -- .../storage/protocol/JsonPreKeyStore.java | 108 - .../storage/protocol/JsonSessionStore.java | 190 -- .../protocol/JsonSignalProtocolStore.java | 198 -- .../protocol/JsonSignedPreKeyStore.java | 125 - .../storage/protocol/RecipientStore.java | 88 - .../signal/storage/protocol/SessionInfo.java | 18 - .../SignalServiceAddressResolver.java | 13 - .../signal/storage/stickers/Sticker.java | 35 - .../signal/storage/stickers/StickerStore.java | 74 - .../threads/LegacyJsonThreadStore.java | 60 - .../signal/storage/threads/ThreadInfo.java | 12 - .../org/asamk/signal/manager/ias.store | Bin 1441 -> 0 bytes .../org/asamk/signal/manager/whisper.store | Bin 1107 -> 0 bytes 66 files changed, 57 insertions(+), 7705 deletions(-) delete mode 100644 build.gradle delete mode 100644 data/signal.service delete mode 100644 settings.gradle delete mode 100644 src/main/java/org/asamk/signal/manager/AttachmentInvalidException.java delete mode 100644 src/main/java/org/asamk/signal/manager/GroupId.java delete mode 100644 src/main/java/org/asamk/signal/manager/GroupIdFormatException.java delete mode 100644 src/main/java/org/asamk/signal/manager/GroupIdV1.java delete mode 100644 src/main/java/org/asamk/signal/manager/GroupIdV2.java delete mode 100644 src/main/java/org/asamk/signal/manager/GroupInviteLinkUrl.java delete mode 100644 src/main/java/org/asamk/signal/manager/GroupLinkPassword.java delete mode 100644 src/main/java/org/asamk/signal/manager/GroupNotFoundException.java delete mode 100644 src/main/java/org/asamk/signal/manager/GroupUtils.java delete mode 100644 src/main/java/org/asamk/signal/manager/HandleAction.java delete mode 100644 src/main/java/org/asamk/signal/manager/IasTrustStore.java delete mode 100644 src/main/java/org/asamk/signal/manager/JsonStickerPack.java delete mode 100644 src/main/java/org/asamk/signal/manager/KeyUtils.java delete mode 100644 src/main/java/org/asamk/signal/manager/Manager.java delete mode 100644 src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java delete mode 100644 src/main/java/org/asamk/signal/manager/PathConfig.java delete mode 100644 src/main/java/org/asamk/signal/manager/ProvisioningManager.java delete mode 100644 src/main/java/org/asamk/signal/manager/ServiceConfig.java delete mode 100644 src/main/java/org/asamk/signal/manager/StickerPackInvalidException.java delete mode 100644 src/main/java/org/asamk/signal/manager/TrustLevel.java delete mode 100644 src/main/java/org/asamk/signal/manager/UserAlreadyExists.java delete mode 100644 src/main/java/org/asamk/signal/manager/Utils.java delete mode 100644 src/main/java/org/asamk/signal/manager/WhisperTrustStore.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/GroupAuthorizationProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/GroupHelper.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/MessagePipeProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/MessageReceiverProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/ProfileKeyCredentialProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/ProfileKeyProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/SelfAddressProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/SelfProfileKeyProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessProvider.java delete mode 100644 src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessSenderCertificateProvider.java delete mode 100644 src/main/java/org/asamk/signal/storage/SignalAccount.java delete mode 100644 src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java delete mode 100644 src/main/java/org/asamk/signal/storage/contacts/JsonContactsStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/groups/GroupInfo.java delete mode 100644 src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java delete mode 100644 src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java delete mode 100644 src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java delete mode 100644 src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java delete mode 100644 src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/protocol/JsonSignalProtocolStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/protocol/SessionInfo.java delete mode 100644 src/main/java/org/asamk/signal/storage/protocol/SignalServiceAddressResolver.java delete mode 100644 src/main/java/org/asamk/signal/storage/stickers/Sticker.java delete mode 100644 src/main/java/org/asamk/signal/storage/stickers/StickerStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/threads/LegacyJsonThreadStore.java delete mode 100644 src/main/java/org/asamk/signal/storage/threads/ThreadInfo.java delete mode 100644 src/main/resources/org/asamk/signal/manager/ias.store delete mode 100644 src/main/resources/org/asamk/signal/manager/whisper.store diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 1fbf5948..00000000 --- a/build.gradle +++ /dev/null @@ -1,76 +0,0 @@ -apply plugin: 'java' -apply plugin: 'application' -apply plugin: 'eclipse' - -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 - -mainClassName = 'org.asamk.signal.Main' - -version = '0.7.1' - -compileJava.options.encoding = 'UTF-8' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - implementation 'com.github.turasa:signal-service-java:2.15.3_unofficial_15' - implementation 'org.bouncycastle:bcprov-jdk15on:1.67' - implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1' - implementation 'com.github.hypfvieh:dbus-java:3.2.4' - implementation 'org.slf4j:slf4j-simple:1.7.30' -} - -jar { - manifest { - attributes( - 'Implementation-Title': project.name, - 'Implementation-Version': project.version, - 'Main-Class': project.mainClassName, - ) - } -} - -run { - if (project.hasProperty("appArgs")) { - // allow passing command-line arguments to the main application e.g.: - // $ gradle run -PappArgs="['-u', '+...', 'daemon', '--json']" - args Eval.me(appArgs) - } -} - -// Find any 3rd party libraries which have released new versions -// to the central Maven repo since we last upgraded. -task checkLibVersions { - doLast { - def checked = [:] - allprojects { - configurations.each { configuration -> - configuration.allDependencies.each { dependency -> - def version = dependency.version - if (!checked[dependency]) { - def group = dependency.group - def path = group.replace('.', '/') - def name = dependency.name - def url = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml" - try { - def metadata = new XmlSlurper().parseText(url.toURL().text) - def newest = metadata.versioning.latest; - if ("$version" != "$newest") { - println "UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}" - } - } catch (FileNotFoundException e) { - logger.debug "Unable to download $url: $e.message" - } catch (org.xml.sax.SAXParseException e) { - logger.debug "Unable to parse $url: $e.message" - } - checked[dependency] = true - } - } - } - } - } -} diff --git a/data/signal.service b/data/signal.service deleted file mode 100644 index 089b0428..00000000 --- a/data/signal.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Send secure messages to Signal clients -Requires=dbus.socket -After=dbus.socket -Wants=network-online.target -After=network-online.target - -[Service] -Type=dbus -Environment="SIGNAL_CLI_OPTS=-Xms2m" -ExecStart=%dir%/bin/signal-cli -u %number% --config /var/lib/signal-cli daemon --system -User=signal-cli -BusName=org.asamk.Signal - -[Install] -Alias=dbus-org.asamk.Signal.service diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 00e6f1b5..be60753f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -161,10 +161,6 @@ import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.stream.Collectors; -import static org.asamk.signal.manager.ServiceConfig.CDS_MRENCLAVE; -import static org.asamk.signal.manager.ServiceConfig.capabilities; -import static org.asamk.signal.util.ErrorUtils.handleAssertionError; -import static org.asamk.signal.manager.ServiceConfig.getIasKeyStore; import static org.asamk.signal.manager.config.ServiceConfig.capabilities; public class Manager implements Closeable { diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 9f877185..00000000 --- a/settings.gradle +++ /dev/null @@ -1,18 +0,0 @@ -/* - * This settings file was auto generated by the Gradle buildInit task - * - * The settings file is used to specify which projects to include in your build. - * In a single project build this file can be empty or even removed. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user guide at http://gradle.org/docs/2.2.1/userguide/multi_project_builds.html - */ - -/* -// To declare projects as part of a multi-project build use the 'include' method -include 'shared' -include 'api' -include 'services:webservice' -*/ - -rootProject.name = 'signal-cli' diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java index 4a8c70f4..c4e14983 100644 --- a/src/main/java/org/asamk/signal/commands/Commands.java +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -13,6 +13,7 @@ public class Commands { addCommand("daemon", new DaemonCommand()); addCommand("stdio", new StdioCommand()); addCommand("getUserStatus", new GetUserStatusCommand()); + addCommand("getUserStatus", new GetUserStatusCommand()); addCommand("link", new LinkCommand()); addCommand("listContacts", new ListContactsCommand()); addCommand("listDevices", new ListDevicesCommand()); diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index 81240fd9..848a66d9 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -9,6 +9,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import java.util.ArrayList; + import java.util.List; import java.util.stream.Collectors; @@ -51,6 +52,30 @@ class JsonDataMessage { @JsonInclude(JsonInclude.Include.NON_NULL) final JsonQuote quote; + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final List mentions; + @JsonProperty + final long timestamp; + + @JsonProperty + final String message; + + @JsonProperty + final Integer expiresInSeconds; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final Boolean viewOnce; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final JsonReaction reaction; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final JsonQuote quote; + @JsonProperty @JsonInclude(JsonInclude.Include.NON_NULL) final List mentions; @@ -71,6 +96,25 @@ class JsonDataMessage { @JsonInclude(JsonInclude.Include.NON_NULL) final List contacts; + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final List attachments; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final JsonSticker sticker; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final JsonRemoteDelete remoteDelete; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final List contacts; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + final JsonGroupInfo groupInfo; JsonReaction reaction; JsonQuote quote; List mentions; @@ -157,8 +201,19 @@ class JsonDataMessage { } if (message.isExpirationUpdate()) { System.out.println("Is Expiration update: " + message.isExpirationUpdate()); - } + } }*/ + this.sticker = dataMessage.getSticker().isPresent() ? new JsonSticker(dataMessage.getSticker().get()) : null; + + if (dataMessage.getSharedContacts().isPresent()) { + this.contacts = dataMessage.getSharedContacts() + .get() + .stream() + .map(JsonSharedContact::new) + .collect(Collectors.toList()); + } else { + this.contacts = List.of(); + } } public JsonDataMessage(Signal.MessageReceived messageReceived) { diff --git a/src/main/java/org/asamk/signal/manager/AttachmentInvalidException.java b/src/main/java/org/asamk/signal/manager/AttachmentInvalidException.java deleted file mode 100644 index 78fba6e0..00000000 --- a/src/main/java/org/asamk/signal/manager/AttachmentInvalidException.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.asamk.signal.manager; - -public class AttachmentInvalidException extends Exception { - - public AttachmentInvalidException(String message) { - super(message); - } - - public AttachmentInvalidException(String attachment, Exception e) { - super(attachment + ": " + e.getMessage()); - } -} diff --git a/src/main/java/org/asamk/signal/manager/GroupId.java b/src/main/java/org/asamk/signal/manager/GroupId.java deleted file mode 100644 index 34e18e8e..00000000 --- a/src/main/java/org/asamk/signal/manager/GroupId.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.asamk.signal.manager; - -import org.whispersystems.util.Base64; - -import java.util.Arrays; - -public abstract class GroupId { - - private final byte[] id; - - public static GroupIdV1 v1(byte[] id) { - return new GroupIdV1(id); - } - - public static GroupIdV2 v2(byte[] id) { - return new GroupIdV2(id); - } - - public static GroupId unknownVersion(byte[] id) { - if (id.length == 16) { - return new GroupIdV1(id); - } else if (id.length == 32) { - return new GroupIdV2(id); - } - - throw new AssertionError("Invalid group id of size " + id.length); - } - - public static GroupId fromBase64(String id) throws GroupIdFormatException { - try { - return unknownVersion(java.util.Base64.getDecoder().decode(id)); - } catch (Throwable e) { - throw new GroupIdFormatException(id, e); - } - } - - public GroupId(final byte[] id) { - this.id = id; - } - - public byte[] serialize() { - return id; - } - - public String toBase64() { - return Base64.encodeBytes(id); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final GroupId groupId = (GroupId) o; - - return Arrays.equals(id, groupId.id); - } - - @Override - public int hashCode() { - return Arrays.hashCode(id); - } -} diff --git a/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java b/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java deleted file mode 100644 index 83afd15b..00000000 --- a/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager; - -public class GroupIdFormatException extends Exception { - - public GroupIdFormatException(String groupId, Throwable e) { - super("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage(), e); - } -} diff --git a/src/main/java/org/asamk/signal/manager/GroupIdV1.java b/src/main/java/org/asamk/signal/manager/GroupIdV1.java deleted file mode 100644 index 40862f07..00000000 --- a/src/main/java/org/asamk/signal/manager/GroupIdV1.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.asamk.signal.manager; - -import static org.asamk.signal.manager.KeyUtils.getSecretBytes; - -public class GroupIdV1 extends GroupId { - - public static GroupIdV1 createRandom() { - return new GroupIdV1(getSecretBytes(16)); - } - - public GroupIdV1(final byte[] id) { - super(id); - } -} diff --git a/src/main/java/org/asamk/signal/manager/GroupIdV2.java b/src/main/java/org/asamk/signal/manager/GroupIdV2.java deleted file mode 100644 index b329be1d..00000000 --- a/src/main/java/org/asamk/signal/manager/GroupIdV2.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.asamk.signal.manager; - -import java.util.Base64; - -public class GroupIdV2 extends GroupId { - - public static GroupIdV2 fromBase64(String groupId) { - return new GroupIdV2(Base64.getDecoder().decode(groupId)); - } - - public GroupIdV2(final byte[] id) { - super(id); - } -} diff --git a/src/main/java/org/asamk/signal/manager/GroupInviteLinkUrl.java b/src/main/java/org/asamk/signal/manager/GroupInviteLinkUrl.java deleted file mode 100644 index 67ce7892..00000000 --- a/src/main/java/org/asamk/signal/manager/GroupInviteLinkUrl.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.asamk.signal.manager; - -import com.google.protobuf.ByteString; - -import org.signal.storageservice.protos.groups.GroupInviteLink; -import org.signal.storageservice.protos.groups.local.DecryptedGroup; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.groups.GroupMasterKey; -import org.whispersystems.util.Base64UrlSafe; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; - -public final class GroupInviteLinkUrl { - - private static final String GROUP_URL_HOST = "signal.group"; - private static final String GROUP_URL_PREFIX = "https://" + GROUP_URL_HOST + "/#"; - - private final GroupMasterKey groupMasterKey; - private final GroupLinkPassword password; - private final String url; - - public static GroupInviteLinkUrl forGroup(GroupMasterKey groupMasterKey, DecryptedGroup group) { - return new GroupInviteLinkUrl(groupMasterKey, - GroupLinkPassword.fromBytes(group.getInviteLinkPassword().toByteArray())); - } - - public static boolean isGroupLink(String urlString) { - return getGroupUrl(urlString) != null; - } - - /** - * @return null iff not a group url. - * @throws InvalidGroupLinkException If group url, but cannot be parsed. - */ - public static GroupInviteLinkUrl fromUri(String urlString) throws InvalidGroupLinkException, UnknownGroupLinkVersionException { - URI uri = getGroupUrl(urlString); - - if (uri == null) { - return null; - } - - try { - if (!"/".equals(uri.getPath()) && uri.getPath().length() > 0) { - throw new InvalidGroupLinkException("No path was expected in uri"); - } - - String encoding = uri.getFragment(); - - if (encoding == null || encoding.length() == 0) { - throw new InvalidGroupLinkException("No reference was in the uri"); - } - - byte[] bytes = Base64UrlSafe.decodePaddingAgnostic(encoding); - GroupInviteLink groupInviteLink = GroupInviteLink.parseFrom(bytes); - - switch (groupInviteLink.getContentsCase()) { - case V1CONTENTS: { - GroupInviteLink.GroupInviteLinkContentsV1 groupInviteLinkContentsV1 = groupInviteLink.getV1Contents(); - GroupMasterKey groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.getGroupMasterKey() - .toByteArray()); - GroupLinkPassword password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.getInviteLinkPassword() - .toByteArray()); - - return new GroupInviteLinkUrl(groupMasterKey, password); - } - default: - throw new UnknownGroupLinkVersionException("Url contains no known group link content"); - } - } catch (InvalidInputException | IOException e) { - throw new InvalidGroupLinkException(e); - } - } - - /** - * @return {@link URI} if the host name matches. - */ - private static URI getGroupUrl(String urlString) { - try { - URI url = new URI(urlString); - - if (!"https".equalsIgnoreCase(url.getScheme()) && !"sgnl".equalsIgnoreCase(url.getScheme())) { - return null; - } - - return GROUP_URL_HOST.equalsIgnoreCase(url.getHost()) ? url : null; - } catch (URISyntaxException e) { - return null; - } - } - - private GroupInviteLinkUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) { - this.groupMasterKey = groupMasterKey; - this.password = password; - this.url = createUrl(groupMasterKey, password); - } - - protected static String createUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) { - GroupInviteLink groupInviteLink = GroupInviteLink.newBuilder() - .setV1Contents(GroupInviteLink.GroupInviteLinkContentsV1.newBuilder() - .setGroupMasterKey(ByteString.copyFrom(groupMasterKey.serialize())) - .setInviteLinkPassword(ByteString.copyFrom(password.serialize()))) - .build(); - - String encoding = Base64UrlSafe.encodeBytesWithoutPadding(groupInviteLink.toByteArray()); - - return GROUP_URL_PREFIX + encoding; - } - - public String getUrl() { - return url; - } - - public GroupMasterKey getGroupMasterKey() { - return groupMasterKey; - } - - public GroupLinkPassword getPassword() { - return password; - } - - public final static class InvalidGroupLinkException extends Exception { - - public InvalidGroupLinkException(String message) { - super(message); - } - - public InvalidGroupLinkException(Throwable cause) { - super(cause); - } - } - - public final static class UnknownGroupLinkVersionException extends Exception { - - public UnknownGroupLinkVersionException(String message) { - super(message); - } - } -} diff --git a/src/main/java/org/asamk/signal/manager/GroupLinkPassword.java b/src/main/java/org/asamk/signal/manager/GroupLinkPassword.java deleted file mode 100644 index 38e2aaf4..00000000 --- a/src/main/java/org/asamk/signal/manager/GroupLinkPassword.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.asamk.signal.manager; - -import java.util.Arrays; - -public final class GroupLinkPassword { - - private static final int SIZE = 16; - - private final byte[] bytes; - - public static GroupLinkPassword createNew() { - return new GroupLinkPassword(KeyUtils.getSecretBytes(SIZE)); - } - - public static GroupLinkPassword fromBytes(byte[] bytes) { - return new GroupLinkPassword(bytes); - } - - private GroupLinkPassword(byte[] bytes) { - this.bytes = bytes; - } - - public byte[] serialize() { - return bytes.clone(); - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof GroupLinkPassword)) { - return false; - } - - return Arrays.equals(bytes, ((GroupLinkPassword) other).bytes); - } - - @Override - public int hashCode() { - return Arrays.hashCode(bytes); - } -} diff --git a/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java b/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java deleted file mode 100644 index d7efa923..00000000 --- a/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager; - -public class GroupNotFoundException extends Exception { - - public GroupNotFoundException(GroupId groupId) { - super("Group not found: " + groupId.toBase64()); - } -} diff --git a/src/main/java/org/asamk/signal/manager/GroupUtils.java b/src/main/java/org/asamk/signal/manager/GroupUtils.java deleted file mode 100644 index d86dfbe9..00000000 --- a/src/main/java/org/asamk/signal/manager/GroupUtils.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.asamk.signal.manager; - -import org.asamk.signal.storage.groups.GroupInfo; -import org.asamk.signal.storage.groups.GroupInfoV1; -import org.asamk.signal.storage.groups.GroupInfoV2; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.groups.GroupMasterKey; -import org.signal.zkgroup.groups.GroupSecretParams; -import org.whispersystems.libsignal.kdf.HKDFv3; -import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; -import org.whispersystems.signalservice.api.messages.SignalServiceGroup; -import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; -import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; - -public class GroupUtils { - - public static void setGroupContext( - final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo groupInfo - ) { - if (groupInfo instanceof GroupInfoV1) { - SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER) - .withId(groupInfo.getGroupId().serialize()) - .build(); - messageBuilder.asGroupMessage(group); - } else { - final GroupInfoV2 groupInfoV2 = (GroupInfoV2) groupInfo; - SignalServiceGroupV2 group = SignalServiceGroupV2.newBuilder(groupInfoV2.getMasterKey()) - .withRevision(groupInfoV2.getGroup() == null ? 0 : groupInfoV2.getGroup().getRevision()) - .build(); - messageBuilder.asGroupMessage(group); - } - } - - public static GroupId getGroupId(SignalServiceGroupContext context) { - if (context.getGroupV1().isPresent()) { - return GroupId.v1(context.getGroupV1().get().getGroupId()); - } else if (context.getGroupV2().isPresent()) { - return getGroupIdV2(context.getGroupV2().get().getMasterKey()); - } else { - return null; - } - } - - public static GroupIdV2 getGroupIdV2(GroupSecretParams groupSecretParams) { - return GroupId.v2(groupSecretParams.getPublicParams().getGroupIdentifier().serialize()); - } - - public static GroupIdV2 getGroupIdV2(GroupMasterKey groupMasterKey) { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - return getGroupIdV2(groupSecretParams); - } - - public static GroupIdV2 getGroupIdV2(GroupIdV1 groupIdV1) { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(deriveV2MigrationMasterKey( - groupIdV1)); - return getGroupIdV2(groupSecretParams); - } - - private static GroupMasterKey deriveV2MigrationMasterKey(GroupIdV1 groupIdV1) { - try { - return new GroupMasterKey(new HKDFv3().deriveSecrets(groupIdV1.serialize(), - "GV2 Migration".getBytes(), - GroupMasterKey.SIZE)); - } catch (InvalidInputException e) { - throw new AssertionError(e); - } - } -} diff --git a/src/main/java/org/asamk/signal/manager/HandleAction.java b/src/main/java/org/asamk/signal/manager/HandleAction.java deleted file mode 100644 index aa25d8c5..00000000 --- a/src/main/java/org/asamk/signal/manager/HandleAction.java +++ /dev/null @@ -1,158 +0,0 @@ -package org.asamk.signal.manager; - -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.util.Objects; - -interface HandleAction { - - void execute(Manager m) throws Throwable; -} - -class SendReceiptAction implements HandleAction { - - private final SignalServiceAddress address; - private final long timestamp; - - public SendReceiptAction(final SignalServiceAddress address, final long timestamp) { - this.address = address; - this.timestamp = timestamp; - } - - @Override - public void execute(Manager m) throws Throwable { - m.sendReceipt(address, timestamp); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final SendReceiptAction that = (SendReceiptAction) o; - return timestamp == that.timestamp && address.equals(that.address); - } - - @Override - public int hashCode() { - return Objects.hash(address, timestamp); - } -} - -class SendSyncContactsAction implements HandleAction { - - private static final SendSyncContactsAction INSTANCE = new SendSyncContactsAction(); - - private SendSyncContactsAction() { - } - - public static SendSyncContactsAction create() { - return INSTANCE; - } - - @Override - public void execute(Manager m) throws Throwable { - m.sendContacts(); - } -} - -class SendSyncGroupsAction implements HandleAction { - - private static final SendSyncGroupsAction INSTANCE = new SendSyncGroupsAction(); - - private SendSyncGroupsAction() { - } - - public static SendSyncGroupsAction create() { - return INSTANCE; - } - - @Override - public void execute(Manager m) throws Throwable { - m.sendGroups(); - } -} - -class SendSyncBlockedListAction implements HandleAction { - - private static final SendSyncBlockedListAction INSTANCE = new SendSyncBlockedListAction(); - - private SendSyncBlockedListAction() { - } - - public static SendSyncBlockedListAction create() { - return INSTANCE; - } - - @Override - public void execute(Manager m) throws Throwable { - m.sendBlockedList(); - } -} - -class SendGroupInfoRequestAction implements HandleAction { - - private final SignalServiceAddress address; - private final GroupIdV1 groupId; - - public SendGroupInfoRequestAction(final SignalServiceAddress address, final GroupIdV1 groupId) { - this.address = address; - this.groupId = groupId; - } - - @Override - public void execute(Manager m) throws Throwable { - m.sendGroupInfoRequest(groupId, address); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final SendGroupInfoRequestAction that = (SendGroupInfoRequestAction) o; - - if (!address.equals(that.address)) return false; - return groupId.equals(that.groupId); - } - - @Override - public int hashCode() { - int result = address.hashCode(); - result = 31 * result + groupId.hashCode(); - return result; - } -} - -class SendGroupUpdateAction implements HandleAction { - - private final SignalServiceAddress address; - private final GroupIdV1 groupId; - - public SendGroupUpdateAction(final SignalServiceAddress address, final GroupIdV1 groupId) { - this.address = address; - this.groupId = groupId; - } - - @Override - public void execute(Manager m) throws Throwable { - m.sendUpdateGroupMessage(groupId, address); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final SendGroupUpdateAction that = (SendGroupUpdateAction) o; - - if (!address.equals(that.address)) return false; - return groupId.equals(that.groupId); - } - - @Override - public int hashCode() { - int result = address.hashCode(); - result = 31 * result + groupId.hashCode(); - return result; - } -} diff --git a/src/main/java/org/asamk/signal/manager/IasTrustStore.java b/src/main/java/org/asamk/signal/manager/IasTrustStore.java deleted file mode 100644 index f9bbb0b3..00000000 --- a/src/main/java/org/asamk/signal/manager/IasTrustStore.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.asamk.signal.manager; - -import org.whispersystems.signalservice.api.push.TrustStore; - -import java.io.InputStream; - -class IasTrustStore implements TrustStore { - - @Override - public InputStream getKeyStoreInputStream() { - return IasTrustStore.class.getResourceAsStream("ias.store"); - } - - @Override - public String getKeyStorePassword() { - return "whisper"; - } -} diff --git a/src/main/java/org/asamk/signal/manager/JsonStickerPack.java b/src/main/java/org/asamk/signal/manager/JsonStickerPack.java deleted file mode 100644 index a7e5eb7f..00000000 --- a/src/main/java/org/asamk/signal/manager/JsonStickerPack.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.asamk.signal.manager; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -class JsonStickerPack { - - @JsonProperty - public String title; - - @JsonProperty - public String author; - - @JsonProperty - public JsonSticker cover; - - @JsonProperty - public List stickers; - - public static class JsonSticker { - - @JsonProperty - public String emoji; - - @JsonProperty - public String file; - } -} diff --git a/src/main/java/org/asamk/signal/manager/KeyUtils.java b/src/main/java/org/asamk/signal/manager/KeyUtils.java deleted file mode 100644 index 21f6037f..00000000 --- a/src/main/java/org/asamk/signal/manager/KeyUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.asamk.signal.manager; - -import org.asamk.signal.util.RandomUtils; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.profiles.ProfileKey; -import org.whispersystems.util.Base64; - -class KeyUtils { - - private KeyUtils() { - } - - static String createSignalingKey() { - return getSecret(52); - } - - static ProfileKey createProfileKey() { - try { - return new ProfileKey(getSecretBytes(32)); - } catch (InvalidInputException e) { - throw new AssertionError("Profile key is guaranteed to be 32 bytes here"); - } - } - - static String createPassword() { - return getSecret(18); - } - - static byte[] createStickerUploadKey() { - return getSecretBytes(32); - } - - private static String getSecret(int size) { - byte[] secret = getSecretBytes(size); - return Base64.encodeBytes(secret); - } - - static byte[] getSecretBytes(int size) { - byte[] secret = new byte[size]; - RandomUtils.getSecureRandom().nextBytes(secret); - return secret; - } -} diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java deleted file mode 100644 index be0789c5..00000000 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ /dev/null @@ -1,2765 +0,0 @@ -/* - Copyright (C) 2015-2020 AsamK and contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package org.asamk.signal.manager; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.asamk.signal.manager.helper.GroupHelper; -import org.asamk.signal.manager.helper.ProfileHelper; -import org.asamk.signal.manager.helper.UnidentifiedAccessHelper; -import org.asamk.signal.storage.SignalAccount; -import org.asamk.signal.storage.contacts.ContactInfo; -import org.asamk.signal.storage.groups.GroupInfo; -import org.asamk.signal.storage.groups.GroupInfoV1; -import org.asamk.signal.storage.groups.GroupInfoV2; -import org.asamk.signal.storage.profiles.SignalProfile; -import org.asamk.signal.storage.profiles.SignalProfileEntry; -import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; -import org.asamk.signal.storage.stickers.Sticker; -import org.asamk.signal.util.IOUtils; -import org.asamk.signal.util.Util; -import org.signal.libsignal.metadata.InvalidMetadataMessageException; -import org.signal.libsignal.metadata.InvalidMetadataVersionException; -import org.signal.libsignal.metadata.ProtocolDuplicateMessageException; -import org.signal.libsignal.metadata.ProtocolInvalidKeyException; -import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException; -import org.signal.libsignal.metadata.ProtocolInvalidMessageException; -import org.signal.libsignal.metadata.ProtocolInvalidVersionException; -import org.signal.libsignal.metadata.ProtocolLegacyMessageException; -import org.signal.libsignal.metadata.ProtocolNoSessionException; -import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; -import org.signal.libsignal.metadata.SelfSendException; -import org.signal.storageservice.protos.groups.GroupChange; -import org.signal.storageservice.protos.groups.local.DecryptedGroup; -import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; -import org.signal.storageservice.protos.groups.local.DecryptedMember; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.VerificationFailedException; -import org.signal.zkgroup.auth.AuthCredentialResponse; -import org.signal.zkgroup.groups.GroupMasterKey; -import org.signal.zkgroup.groups.GroupSecretParams; -import org.signal.zkgroup.profiles.ClientZkProfileOperations; -import org.signal.zkgroup.profiles.ProfileKey; -import org.signal.zkgroup.profiles.ProfileKeyCredential; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.IdentityKeyPair; -import org.whispersystems.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.InvalidMessageException; -import org.whispersystems.libsignal.InvalidVersionException; -import org.whispersystems.libsignal.ecc.Curve; -import org.whispersystems.libsignal.ecc.ECKeyPair; -import org.whispersystems.libsignal.ecc.ECPublicKey; -import org.whispersystems.libsignal.state.PreKeyRecord; -import org.whispersystems.libsignal.state.SignedPreKeyRecord; -import org.whispersystems.libsignal.util.KeyHelper; -import org.whispersystems.libsignal.util.Medium; -import org.whispersystems.libsignal.util.Pair; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.SignalServiceMessagePipe; -import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; -import org.whispersystems.signalservice.api.SignalServiceMessageSender; -import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException; -import org.whispersystems.signalservice.api.crypto.ProfileCipher; -import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; -import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; -import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; -import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; -import org.whispersystems.signalservice.api.messages.SendMessageResult; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; -import org.whispersystems.signalservice.api.messages.SignalServiceContent; -import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.messages.SignalServiceGroup; -import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; -import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; -import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload; -import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload.StickerInfo; -import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; -import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; -import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; -import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream; -import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream; -import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; -import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream; -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.SentTranscriptMessage; -import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; -import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; -import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; -import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; -import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; -import org.whispersystems.signalservice.api.push.ContactTokenDetails; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; -import org.whispersystems.signalservice.api.util.InvalidNumberException; -import org.whispersystems.signalservice.api.util.SleepTimer; -import org.whispersystems.signalservice.api.util.StreamDetails; -import org.whispersystems.signalservice.api.util.UptimeSleepTimer; -import org.whispersystems.signalservice.api.util.UuidUtil; -import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; -import org.whispersystems.signalservice.internal.contacts.crypto.Quote; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; -import org.whispersystems.signalservice.internal.push.SignalServiceProtos; -import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; -import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; -import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; -import org.whispersystems.signalservice.internal.util.Hex; -import org.whispersystems.util.Base64; - -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import static org.asamk.signal.manager.ServiceConfig.CDS_MRENCLAVE; -import static org.asamk.signal.manager.ServiceConfig.capabilities; -import static org.asamk.signal.util.ErrorUtils.handleAssertionError; -import static org.asamk.signal.manager.ServiceConfig.getIasKeyStore; - -public class Manager implements Closeable { - - final static Logger logger = LoggerFactory.getLogger(Manager.class); - - private final SleepTimer timer = new UptimeSleepTimer(); - - private final SignalServiceConfiguration serviceConfiguration; - private final String userAgent; - private final boolean discoverableByPhoneNumber = true; - private final boolean unrestrictedUnidentifiedAccess = false; - - private final SignalAccount account; - private final PathConfig pathConfig; - private SignalServiceAccountManager accountManager; - private GroupsV2Api groupsV2Api; - private final GroupsV2Operations groupsV2Operations; - - private SignalServiceMessageReceiver messageReceiver = null; - private SignalServiceMessagePipe messagePipe = null; - private SignalServiceMessagePipe unidentifiedMessagePipe = null; - - private final UnidentifiedAccessHelper unidentifiedAccessHelper; - private final ProfileHelper profileHelper; - private final GroupHelper groupHelper; - - public Manager( - SignalAccount account, - PathConfig pathConfig, - SignalServiceConfiguration serviceConfiguration, - String userAgent - ) { - this.account = account; - this.pathConfig = pathConfig; - this.serviceConfiguration = serviceConfiguration; - this.userAgent = userAgent; - this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create( - serviceConfiguration)) : null; - this.accountManager = createSignalServiceAccountManager(); - this.groupsV2Api = accountManager.getGroupsV2Api(); - - this.account.setResolver(this::resolveSignalServiceAddress); - - this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey, - account.getProfileStore()::getProfileKey, - this::getRecipientProfile, - this::getSenderCertificate); - this.profileHelper = new ProfileHelper(account.getProfileStore()::getProfileKey, - unidentifiedAccessHelper::getAccessFor, - unidentified -> unidentified ? getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(), - this::getOrCreateMessageReceiver); - this.groupHelper = new GroupHelper(this::getRecipientProfileKeyCredential, - this::getRecipientProfile, - account::getSelfAddress, - groupsV2Operations, - groupsV2Api, - this::getGroupAuthForToday); - } - - public String getUsername() { - return account.getUsername(); - } - - public SignalServiceAddress getSelfAddress() { - return account.getSelfAddress(); - } - - private SignalServiceAccountManager createSignalServiceAccountManager() { - return new SignalServiceAccountManager(serviceConfiguration, - new DynamicCredentialsProvider(account.getUuid(), - account.getUsername(), - account.getPassword(), - null, - account.getDeviceId()), - userAgent, - groupsV2Operations, - timer); - } - - private IdentityKeyPair getIdentityKeyPair() { - return account.getSignalProtocolStore().getIdentityKeyPair(); - } - - public int getDeviceId() { - return account.getDeviceId(); - } - - private File getMessageCachePath() { - return SignalAccount.getMessageCachePath(pathConfig.getDataPath(), account.getUsername()); - } - - private File getMessageCachePath(String sender) { - if (sender == null || sender.isEmpty()) { - return getMessageCachePath(); - } - - return new File(getMessageCachePath(), sender.replace("/", "_")); - } - - private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException { - File cachePath = getMessageCachePath(sender); - IOUtils.createPrivateDirectories(cachePath); - return new File(cachePath, now + "_" + timestamp); - } - - public static Manager init( - String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent - ) throws IOException { - PathConfig pathConfig = PathConfig.createDefault(settingsPath); - - if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) { - IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair(); - int registrationId = KeyHelper.generateRegistrationId(false); - - ProfileKey profileKey = KeyUtils.createProfileKey(); - SignalAccount account = SignalAccount.create(pathConfig.getDataPath(), - username, - identityKey, - registrationId, - profileKey); - account.save(); - - return new Manager(account, pathConfig, serviceConfiguration, userAgent); - } - - SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username); - - Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent); - - m.migrateLegacyConfigs(); - - return m; - } - - private void migrateLegacyConfigs() { - if (account.getProfileKey() == null && isRegistered()) { - // Old config file, creating new profile key - account.setProfileKey(KeyUtils.createProfileKey()); - account.save(); - } - // Store profile keys only in profile store - for (ContactInfo contact : account.getContactStore().getContacts()) { - String profileKeyString = contact.profileKey; - if (profileKeyString == null) { - continue; - } - final ProfileKey profileKey; - try { - profileKey = new ProfileKey(Base64.decode(profileKeyString)); - } catch (InvalidInputException | IOException e) { - continue; - } - contact.profileKey = null; - account.getProfileStore().storeProfileKey(contact.getAddress(), profileKey); - } - // Ensure our profile key is stored in profile store - account.getProfileStore().storeProfileKey(getSelfAddress(), account.getProfileKey()); - } - - public void checkAccountState() throws IOException { - if (account.isRegistered()) { - if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) { - refreshPreKeys(); - account.save(); - } - if (account.getUuid() == null) { - account.setUuid(accountManager.getOwnUuid()); - account.save(); - } - updateAccountAttributes(); - } - } - - public boolean isRegistered() { - return account.isRegistered(); - } - - public void register(boolean voiceVerification, String captcha) throws IOException { - account.setPassword(KeyUtils.createPassword()); - - // Resetting UUID, because registering doesn't work otherwise - account.setUuid(null); - accountManager = createSignalServiceAccountManager(); - this.groupsV2Api = accountManager.getGroupsV2Api(); - - if (voiceVerification) { - accountManager.requestVoiceVerificationCode(Locale.getDefault(), - Optional.fromNullable(captcha), - Optional.absent()); - } else { - accountManager.requestSmsVerificationCode(false, Optional.fromNullable(captcha), Optional.absent()); - } - - account.setRegistered(false); - account.save(); - } - - public void updateAccountAttributes() throws IOException { - accountManager.setAccountAttributes(account.getSignalingKey(), - account.getSignalProtocolStore().getLocalRegistrationId(), - true, - account.getRegistrationLockPin(), - account.getRegistrationLock(), - unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(), - unrestrictedUnidentifiedAccess, - capabilities, - discoverableByPhoneNumber); - } - - public void setProfile(String name, File avatar) throws IOException { - try (final StreamDetails streamDetails = avatar == null ? null : Utils.createStreamDetailsFromFile(avatar)) { - accountManager.setVersionedProfile(account.getUuid(), account.getProfileKey(), name, streamDetails); - } - } - - public void unregister() throws IOException { - // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false. - // If this is the master device, other users can't send messages to this number anymore. - // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore. - accountManager.setGcmId(Optional.absent()); - - account.setRegistered(false); - account.save(); - } - - public List getLinkedDevices() throws IOException { - List devices = accountManager.getDevices(); - account.setMultiDevice(devices.size() > 1); - account.save(); - return devices; - } - - public void removeLinkedDevices(int deviceId) throws IOException { - accountManager.removeDevice(deviceId); - List devices = accountManager.getDevices(); - account.setMultiDevice(devices.size() > 1); - account.save(); - } - - public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException { - Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri); - - addDevice(info.deviceIdentifier, info.deviceKey); - } - - private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException { - IdentityKeyPair identityKeyPair = getIdentityKeyPair(); - String verificationCode = accountManager.getNewDeviceVerificationCode(); - - accountManager.addDevice(deviceIdentifier, - deviceKey, - identityKeyPair, - Optional.of(account.getProfileKey().serialize()), - verificationCode); - account.setMultiDevice(true); - account.save(); - } - - private List generatePreKeys() { - List records = new ArrayList<>(ServiceConfig.PREKEY_BATCH_SIZE); - - final int offset = account.getPreKeyIdOffset(); - for (int i = 0; i < ServiceConfig.PREKEY_BATCH_SIZE; i++) { - int preKeyId = (offset + i) % Medium.MAX_VALUE; - ECKeyPair keyPair = Curve.generateKeyPair(); - PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair); - - records.add(record); - } - - account.addPreKeys(records); - account.save(); - - return records; - } - - private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) { - try { - ECKeyPair keyPair = Curve.generateKeyPair(); - byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), - keyPair.getPublicKey().serialize()); - SignedPreKeyRecord record = new SignedPreKeyRecord(account.getNextSignedPreKeyId(), - System.currentTimeMillis(), - keyPair, - signature); - - account.addSignedPreKey(record); - account.save(); - - return record; - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } - } - - public void verifyAccount(String verificationCode, String pin) throws IOException { - verificationCode = verificationCode.replace("-", ""); - account.setSignalingKey(KeyUtils.createSignalingKey()); - // TODO make unrestricted unidentified access configurable - VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, - account.getSignalingKey(), - account.getSignalProtocolStore().getLocalRegistrationId(), - true, - pin, - null, - unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(), - unrestrictedUnidentifiedAccess, - capabilities, - discoverableByPhoneNumber); - - UUID uuid = UuidUtil.parseOrNull(response.getUuid()); - // TODO response.isStorageCapable() - //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); - account.setRegistered(true); - account.setUuid(uuid); - account.setRegistrationLockPin(pin); - account.getSignalProtocolStore() - .saveIdentity(account.getSelfAddress(), - getIdentityKeyPair().getPublicKey(), - TrustLevel.TRUSTED_VERIFIED); - - refreshPreKeys(); - account.save(); - } - - public void setRegistrationLockPin(Optional pin) throws IOException { - if (pin.isPresent()) { - account.setRegistrationLockPin(pin.get()); - throw new RuntimeException("Not implemented anymore, will be replaced with KBS"); - } else { - account.setRegistrationLockPin(null); - accountManager.removeRegistrationLockV1(); - } - account.save(); - } - - void refreshPreKeys() throws IOException { - List oneTimePreKeys = generatePreKeys(); - final IdentityKeyPair identityKeyPair = getIdentityKeyPair(); - SignedPreKeyRecord signedPreKeyRecord = generateSignedPreKey(identityKeyPair); - - accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys); - } - - private SignalServiceMessageReceiver createMessageReceiver() { - final ClientZkProfileOperations clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create( - serviceConfiguration).getProfileOperations() : null; - return new SignalServiceMessageReceiver(serviceConfiguration, - account.getUuid(), - account.getUsername(), - account.getPassword(), - account.getDeviceId(), - account.getSignalingKey(), - userAgent, - null, - timer, - clientZkProfileOperations); - } - - private SignalServiceMessageReceiver getOrCreateMessageReceiver() { - if (messageReceiver == null) { - messageReceiver = createMessageReceiver(); - } - return messageReceiver; - } - - private SignalServiceMessagePipe getOrCreateMessagePipe() { - if (messagePipe == null) { - messagePipe = getOrCreateMessageReceiver().createMessagePipe(); - } - return messagePipe; - } - - private SignalServiceMessagePipe getOrCreateUnidentifiedMessagePipe() { - if (unidentifiedMessagePipe == null) { - unidentifiedMessagePipe = getOrCreateMessageReceiver().createUnidentifiedMessagePipe(); - } - return unidentifiedMessagePipe; - } - - private SignalServiceMessageSender createMessageSender() { - final ClientZkProfileOperations clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create( - serviceConfiguration).getProfileOperations() : null; - final ExecutorService executor = null; - return new SignalServiceMessageSender(serviceConfiguration, - account.getUuid(), - account.getUsername(), - account.getPassword(), - account.getDeviceId(), - account.getSignalProtocolStore(), - userAgent, - account.isMultiDevice(), - Optional.fromNullable(messagePipe), - Optional.fromNullable(unidentifiedMessagePipe), - Optional.absent(), - clientZkProfileOperations, - executor, - ServiceConfig.MAX_ENVELOPE_SIZE); - } - - private SignalServiceProfile getEncryptedRecipientProfile(SignalServiceAddress address) throws IOException { - return profileHelper.retrieveProfileSync(address, SignalServiceProfile.RequestType.PROFILE).getProfile(); - } - - private SignalProfile getRecipientProfile( - SignalServiceAddress address - ) { - SignalProfileEntry profileEntry = account.getProfileStore().getProfileEntry(address); - if (profileEntry == null) { - return null; - } - long now = new Date().getTime(); - // Profiles are cache for 24h before retrieving them again - if (!profileEntry.isRequestPending() && ( - profileEntry.getProfile() == null || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000 - )) { - ProfileKey profileKey = profileEntry.getProfileKey(); - profileEntry.setRequestPending(true); - SignalProfile profile; - try { - profile = retrieveRecipientProfile(address, profileKey); - } catch (IOException e) { - logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage()); - profileEntry.setRequestPending(false); - return null; - } - profileEntry.setRequestPending(false); - account.getProfileStore() - .updateProfile(address, profileKey, now, profile, profileEntry.getProfileKeyCredential()); - return profile; - } - return profileEntry.getProfile(); - } - - private ProfileKeyCredential getRecipientProfileKeyCredential(SignalServiceAddress address) { - SignalProfileEntry profileEntry = account.getProfileStore().getProfileEntry(address); - if (profileEntry == null) { - return null; - } - if (profileEntry.getProfileKeyCredential() == null) { - ProfileAndCredential profileAndCredential; - try { - profileAndCredential = profileHelper.retrieveProfileSync(address, - SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL); - } catch (IOException e) { - logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage()); - return null; - } - - long now = new Date().getTime(); - final ProfileKeyCredential profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull(); - final SignalProfile profile = decryptProfile(address, - profileEntry.getProfileKey(), - profileAndCredential.getProfile()); - account.getProfileStore() - .updateProfile(address, profileEntry.getProfileKey(), now, profile, profileKeyCredential); - return profileKeyCredential; - } - return profileEntry.getProfileKeyCredential(); - } - - private SignalProfile retrieveRecipientProfile( - SignalServiceAddress address, ProfileKey profileKey - ) throws IOException { - final SignalServiceProfile encryptedProfile = getEncryptedRecipientProfile(address); - - return decryptProfile(address, profileKey, encryptedProfile); - } - - private SignalProfile decryptProfile( - final SignalServiceAddress address, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile - ) { - File avatarFile = null; - try { - avatarFile = encryptedProfile.getAvatar() == null - ? null - : retrieveProfileAvatar(address, encryptedProfile.getAvatar(), profileKey); - } catch (Throwable e) { - logger.warn("Failed to retrieve profile avatar, ignoring: {}", e.getMessage()); - } - - ProfileCipher profileCipher = new ProfileCipher(profileKey); - try { - String name; - try { - name = encryptedProfile.getName() == null - ? null - : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName()))); - } catch (IOException e) { - name = null; - } - String unidentifiedAccess; - try { - unidentifiedAccess = encryptedProfile.getUnidentifiedAccess() == null - || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess())) - ? null - : encryptedProfile.getUnidentifiedAccess(); - } catch (IOException e) { - unidentifiedAccess = null; - } - return new SignalProfile(encryptedProfile.getIdentityKey(), - name, - avatarFile, - unidentifiedAccess, - encryptedProfile.isUnrestrictedUnidentifiedAccess(), - encryptedProfile.getCapabilities()); - } catch (InvalidCiphertextException e) { - return null; - } - } - - private Optional createGroupAvatarAttachment(GroupId groupId) throws IOException { - File file = getGroupAvatarFile(groupId); - if (!file.exists()) { - return Optional.absent(); - } - - return Optional.of(Utils.createAttachment(file)); - } - - private Optional createContactAvatarAttachment(String number) throws IOException { - File file = getContactAvatarFile(number); - if (!file.exists()) { - return Optional.absent(); - } - - return Optional.of(Utils.createAttachment(file)); - } - - private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { - GroupInfo g = account.getGroupStore().getGroup(groupId); - if (g == null) { - throw new GroupNotFoundException(groupId); - } - if (!g.isMember(account.getSelfAddress())) { - throw new NotAGroupMemberException(groupId, g.getTitle()); - } - return g; - } - - private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { - GroupInfo g = account.getGroupStore().getGroup(groupId); - if (g == null) { - throw new GroupNotFoundException(groupId); - } - if (!g.isMember(account.getSelfAddress()) && !g.isPendingMember(account.getSelfAddress())) { - throw new NotAGroupMemberException(groupId, g.getTitle()); - } - return g; - } - - public List getGroups() { - return account.getGroupStore().getGroups(); - } - - public Pair> sendGroupMessage( - SignalServiceDataMessage.Builder messageBuilder, GroupId groupId - ) throws IOException, GroupNotFoundException, NotAGroupMemberException { - final GroupInfo g = getGroupForSending(groupId); - - GroupUtils.setGroupContext(messageBuilder, g); - messageBuilder.withExpiration(g.getMessageExpirationTime()); - - return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); - } - - public Pair> sendGroupMessage( - String messageText, List attachments, GroupId groupId - ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { - final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() - .withBody(messageText); - if (attachments != null) { - messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments)); - } - - return sendGroupMessage(messageBuilder, groupId); - } - - public Pair> sendGroupMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, GroupId groupId - ) throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException { - SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, - remove, - canonicalizeAndResolveSignalServiceAddress(targetAuthor), - targetSentTimestamp); - final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() - .withReaction(reaction); - - return sendGroupMessage(messageBuilder, groupId); - } - - public Pair> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException { - - SignalServiceDataMessage.Builder messageBuilder; - - final GroupInfo g = getGroupForUpdating(groupId); - if (g instanceof GroupInfoV1) { - GroupInfoV1 groupInfoV1 = (GroupInfoV1) g; - SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT) - .withId(groupId.serialize()) - .build(); - messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group); - groupInfoV1.removeMember(account.getSelfAddress()); - account.getGroupStore().updateGroup(groupInfoV1); - } else { - final GroupInfoV2 groupInfoV2 = (GroupInfoV2) g; - final Pair groupGroupChangePair = groupHelper.leaveGroup(groupInfoV2); - groupInfoV2.setGroup(groupGroupChangePair.first()); - messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray()); - account.getGroupStore().updateGroup(groupInfoV2); - } - - return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); - } - - private Pair> sendUpdateGroupMessage( - GroupId groupId, String name, Collection members, String avatarFile - ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { - GroupInfo g; - SignalServiceDataMessage.Builder messageBuilder; - if (groupId == null) { - // Create new group - GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile); - if (gv2 == null) { - GroupInfoV1 gv1 = new GroupInfoV1(GroupIdV1.createRandom()); - gv1.addMembers(Collections.singleton(account.getSelfAddress())); - updateGroupV1(gv1, name, members, avatarFile); - messageBuilder = getGroupUpdateMessageBuilder(gv1); - g = gv1; - } else { - messageBuilder = getGroupUpdateMessageBuilder(gv2, null); - g = gv2; - } - } else { - GroupInfo group = getGroupForUpdating(groupId); - if (group instanceof GroupInfoV2) { - final GroupInfoV2 groupInfoV2 = (GroupInfoV2) group; - - Pair> result = null; - if (groupInfoV2.isPendingMember(getSelfAddress())) { - Pair groupGroupChangePair = groupHelper.acceptInvite(groupInfoV2); - result = sendUpdateGroupMessage(groupInfoV2, - groupGroupChangePair.first(), - groupGroupChangePair.second()); - } - - if (members != null) { - final Set newMembers = new HashSet<>(members); - newMembers.removeAll(group.getMembers() - .stream() - .map(this::resolveSignalServiceAddress) - .collect(Collectors.toSet())); - if (newMembers.size() > 0) { - Pair groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2, - newMembers); - result = sendUpdateGroupMessage(groupInfoV2, - groupGroupChangePair.first(), - groupGroupChangePair.second()); - } - } - if (result == null || name != null || avatarFile != null) { - Pair groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2, - name, - avatarFile); - result = sendUpdateGroupMessage(groupInfoV2, - groupGroupChangePair.first(), - groupGroupChangePair.second()); - } - - return new Pair<>(group.getGroupId(), result.second()); - } else { - GroupInfoV1 gv1 = (GroupInfoV1) group; - updateGroupV1(gv1, name, members, avatarFile); - messageBuilder = getGroupUpdateMessageBuilder(gv1); - g = gv1; - } - } - - account.getGroupStore().updateGroup(g); - - final Pair> result = sendMessage(messageBuilder, - g.getMembersIncludingPendingWithout(account.getSelfAddress())); - return new Pair<>(g.getGroupId(), result.second()); - } - - public Pair> joinGroup( - GroupInviteLinkUrl inviteLinkUrl - ) throws IOException, GroupLinkNotActiveException { - return sendJoinGroupMessage(inviteLinkUrl); - } - - private Pair> sendJoinGroupMessage( - GroupInviteLinkUrl inviteLinkUrl - ) throws IOException, GroupLinkNotActiveException { - final DecryptedGroupJoinInfo groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(), - inviteLinkUrl.getPassword()); - final GroupChange groupChange = groupHelper.joinGroup(inviteLinkUrl.getGroupMasterKey(), - inviteLinkUrl.getPassword(), - groupJoinInfo); - final GroupInfoV2 group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(), - groupJoinInfo.getRevision() + 1, - groupChange.toByteArray()); - - if (group.getGroup() == null) { - // Only requested member, can't send update to group members - return new Pair<>(group.getGroupId(), List.of()); - } - - final Pair> result = sendUpdateGroupMessage(group, group.getGroup(), groupChange); - - return new Pair<>(group.getGroupId(), result.second()); - } - - private Pair> sendUpdateGroupMessage( - GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange - ) throws IOException { - group.setGroup(newDecryptedGroup); - final SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(group, - groupChange.toByteArray()); - account.getGroupStore().updateGroup(group); - return sendMessage(messageBuilder, group.getMembersIncludingPendingWithout(account.getSelfAddress())); - } - - private void updateGroupV1( - final GroupInfoV1 g, - final String name, - final Collection members, - final String avatarFile - ) throws IOException { - if (name != null) { - g.name = name; - } - - if (members != null) { - final Set newE164Members = new HashSet<>(); - for (SignalServiceAddress member : members) { - if (g.isMember(member) || !member.getNumber().isPresent()) { - continue; - } - newE164Members.add(member.getNumber().get()); - } - - final List contacts = accountManager.getContacts(newE164Members); - if (contacts.size() != newE164Members.size()) { - // Some of the new members are not registered on Signal - for (ContactTokenDetails contact : contacts) { - newE164Members.remove(contact.getNumber()); - } - throw new IOException("Failed to add members " - + Util.join(", ", newE164Members) - + " to group: Not registered on Signal"); - } - - g.addMembers(members); - } - - if (avatarFile != null) { - IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); - File aFile = getGroupAvatarFile(g.getGroupId()); - Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } - - Pair> sendUpdateGroupMessage( - GroupIdV1 groupId, SignalServiceAddress recipient - ) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { - GroupInfoV1 g; - GroupInfo group = getGroupForSending(groupId); - if (!(group instanceof GroupInfoV1)) { - throw new RuntimeException("Received an invalid group request for a v2 group!"); - } - g = (GroupInfoV1) group; - - if (!g.isMember(recipient)) { - throw new NotAGroupMemberException(groupId, g.name); - } - - SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g); - - // Send group message only to the recipient who requested it - return sendMessage(messageBuilder, Collections.singleton(recipient)); - } - - private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException { - SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.UPDATE) - .withId(g.getGroupId().serialize()) - .withName(g.name) - .withMembers(new ArrayList<>(g.getMembers())); - - File aFile = getGroupAvatarFile(g.getGroupId()); - if (aFile.exists()) { - try { - group.withAvatar(Utils.createAttachment(aFile)); - } catch (IOException e) { - throw new AttachmentInvalidException(aFile.toString(), e); - } - } - - return SignalServiceDataMessage.newBuilder() - .asGroupMessage(group.build()) - .withExpiration(g.getMessageExpirationTime()); - } - - private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) { - SignalServiceGroupV2.Builder group = SignalServiceGroupV2.newBuilder(g.getMasterKey()) - .withRevision(g.getGroup().getRevision()) - .withSignedGroupChange(signedGroupChange); - return SignalServiceDataMessage.newBuilder() - .asGroupMessage(group.build()) - .withExpiration(g.getMessageExpirationTime()); - } - - Pair> sendGroupInfoRequest( - GroupIdV1 groupId, SignalServiceAddress recipient - ) throws IOException { - SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO) - .withId(groupId.serialize()); - - SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() - .asGroupMessage(group.build()); - - // Send group info request message to the recipient who sent us a message with this groupId - return sendMessage(messageBuilder, Collections.singleton(recipient)); - } - - void sendReceipt( - SignalServiceAddress remoteAddress, long messageId - ) throws IOException, UntrustedIdentityException { - SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY, - Collections.singletonList(messageId), - System.currentTimeMillis()); - - createMessageSender().sendReceipt(remoteAddress, - unidentifiedAccessHelper.getAccessFor(remoteAddress), - receiptMessage); - } - - public Pair> sendMessage( - String messageText, List attachments, List recipients - ) throws IOException, AttachmentInvalidException, InvalidNumberException { - final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() - .withBody(messageText); - if (attachments != null) { - List attachmentStreams = Utils.getSignalServiceAttachments(attachments); - - // Upload attachments here, so we only upload once even for multiple recipients - SignalServiceMessageSender messageSender = createMessageSender(); - List attachmentPointers = new ArrayList<>(attachmentStreams.size()); - for (SignalServiceAttachment attachment : attachmentStreams) { - if (attachment.isStream()) { - attachmentPointers.add(messageSender.uploadAttachment(attachment.asStream())); - } else if (attachment.isPointer()) { - attachmentPointers.add(attachment.asPointer()); - } - } - - messageBuilder.withAttachments(attachmentPointers); - } - return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); - } - - public Pair> sendMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List recipients - ) throws IOException, InvalidNumberException { - SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, - remove, - canonicalizeAndResolveSignalServiceAddress(targetAuthor), - targetSentTimestamp); - final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() - .withReaction(reaction); - return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); - } - - public Pair> sendEndSessionMessage(List recipients) throws IOException, InvalidNumberException { - SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().asEndSessionMessage(); - - final Collection signalServiceAddresses = getSignalServiceAddresses(recipients); - try { - return sendMessage(messageBuilder, signalServiceAddresses); - } catch (Exception e) { - for (SignalServiceAddress address : signalServiceAddresses) { - handleEndSession(address); - } - account.save(); - throw e; - } - } - - public String getContactName(String number) throws InvalidNumberException { - ContactInfo contact = account.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number)); - if (contact == null) { - return ""; - } else { - return contact.name; - } - } - - public void setContactName(String number, String name) throws InvalidNumberException { - final SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(number); - ContactInfo contact = account.getContactStore().getContact(address); - if (contact == null) { - contact = new ContactInfo(address); - } - contact.name = name; - account.getContactStore().updateContact(contact); - account.save(); - } - - public void setContactBlocked(String number, boolean blocked) throws InvalidNumberException { - setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number), blocked); - } - - private void setContactBlocked(SignalServiceAddress address, boolean blocked) { - ContactInfo contact = account.getContactStore().getContact(address); - if (contact == null) { - contact = new ContactInfo(address); - } - contact.blocked = blocked; - account.getContactStore().updateContact(contact); - account.save(); - } - - public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException { - GroupInfo group = getGroup(groupId); - if (group == null) { - throw new GroupNotFoundException(groupId); - } - - group.setBlocked(blocked); - account.getGroupStore().updateGroup(group); - account.save(); - } - - public Pair> updateGroup( - GroupId groupId, String name, List members, String avatar - ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException { - return sendUpdateGroupMessage(groupId, - name, - members == null ? null : getSignalServiceAddresses(members), - avatar); - } - - /** - * Change the expiration timer for a contact - */ - public void setExpirationTimer(SignalServiceAddress address, int messageExpirationTimer) throws IOException { - ContactInfo contact = account.getContactStore().getContact(address); - contact.messageExpirationTime = messageExpirationTimer; - account.getContactStore().updateContact(contact); - sendExpirationTimerUpdate(address); - account.save(); - } - - private void sendExpirationTimerUpdate(SignalServiceAddress address) throws IOException { - final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() - .asExpirationUpdate(); - sendMessage(messageBuilder, Collections.singleton(address)); - } - - /** - * Change the expiration timer for a contact - */ - public void setExpirationTimer( - String number, int messageExpirationTimer - ) throws IOException, InvalidNumberException { - SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(number); - setExpirationTimer(address, messageExpirationTimer); - } - - /** - * Change the expiration timer for a group - */ - public void setExpirationTimer(GroupId groupId, int messageExpirationTimer) { - GroupInfo g = account.getGroupStore().getGroup(groupId); - if (g instanceof GroupInfoV1) { - GroupInfoV1 groupInfoV1 = (GroupInfoV1) g; - groupInfoV1.messageExpirationTime = messageExpirationTimer; - account.getGroupStore().updateGroup(groupInfoV1); - } else { - throw new RuntimeException("TODO Not implemented!"); - } - } - - /** - * Upload the sticker pack from path. - * - * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file - * @return if successful, returns the URL to install the sticker pack in the signal app - */ - public String uploadStickerPack(File path) throws IOException, StickerPackInvalidException { - SignalServiceStickerManifestUpload manifest = getSignalServiceStickerManifestUpload(path); - - SignalServiceMessageSender messageSender = createMessageSender(); - - byte[] packKey = KeyUtils.createStickerUploadKey(); - String packId = messageSender.uploadStickerManifest(manifest, packKey); - - Sticker sticker = new Sticker(Hex.fromStringCondensed(packId), packKey); - account.getStickerStore().updateSticker(sticker); - account.save(); - - try { - return new URI("https", - "signal.art", - "/addstickers/", - "pack_id=" + URLEncoder.encode(packId, StandardCharsets.UTF_8) + "&pack_key=" + URLEncoder.encode( - Hex.toStringCondensed(packKey), - StandardCharsets.UTF_8)).toString(); - } catch (URISyntaxException e) { - throw new AssertionError(e); - } - } - - private SignalServiceStickerManifestUpload getSignalServiceStickerManifestUpload( - final File file - ) throws IOException, StickerPackInvalidException { - ZipFile zip = null; - String rootPath = null; - - if (file.getName().endsWith(".zip")) { - zip = new ZipFile(file); - } else if (file.getName().equals("manifest.json")) { - rootPath = file.getParent(); - } else { - throw new StickerPackInvalidException("Could not find manifest.json"); - } - - JsonStickerPack pack = parseStickerPack(rootPath, zip); - - if (pack.stickers == null) { - throw new StickerPackInvalidException("Must set a 'stickers' field."); - } - - if (pack.stickers.isEmpty()) { - throw new StickerPackInvalidException("Must include stickers."); - } - - List stickers = new ArrayList<>(pack.stickers.size()); - for (JsonStickerPack.JsonSticker sticker : pack.stickers) { - if (sticker.file == null) { - throw new StickerPackInvalidException("Must set a 'file' field on each sticker."); - } - - Pair data; - try { - data = getInputStreamAndLength(rootPath, zip, sticker.file); - } catch (IOException ignored) { - throw new StickerPackInvalidException("Could not find find " + sticker.file); - } - - String contentType = Utils.getFileMimeType(new File(sticker.file), null); - StickerInfo stickerInfo = new StickerInfo(data.first(), - data.second(), - Optional.fromNullable(sticker.emoji).or(""), - contentType); - stickers.add(stickerInfo); - } - - StickerInfo cover = null; - if (pack.cover != null) { - if (pack.cover.file == null) { - throw new StickerPackInvalidException("Must set a 'file' field on the cover."); - } - - Pair data; - try { - data = getInputStreamAndLength(rootPath, zip, pack.cover.file); - } catch (IOException ignored) { - throw new StickerPackInvalidException("Could not find find " + pack.cover.file); - } - - String contentType = Utils.getFileMimeType(new File(pack.cover.file), null); - cover = new StickerInfo(data.first(), - data.second(), - Optional.fromNullable(pack.cover.emoji).or(""), - contentType); - } - - return new SignalServiceStickerManifestUpload(pack.title, pack.author, cover, stickers); - } - - private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException { - InputStream inputStream; - if (zip != null) { - inputStream = zip.getInputStream(zip.getEntry("manifest.json")); - } else { - inputStream = new FileInputStream((new File(rootPath, "manifest.json"))); - } - return new ObjectMapper().readValue(inputStream, JsonStickerPack.class); - } - - private static Pair getInputStreamAndLength( - final String rootPath, final ZipFile zip, final String subfile - ) throws IOException { - if (zip != null) { - final ZipEntry entry = zip.getEntry(subfile); - return new Pair<>(zip.getInputStream(entry), entry.getSize()); - } else { - final File file = new File(rootPath, subfile); - return new Pair<>(new FileInputStream(file), file.length()); - } - } - - void requestSyncGroups() throws IOException { - SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder() - .setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS) - .build(); - SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - e.printStackTrace(); - } - } - - void requestSyncContacts() throws IOException { - SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder() - .setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS) - .build(); - SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - e.printStackTrace(); - } - } - - void requestSyncBlocked() throws IOException { - SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder() - .setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED) - .build(); - SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - e.printStackTrace(); - } - } - - void requestSyncConfiguration() throws IOException { - SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder() - .setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION) - .build(); - SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); - try { - sendSyncMessage(message); - } catch (UntrustedIdentityException e) { - e.printStackTrace(); - } - } - - private byte[] getSenderCertificate() { - // TODO support UUID capable sender certificates - // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy(); - byte[] certificate; - try { - certificate = accountManager.getSenderCertificate(); - } catch (IOException e) { - logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage()); - return null; - } - // TODO cache for a day - return certificate; - } - - private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { - SignalServiceMessageSender messageSender = createMessageSender(); - try { - messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync()); - } catch (UntrustedIdentityException e) { - account.getSignalProtocolStore() - .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), - e.getIdentityKey(), - TrustLevel.UNTRUSTED); - throw e; - } - } - - private Collection getSignalServiceAddresses(Collection numbers) throws InvalidNumberException { - final Set signalServiceAddresses = new HashSet<>(numbers.size()); - final Set missingUuids = new HashSet<>(); - - for (String number : numbers) { - final SignalServiceAddress resolvedAddress = canonicalizeAndResolveSignalServiceAddress(number); - if (resolvedAddress.getUuid().isPresent()) { - signalServiceAddresses.add(resolvedAddress); - } else { - missingUuids.add(resolvedAddress); - } - } - - Map registeredUsers; - try { - registeredUsers = accountManager.getRegisteredUsers(getIasKeyStore(), - missingUuids.stream().map(a -> a.getNumber().get()).collect(Collectors.toSet()), - CDS_MRENCLAVE); - } catch (IOException | Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException e) { - logger.warn("Failed to resolve uuids from server, ignoring: {}", e.getMessage()); - registeredUsers = new HashMap<>(); - } - - for (SignalServiceAddress address : missingUuids) { - final String number = address.getNumber().get(); - if (registeredUsers.containsKey(number)) { - final SignalServiceAddress newAddress = resolveSignalServiceAddress(new SignalServiceAddress( - registeredUsers.get(number), - number)); - signalServiceAddresses.add(newAddress); - } else { - signalServiceAddresses.add(address); - } - } - - return signalServiceAddresses; - } - - private Pair> sendMessage( - SignalServiceDataMessage.Builder messageBuilder, Collection recipients - ) throws IOException { - recipients = recipients.stream().map(this::resolveSignalServiceAddress).collect(Collectors.toSet()); - final long timestamp = System.currentTimeMillis(); - messageBuilder.withTimestamp(timestamp); - getOrCreateMessagePipe(); - getOrCreateUnidentifiedMessagePipe(); - SignalServiceDataMessage message = null; - try { - message = messageBuilder.build(); - if (message.getGroupContext().isPresent()) { - try { - SignalServiceMessageSender messageSender = createMessageSender(); - final boolean isRecipientUpdate = false; - List result = messageSender.sendMessage(new ArrayList<>(recipients), - unidentifiedAccessHelper.getAccessFor(recipients), - isRecipientUpdate, - message); - for (SendMessageResult r : result) { - if (r.getIdentityFailure() != null) { - account.getSignalProtocolStore() - .saveIdentity(r.getAddress(), - r.getIdentityFailure().getIdentityKey(), - TrustLevel.UNTRUSTED); - } - } - return new Pair<>(timestamp, result); - } catch (UntrustedIdentityException e) { - account.getSignalProtocolStore() - .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), - e.getIdentityKey(), - TrustLevel.UNTRUSTED); - return new Pair<>(timestamp, Collections.emptyList()); - } - } else { - // Send to all individually, so sync messages are sent correctly - List results = new ArrayList<>(recipients.size()); - for (SignalServiceAddress address : recipients) { - ContactInfo contact = account.getContactStore().getContact(address); - if (contact != null) { - messageBuilder.withExpiration(contact.messageExpirationTime); - messageBuilder.withProfileKey(account.getProfileKey().serialize()); - } else { - messageBuilder.withExpiration(0); - messageBuilder.withProfileKey(null); - } - message = messageBuilder.build(); - if (address.matches(account.getSelfAddress())) { - results.add(sendSelfMessage(message)); - } else { - results.add(sendMessage(address, message)); - } - } - return new Pair<>(timestamp, results); - } - } finally { - if (message != null && message.isEndSession()) { - for (SignalServiceAddress recipient : recipients) { - handleEndSession(recipient); - } - } - account.save(); - } - } - - private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException { - SignalServiceMessageSender messageSender = createMessageSender(); - - SignalServiceAddress recipient = account.getSelfAddress(); - - final Optional unidentifiedAccess = unidentifiedAccessHelper.getAccessFor(recipient); - SentTranscriptMessage transcript = new SentTranscriptMessage(Optional.of(recipient), - message.getTimestamp(), - message, - message.getExpiresInSeconds(), - Collections.singletonMap(recipient, unidentifiedAccess.isPresent()), - false); - SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript); - - try { - long startTime = System.currentTimeMillis(); - messageSender.sendMessage(syncMessage, unidentifiedAccess); - return SendMessageResult.success(recipient, - unidentifiedAccess.isPresent(), - false, - System.currentTimeMillis() - startTime); - } catch (UntrustedIdentityException e) { - account.getSignalProtocolStore() - .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), - e.getIdentityKey(), - TrustLevel.UNTRUSTED); - return SendMessageResult.identityFailure(recipient, e.getIdentityKey()); - } - } - - private SendMessageResult sendMessage( - SignalServiceAddress address, SignalServiceDataMessage message - ) throws IOException { - SignalServiceMessageSender messageSender = createMessageSender(); - - try { - return messageSender.sendMessage(address, unidentifiedAccessHelper.getAccessFor(address), message); - } catch (UntrustedIdentityException e) { - account.getSignalProtocolStore() - .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), - e.getIdentityKey(), - TrustLevel.UNTRUSTED); - return SendMessageResult.identityFailure(address, e.getIdentityKey()); - } - } - - private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException { - SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(), - account.getSignalProtocolStore(), - Utils.getCertificateValidator()); - try { - return cipher.decrypt(envelope); - } catch (ProtocolUntrustedIdentityException e) { - if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) { - org.whispersystems.libsignal.UntrustedIdentityException identityException = (org.whispersystems.libsignal.UntrustedIdentityException) e - .getCause(); - account.getSignalProtocolStore() - .saveIdentity(resolveSignalServiceAddress(identityException.getName()), - identityException.getUntrustedIdentity(), - TrustLevel.UNTRUSTED); - throw identityException; - } - throw new AssertionError(e); - } - } - - private void handleEndSession(SignalServiceAddress source) { - account.getSignalProtocolStore().deleteAllSessions(source); - } - - private static int currentTimeDays() { - return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()); - } - - private GroupsV2AuthorizationString getGroupAuthForToday( - final GroupSecretParams groupSecretParams - ) throws IOException { - final int today = currentTimeDays(); - // Returns credentials for the next 7 days - final HashMap credentials = groupsV2Api.getCredentials(today); - // TODO cache credentials until they expire - AuthCredentialResponse authCredentialResponse = credentials.get(today); - try { - return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(), - today, - groupSecretParams, - authCredentialResponse); - } catch (VerificationFailedException e) { - throw new IOException(e); - } - } - - private List handleSignalServiceDataMessage( - SignalServiceDataMessage message, - boolean isSync, - SignalServiceAddress source, - SignalServiceAddress destination, - boolean ignoreAttachments - ) { - List actions = new ArrayList<>(); - if (message.getGroupContext().isPresent()) { - if (message.getGroupContext().get().getGroupV1().isPresent()) { - SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); - GroupIdV1 groupId = GroupId.v1(groupInfo.getGroupId()); - GroupInfo group = account.getGroupStore().getGroup(groupId); - if (group == null || group instanceof GroupInfoV1) { - GroupInfoV1 groupV1 = (GroupInfoV1) group; - switch (groupInfo.getType()) { - case UPDATE: { - if (groupV1 == null) { - groupV1 = new GroupInfoV1(groupId); - } - - if (groupInfo.getAvatar().isPresent()) { - SignalServiceAttachment avatar = groupInfo.getAvatar().get(); - if (avatar.isPointer()) { - try { - retrieveGroupAvatarAttachment(avatar.asPointer(), groupV1.getGroupId()); - } catch (IOException | InvalidMessageException | MissingConfigurationException e) { - logger.warn("Failed to retrieve avatar for group {}, ignoring: {}", - groupId.toBase64(), - e.getMessage()); - } - } - } - - if (groupInfo.getName().isPresent()) { - groupV1.name = groupInfo.getName().get(); - } - - if (groupInfo.getMembers().isPresent()) { - groupV1.addMembers(groupInfo.getMembers() - .get() - .stream() - .map(this::resolveSignalServiceAddress) - .collect(Collectors.toSet())); - } - - account.getGroupStore().updateGroup(groupV1); - break; - } - case DELIVER: - if (groupV1 == null && !isSync) { - actions.add(new SendGroupInfoRequestAction(source, groupId)); - } - break; - case QUIT: { - if (groupV1 != null) { - groupV1.removeMember(source); - account.getGroupStore().updateGroup(groupV1); - } - break; - } - case REQUEST_INFO: - if (groupV1 != null && !isSync) { - actions.add(new SendGroupUpdateAction(source, groupV1.getGroupId())); - } - break; - } - } else { - // Received a group v1 message for a v2 group - } - } - if (message.getGroupContext().get().getGroupV2().isPresent()) { - final SignalServiceGroupV2 groupContext = message.getGroupContext().get().getGroupV2().get(); - final GroupMasterKey groupMasterKey = groupContext.getMasterKey(); - - getOrMigrateGroup(groupMasterKey, - groupContext.getRevision(), - groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null); - } - } - - final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source; - if (conversationPartnerAddress != null && message.isEndSession()) { - handleEndSession(conversationPartnerAddress); - } - if (message.isExpirationUpdate() || message.getBody().isPresent()) { - if (message.getGroupContext().isPresent()) { - if (message.getGroupContext().get().getGroupV1().isPresent()) { - SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); - GroupInfoV1 group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId())); - if (group != null) { - if (group.messageExpirationTime != message.getExpiresInSeconds()) { - group.messageExpirationTime = message.getExpiresInSeconds(); - account.getGroupStore().updateGroup(group); - } - } - } else if (message.getGroupContext().get().getGroupV2().isPresent()) { - // disappearing message timer already stored in the DecryptedGroup - } - } else if (conversationPartnerAddress != null) { - ContactInfo contact = account.getContactStore().getContact(conversationPartnerAddress); - if (contact == null) { - contact = new ContactInfo(conversationPartnerAddress); - } - if (contact.messageExpirationTime != message.getExpiresInSeconds()) { - contact.messageExpirationTime = message.getExpiresInSeconds(); - account.getContactStore().updateContact(contact); - } - } - } - if (message.getAttachments().isPresent() && !ignoreAttachments) { - for (SignalServiceAttachment attachment : message.getAttachments().get()) { - if (attachment.isPointer()) { - try { - retrieveAttachment(attachment.asPointer()); - } catch (IOException | InvalidMessageException | MissingConfigurationException e) { - logger.warn("Failed to retrieve attachment ({}), ignoring: {}", - attachment.asPointer().getRemoteId(), - e.getMessage()); - } - } - } - } - if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { - final ProfileKey profileKey; - try { - profileKey = new ProfileKey(message.getProfileKey().get()); - } catch (InvalidInputException e) { - throw new AssertionError(e); - } - if (source.matches(account.getSelfAddress())) { - this.account.setProfileKey(profileKey); - } - this.account.getProfileStore().storeProfileKey(source, profileKey); - } - if (message.getPreviews().isPresent()) { - final List previews = message.getPreviews().get(); - for (SignalServiceDataMessage.Preview preview : previews) { - if (preview.getImage().isPresent() && preview.getImage().get().isPointer()) { - SignalServiceAttachmentPointer attachment = preview.getImage().get().asPointer(); - try { - retrieveAttachment(attachment); - } catch (IOException | InvalidMessageException | MissingConfigurationException e) { - logger.warn("Failed to retrieve preview image ({}), ignoring: {}", - attachment.getRemoteId(), - e.getMessage()); - } - } - } - } - if (message.getQuote().isPresent()) { - final SignalServiceDataMessage.Quote quote = message.getQuote().get(); - - for (SignalServiceDataMessage.Quote.QuotedAttachment quotedAttachment : quote.getAttachments()) { - final SignalServiceAttachment attachment = quotedAttachment.getThumbnail(); - if (attachment != null && attachment.isPointer()) { - try { - retrieveAttachment(attachment.asPointer()); - } catch (IOException | InvalidMessageException | MissingConfigurationException e) { - logger.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}", - attachment.asPointer().getRemoteId(), - e.getMessage()); - } - } - } - } - if (message.getSticker().isPresent()) { - final SignalServiceDataMessage.Sticker messageSticker = message.getSticker().get(); - Sticker sticker = account.getStickerStore().getSticker(messageSticker.getPackId()); - if (sticker == null) { - sticker = new Sticker(messageSticker.getPackId(), messageSticker.getPackKey()); - account.getStickerStore().updateSticker(sticker); - } - } - return actions; - } - - private GroupInfoV2 getOrMigrateGroup( - final GroupMasterKey groupMasterKey, final int revision, final byte[] signedGroupChange - ) { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - - GroupIdV2 groupId = GroupUtils.getGroupIdV2(groupSecretParams); - GroupInfo groupInfo = account.getGroupStore().getGroup(groupId); - final GroupInfoV2 groupInfoV2; - if (groupInfo instanceof GroupInfoV1) { - // Received a v2 group message for a v1 group, we need to locally migrate the group - account.getGroupStore().deleteGroup(groupInfo.getGroupId()); - groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey); - logger.info("Locally migrated group {} to group v2, id: {}", - groupInfo.getGroupId().toBase64(), - groupInfoV2.getGroupId().toBase64()); - } else if (groupInfo instanceof GroupInfoV2) { - groupInfoV2 = (GroupInfoV2) groupInfo; - } else { - groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey); - } - - if (groupInfoV2.getGroup() == null || groupInfoV2.getGroup().getRevision() < revision) { - DecryptedGroup group = null; - if (signedGroupChange != null - && groupInfoV2.getGroup() != null - && groupInfoV2.getGroup().getRevision() + 1 == revision) { - group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(), signedGroupChange, groupMasterKey); - } - if (group == null) { - group = groupHelper.getDecryptedGroup(groupSecretParams); - } - if (group != null) { - storeProfileKeysFromMembers(group); - final String avatar = group.getAvatar(); - if (avatar != null && !avatar.isEmpty()) { - try { - retrieveGroupAvatar(groupId, groupSecretParams, avatar); - } catch (IOException e) { - logger.warn("Failed to download group avatar, ignoring: {}", e.getMessage()); - } - } - } - groupInfoV2.setGroup(group); - account.getGroupStore().updateGroup(groupInfoV2); - } - - return groupInfoV2; - } - - private void storeProfileKeysFromMembers(final DecryptedGroup group) { - for (DecryptedMember member : group.getMembersList()) { - final SignalServiceAddress address = resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil.parseOrThrow( - member.getUuid().toByteArray()), null)); - try { - account.getProfileStore() - .storeProfileKey(address, new ProfileKey(member.getProfileKey().toByteArray())); - } catch (InvalidInputException ignored) { - } - } - } - - private void retryFailedReceivedMessages( - ReceiveMessageHandler handler, boolean ignoreAttachments - ) { - final File cachePath = getMessageCachePath(); - if (!cachePath.exists()) { - return; - } - for (final File dir : Objects.requireNonNull(cachePath.listFiles())) { - if (!dir.isDirectory()) { - retryFailedReceivedMessage(handler, ignoreAttachments, dir); - continue; - } - - for (final File fileEntry : Objects.requireNonNull(dir.listFiles())) { - if (!fileEntry.isFile()) { - continue; - } - retryFailedReceivedMessage(handler, ignoreAttachments, fileEntry); - } - // Try to delete directory if empty - dir.delete(); - } - } - - private void retryFailedReceivedMessage( - final ReceiveMessageHandler handler, final boolean ignoreAttachments, final File fileEntry - ) { - SignalServiceEnvelope envelope; - try { - envelope = Utils.loadEnvelope(fileEntry); - if (envelope == null) { - return; - } - } catch (IOException e) { - e.printStackTrace(); - return; - } - SignalServiceContent content = null; - if (!envelope.isReceipt()) { - try { - content = decryptMessage(envelope); - } catch (org.whispersystems.libsignal.UntrustedIdentityException e) { - return; - } catch (Exception er) { - // All other errors are not recoverable, so delete the cached message - try { - Files.delete(fileEntry.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage()); - } - return; - } - List actions = handleMessage(envelope, content, ignoreAttachments); - for (HandleAction action : actions) { - try { - action.execute(this); - } catch (Throwable e) { - e.printStackTrace(); - } - } - } - account.save(); - handler.handleMessage(envelope, content, null); - try { - Files.delete(fileEntry.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage()); - } - } - - public void receiveMessagesAndReadStdin(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException { - retryFailedReceivedMessages(handler, ignoreAttachments); - final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); - - Set queuedActions = null; - - if (messagePipe == null) { - messagePipe = messageReceiver.createMessagePipe(); - } - - boolean hasCaughtUpWithOldMessages = false; - - while (true) { - SignalServiceEnvelope envelope; - SignalServiceContent content = null; - Exception exception = null; - final long now = new Date().getTime(); - try { - Optional result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> { - // store message on disk, before acknowledging receipt to the server - try { - String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : ""; - File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp()); - Utils.storeEnvelope(envelope1, cacheFile); - } catch (IOException e) { - System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage()); - } - }); - if (result.isPresent()) { - envelope = result.get(); - } else { - // Received indicator that server queue is empty - hasCaughtUpWithOldMessages = true; - - if (queuedActions != null) { - for (HandleAction action : queuedActions) { - try { - action.execute(this); - } catch (Throwable e) { - e.printStackTrace(); - } - } - queuedActions.clear(); - queuedActions = null; - } - - // Continue to wait another timeout for new messages - continue; - } - } catch (TimeoutException e) { - if (returnOnTimeout) - return; - continue; - } catch (InvalidVersionException e) { - System.err.println("Ignoring error: " + e.getMessage()); - continue; - } - if (envelope.hasSource()) { - // Store uuid if we don't have it already - SignalServiceAddress source = envelope.getSourceAddress(); - resolveSignalServiceAddress(source); - } - if (!envelope.isReceipt()) { - try { - content = decryptMessage(envelope); - } catch (Exception e) { - exception = e; - } - List actions = handleMessage(envelope, content, ignoreAttachments); - if (hasCaughtUpWithOldMessages) { - for (HandleAction action : actions) { - try { - action.execute(this); - } catch (Throwable e) { - e.printStackTrace(); - } - } - } else { - if (queuedActions == null) { - queuedActions = new HashSet<>(); - } - queuedActions.addAll(actions); - } - } - account.save(); - if (!isMessageBlocked(envelope, content)) { - handler.handleMessage(envelope, content, exception); - } - if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) { - File cacheFile = null; - try { - String source = envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : ""; - cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp()); - Files.delete(cacheFile.toPath()); - // Try to delete directory if empty - new File(getMessageCachePath()).delete(); - } catch (IOException e) { - System.err.println("Failed to delete cached message file “" + cacheFile + "”: " + e.getMessage()); - } - } - } - } - public void receiveMessages( - long timeout, - TimeUnit unit, - boolean returnOnTimeout, - boolean ignoreAttachments, - ReceiveMessageHandler handler - ) throws IOException { - retryFailedReceivedMessages(handler, ignoreAttachments); - - Set queuedActions = null; - - getOrCreateMessagePipe(); - - boolean hasCaughtUpWithOldMessages = false; - - while (true) { - SignalServiceEnvelope envelope; - SignalServiceContent content = null; - Exception exception = null; - final long now = new Date().getTime(); - try { - Optional result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> { - // store message on disk, before acknowledging receipt to the server - try { - String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : ""; - File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp()); - Utils.storeEnvelope(envelope1, cacheFile); - } catch (IOException e) { - logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage()); - } - }); - if (result.isPresent()) { - envelope = result.get(); - } else { - // Received indicator that server queue is empty - hasCaughtUpWithOldMessages = true; - - if (queuedActions != null) { - for (HandleAction action : queuedActions) { - try { - action.execute(this); - } catch (Throwable e) { - e.printStackTrace(); - } - } - account.save(); - queuedActions.clear(); - queuedActions = null; - } - - // Continue to wait another timeout for new messages - continue; - } - } catch (TimeoutException e) { - if (returnOnTimeout) return; - continue; - } catch (InvalidVersionException e) { - logger.warn("Error while receiving messages, ignoring: {}", e.getMessage()); - continue; - } - - if (envelope.hasSource()) { - // Store uuid if we don't have it already - SignalServiceAddress source = envelope.getSourceAddress(); - resolveSignalServiceAddress(source); - } - if (!envelope.isReceipt()) { - try { - content = decryptMessage(envelope); - } catch (Exception e) { - exception = e; - } - List actions = handleMessage(envelope, content, ignoreAttachments); - if (hasCaughtUpWithOldMessages) { - for (HandleAction action : actions) { - try { - action.execute(this); - } catch (Throwable e) { - e.printStackTrace(); - } - } - } else { - if (queuedActions == null) { - queuedActions = new HashSet<>(); - } - queuedActions.addAll(actions); - } - } - account.save(); - if (!isMessageBlocked(envelope, content)) { - handler.handleMessage(envelope, content, exception); - } - if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) { - File cacheFile = null; - try { - String source = envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : ""; - cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp()); - Files.delete(cacheFile.toPath()); - // Try to delete directory if empty - getMessageCachePath().delete(); - } catch (IOException e) { - logger.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile, e.getMessage()); - } - } - } - } - - private boolean isMessageBlocked( - SignalServiceEnvelope envelope, SignalServiceContent content - ) { - SignalServiceAddress source; - if (!envelope.isUnidentifiedSender() && envelope.hasSource()) { - source = envelope.getSourceAddress(); - } else if (content != null) { - source = content.getSender(); - } else { - return false; - } - ContactInfo sourceContact = account.getContactStore().getContact(source); - if (sourceContact != null && sourceContact.blocked) { - return true; - } - - if (content != null && content.getDataMessage().isPresent()) { - SignalServiceDataMessage message = content.getDataMessage().get(); - if (message.getGroupContext().isPresent()) { - if (message.getGroupContext().get().getGroupV1().isPresent()) { - SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); - if (groupInfo.getType() != SignalServiceGroup.Type.DELIVER) { - return false; - } - } - GroupId groupId = GroupUtils.getGroupId(message.getGroupContext().get()); - GroupInfo group = account.getGroupStore().getGroup(groupId); - if (group != null && group.isBlocked()) { - return true; - } - } - } - return false; - } - - private List handleMessage( - SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments - ) { - List actions = new ArrayList<>(); - if (content != null) { - final SignalServiceAddress sender; - if (!envelope.isUnidentifiedSender() && envelope.hasSource()) { - sender = envelope.getSourceAddress(); - } else { - sender = content.getSender(); - } - // Store uuid if we don't have it already - resolveSignalServiceAddress(sender); - - if (content.getDataMessage().isPresent()) { - SignalServiceDataMessage message = content.getDataMessage().get(); - - if (content.isNeedsReceipt()) { - actions.add(new SendReceiptAction(sender, message.getTimestamp())); - } - - actions.addAll(handleSignalServiceDataMessage(message, - false, - sender, - account.getSelfAddress(), - ignoreAttachments)); - } - if (content.getSyncMessage().isPresent()) { - account.setMultiDevice(true); - SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); - if (syncMessage.getSent().isPresent()) { - SentTranscriptMessage message = syncMessage.getSent().get(); - final SignalServiceAddress destination = message.getDestination().orNull(); - actions.addAll(handleSignalServiceDataMessage(message.getMessage(), - true, - sender, - destination, - ignoreAttachments)); - } - if (syncMessage.getRequest().isPresent()) { - RequestMessage rm = syncMessage.getRequest().get(); - if (rm.isContactsRequest()) { - actions.add(SendSyncContactsAction.create()); - } - if (rm.isGroupsRequest()) { - actions.add(SendSyncGroupsAction.create()); - } - if (rm.isBlockedListRequest()) { - actions.add(SendSyncBlockedListAction.create()); - } - // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest(); - } - if (syncMessage.getGroups().isPresent()) { - File tmpFile = null; - try { - tmpFile = IOUtils.createTempFile(); - try (InputStream attachmentAsStream = retrieveAttachmentAsStream(syncMessage.getGroups() - .get() - .asPointer(), tmpFile)) { - DeviceGroupsInputStream s = new DeviceGroupsInputStream(attachmentAsStream); - DeviceGroup g; - while ((g = s.read()) != null) { - GroupInfoV1 syncGroup = account.getGroupStore() - .getOrCreateGroupV1(GroupId.v1(g.getId())); - if (syncGroup != null) { - if (g.getName().isPresent()) { - syncGroup.name = g.getName().get(); - } - syncGroup.addMembers(g.getMembers() - .stream() - .map(this::resolveSignalServiceAddress) - .collect(Collectors.toSet())); - if (!g.isActive()) { - syncGroup.removeMember(account.getSelfAddress()); - } else { - // Add ourself to the member set as it's marked as active - syncGroup.addMembers(Collections.singleton(account.getSelfAddress())); - } - syncGroup.blocked = g.isBlocked(); - if (g.getColor().isPresent()) { - syncGroup.color = g.getColor().get(); - } - - if (g.getAvatar().isPresent()) { - retrieveGroupAvatarAttachment(g.getAvatar().get(), syncGroup.getGroupId()); - } - syncGroup.inboxPosition = g.getInboxPosition().orNull(); - syncGroup.archived = g.isArchived(); - account.getGroupStore().updateGroup(syncGroup); - } - } - } - } catch (Exception e) { - logger.warn("Failed to handle received sync groups “{}”, ignoring: {}", - tmpFile, - e.getMessage()); - e.printStackTrace(); - } finally { - if (tmpFile != null) { - try { - Files.delete(tmpFile.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete received groups temp file “{}”, ignoring: {}", - tmpFile, - e.getMessage()); - } - } - } - } - if (syncMessage.getBlockedList().isPresent()) { - final BlockedListMessage blockedListMessage = syncMessage.getBlockedList().get(); - for (SignalServiceAddress address : blockedListMessage.getAddresses()) { - setContactBlocked(resolveSignalServiceAddress(address), true); - } - for (GroupId groupId : blockedListMessage.getGroupIds() - .stream() - .map(GroupId::unknownVersion) - .collect(Collectors.toSet())) { - try { - setGroupBlocked(groupId, true); - } catch (GroupNotFoundException e) { - logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}", - groupId.toBase64()); - } - } - } - if (syncMessage.getContacts().isPresent()) { - File tmpFile = null; - try { - tmpFile = IOUtils.createTempFile(); - final ContactsMessage contactsMessage = syncMessage.getContacts().get(); - try (InputStream attachmentAsStream = retrieveAttachmentAsStream(contactsMessage.getContactsStream() - .asPointer(), tmpFile)) { - DeviceContactsInputStream s = new DeviceContactsInputStream(attachmentAsStream); - if (contactsMessage.isComplete()) { - account.getContactStore().clear(); - } - DeviceContact c; - while ((c = s.read()) != null) { - if (c.getAddress().matches(account.getSelfAddress()) && c.getProfileKey().isPresent()) { - account.setProfileKey(c.getProfileKey().get()); - } - final SignalServiceAddress address = resolveSignalServiceAddress(c.getAddress()); - ContactInfo contact = account.getContactStore().getContact(address); - if (contact == null) { - contact = new ContactInfo(address); - } - if (c.getName().isPresent()) { - contact.name = c.getName().get(); - } - if (c.getColor().isPresent()) { - contact.color = c.getColor().get(); - } - if (c.getProfileKey().isPresent()) { - account.getProfileStore().storeProfileKey(address, c.getProfileKey().get()); - } - if (c.getVerified().isPresent()) { - final VerifiedMessage verifiedMessage = c.getVerified().get(); - account.getSignalProtocolStore() - .setIdentityTrustLevel(verifiedMessage.getDestination(), - verifiedMessage.getIdentityKey(), - TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); - } - if (c.getExpirationTimer().isPresent()) { - contact.messageExpirationTime = c.getExpirationTimer().get(); - } - contact.blocked = c.isBlocked(); - contact.inboxPosition = c.getInboxPosition().orNull(); - contact.archived = c.isArchived(); - account.getContactStore().updateContact(contact); - - if (c.getAvatar().isPresent()) { - retrieveContactAvatarAttachment(c.getAvatar().get(), contact.number); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (tmpFile != null) { - try { - Files.delete(tmpFile.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete received contacts temp file “{}”, ignoring: {}", - tmpFile, - e.getMessage()); - } - } - } - } - if (syncMessage.getVerified().isPresent()) { - final VerifiedMessage verifiedMessage = syncMessage.getVerified().get(); - account.getSignalProtocolStore() - .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()), - verifiedMessage.getIdentityKey(), - TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); - } - if (syncMessage.getStickerPackOperations().isPresent()) { - final List stickerPackOperationMessages = syncMessage.getStickerPackOperations() - .get(); - for (StickerPackOperationMessage m : stickerPackOperationMessages) { - if (!m.getPackId().isPresent()) { - continue; - } - Sticker sticker = account.getStickerStore().getSticker(m.getPackId().get()); - if (sticker == null) { - if (!m.getPackKey().isPresent()) { - continue; - } - sticker = new Sticker(m.getPackId().get(), m.getPackKey().get()); - } - sticker.setInstalled(!m.getType().isPresent() - || m.getType().get() == StickerPackOperationMessage.Type.INSTALL); - account.getStickerStore().updateSticker(sticker); - } - } - if (syncMessage.getConfiguration().isPresent()) { - // TODO - } - } - } - return actions; - } - - private File getContactAvatarFile(String number) { - return new File(pathConfig.getAvatarsPath(), "contact-" + number); - } - - private File retrieveContactAvatarAttachment( - SignalServiceAttachment attachment, String number - ) throws IOException, InvalidMessageException, MissingConfigurationException { - IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); - if (attachment.isPointer()) { - SignalServiceAttachmentPointer pointer = attachment.asPointer(); - return retrieveAttachment(pointer, getContactAvatarFile(number), false); - } else { - SignalServiceAttachmentStream stream = attachment.asStream(); - return Utils.retrieveAttachment(stream, getContactAvatarFile(number)); - } - } - - private File getGroupAvatarFile(GroupId groupId) { - return new File(pathConfig.getAvatarsPath(), "group-" + groupId.toBase64().replace("/", "_")); - } - - private File retrieveGroupAvatarAttachment( - SignalServiceAttachment attachment, GroupId groupId - ) throws IOException, InvalidMessageException, MissingConfigurationException { - IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); - if (attachment.isPointer()) { - SignalServiceAttachmentPointer pointer = attachment.asPointer(); - return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false); - } else { - SignalServiceAttachmentStream stream = attachment.asStream(); - return Utils.retrieveAttachment(stream, getGroupAvatarFile(groupId)); - } - } - - private File retrieveGroupAvatar( - GroupId groupId, GroupSecretParams groupSecretParams, String cdnKey - ) throws IOException { - IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); - SignalServiceMessageReceiver receiver = getOrCreateMessageReceiver(); - File outputFile = getGroupAvatarFile(groupId); - GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - - File tmpFile = IOUtils.createTempFile(); - tmpFile.deleteOnExit(); - try (InputStream input = receiver.retrieveGroupsV2ProfileAvatar(cdnKey, - tmpFile, - ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) { - byte[] encryptedData = IOUtils.readFully(input); - - byte[] decryptedData = groupOperations.decryptAvatar(encryptedData); - try (OutputStream output = new FileOutputStream(outputFile)) { - output.write(decryptedData); - } - } finally { - try { - Files.delete(tmpFile.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}", - tmpFile, - e.getMessage()); - } - } - return outputFile; - } - - private File getProfileAvatarFile(SignalServiceAddress address) { - return new File(pathConfig.getAvatarsPath(), "profile-" + address.getLegacyIdentifier()); - } - - private File retrieveProfileAvatar( - SignalServiceAddress address, String avatarPath, ProfileKey profileKey - ) throws IOException { - IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); - SignalServiceMessageReceiver receiver = getOrCreateMessageReceiver(); - File outputFile = getProfileAvatarFile(address); - - File tmpFile = IOUtils.createTempFile(); - try (InputStream input = receiver.retrieveProfileAvatar(avatarPath, - tmpFile, - profileKey, - ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) { - // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ... - IOUtils.copyStreamToFile(input, outputFile, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE); - } finally { - try { - Files.delete(tmpFile.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}", - tmpFile, - e.getMessage()); - } - } - return outputFile; - } - - public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) { - return new File(pathConfig.getAttachmentsPath(), attachmentId.toString()); - } - - private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException, MissingConfigurationException { - IOUtils.createPrivateDirectories(pathConfig.getAttachmentsPath()); - return retrieveAttachment(pointer, getAttachmentFile(pointer.getRemoteId()), true); - } - - private File retrieveAttachment( - SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview - ) throws IOException, InvalidMessageException, MissingConfigurationException { - if (storePreview && pointer.getPreview().isPresent()) { - File previewFile = new File(outputFile + ".preview"); - try (OutputStream output = new FileOutputStream(previewFile)) { - byte[] preview = pointer.getPreview().get(); - output.write(preview, 0, preview.length); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; - } - } - - final SignalServiceMessageReceiver messageReceiver = getOrCreateMessageReceiver(); - - File tmpFile = IOUtils.createTempFile(); - try (InputStream input = messageReceiver.retrieveAttachment(pointer, - tmpFile, - ServiceConfig.MAX_ATTACHMENT_SIZE)) { - IOUtils.copyStreamToFile(input, outputFile); - } finally { - try { - Files.delete(tmpFile.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete received attachment temp file “{}”, ignoring: {}", - tmpFile, - e.getMessage()); - } - } - return outputFile; - } - - private InputStream retrieveAttachmentAsStream( - SignalServiceAttachmentPointer pointer, File tmpFile - ) throws IOException, InvalidMessageException, MissingConfigurationException { - final SignalServiceMessageReceiver messageReceiver = getOrCreateMessageReceiver(); - return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE); - } - - void sendGroups() throws IOException, UntrustedIdentityException { - File groupsFile = IOUtils.createTempFile(); - - try { - try (OutputStream fos = new FileOutputStream(groupsFile)) { - DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos); - for (GroupInfo record : account.getGroupStore().getGroups()) { - if (record instanceof GroupInfoV1) { - GroupInfoV1 groupInfo = (GroupInfoV1) record; - out.write(new DeviceGroup(groupInfo.getGroupId().serialize(), - Optional.fromNullable(groupInfo.name), - new ArrayList<>(groupInfo.getMembers()), - createGroupAvatarAttachment(groupInfo.getGroupId()), - groupInfo.isMember(account.getSelfAddress()), - Optional.of(groupInfo.messageExpirationTime), - Optional.fromNullable(groupInfo.color), - groupInfo.blocked, - Optional.fromNullable(groupInfo.inboxPosition), - groupInfo.archived)); - } - } - } - - if (groupsFile.exists() && groupsFile.length() > 0) { - try (FileInputStream groupsFileStream = new FileInputStream(groupsFile)) { - SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder() - .withStream(groupsFileStream) - .withContentType("application/octet-stream") - .withLength(groupsFile.length()) - .build(); - - sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); - } - } - } finally { - try { - Files.delete(groupsFile.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile, e.getMessage()); - } - } - } - - public void sendContacts() throws IOException, UntrustedIdentityException { - File contactsFile = IOUtils.createTempFile(); - - try { - try (OutputStream fos = new FileOutputStream(contactsFile)) { - DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos); - for (ContactInfo record : account.getContactStore().getContacts()) { - VerifiedMessage verifiedMessage = null; - JsonIdentityKeyStore.Identity currentIdentity = account.getSignalProtocolStore() - .getIdentity(record.getAddress()); - if (currentIdentity != null) { - verifiedMessage = new VerifiedMessage(record.getAddress(), - currentIdentity.getIdentityKey(), - currentIdentity.getTrustLevel().toVerifiedState(), - currentIdentity.getDateAdded().getTime()); - } - - ProfileKey profileKey = account.getProfileStore().getProfileKey(record.getAddress()); - out.write(new DeviceContact(record.getAddress(), - Optional.fromNullable(record.name), - createContactAvatarAttachment(record.number), - Optional.fromNullable(record.color), - Optional.fromNullable(verifiedMessage), - Optional.fromNullable(profileKey), - record.blocked, - Optional.of(record.messageExpirationTime), - Optional.fromNullable(record.inboxPosition), - record.archived)); - } - - if (account.getProfileKey() != null) { - // Send our own profile key as well - out.write(new DeviceContact(account.getSelfAddress(), - Optional.absent(), - Optional.absent(), - Optional.absent(), - Optional.absent(), - Optional.of(account.getProfileKey()), - false, - Optional.absent(), - Optional.absent(), - false)); - } - } - - if (contactsFile.exists() && contactsFile.length() > 0) { - try (FileInputStream contactsFileStream = new FileInputStream(contactsFile)) { - SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder() - .withStream(contactsFileStream) - .withContentType("application/octet-stream") - .withLength(contactsFile.length()) - .build(); - - sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, true))); - } - } - } finally { - try { - Files.delete(contactsFile.toPath()); - } catch (IOException e) { - logger.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile, e.getMessage()); - } - } - } - - void sendBlockedList() throws IOException, UntrustedIdentityException { - List addresses = new ArrayList<>(); - for (ContactInfo record : account.getContactStore().getContacts()) { - if (record.blocked) { - addresses.add(record.getAddress()); - } - } - List groupIds = new ArrayList<>(); - for (GroupInfo record : account.getGroupStore().getGroups()) { - if (record.isBlocked()) { - groupIds.add(record.getGroupId().serialize()); - } - } - sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds))); - } - - private void sendVerifiedMessage( - SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel - ) throws IOException, UntrustedIdentityException { - VerifiedMessage verifiedMessage = new VerifiedMessage(destination, - identityKey, - trustLevel.toVerifiedState(), - System.currentTimeMillis()); - sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); - } - - public List getContacts() { - return account.getContactStore().getContacts(); - } - - public ContactInfo getContact(String number) { - return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number)); - } - - public GroupInfo getGroup(GroupId groupId) { - return account.getGroupStore().getGroup(groupId); - } - - public List getIdentities() { - return account.getSignalProtocolStore().getIdentities(); - } - - public List getIdentities(String number) throws InvalidNumberException { - return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number)); - } - - /** - * Trust this the identity with this fingerprint - * - * @param name username of the identity - * @param fingerprint Fingerprint - */ - public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException { - SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name); - List ids = account.getSignalProtocolStore().getIdentities(address); - if (ids == null) { - return false; - } - for (JsonIdentityKeyStore.Identity id : ids) { - if (!Arrays.equals(id.getIdentityKey().serialize(), fingerprint)) { - continue; - } - - account.getSignalProtocolStore() - .setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); - try { - sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); - } catch (IOException | UntrustedIdentityException e) { - e.printStackTrace(); - } - account.save(); - return true; - } - return false; - } - - /** - * Trust this the identity with this safety number - * - * @param name username of the identity - * @param safetyNumber Safety number - */ - public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException { - SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name); - List ids = account.getSignalProtocolStore().getIdentities(address); - if (ids == null) { - return false; - } - for (JsonIdentityKeyStore.Identity id : ids) { - if (!safetyNumber.equals(computeSafetyNumber(address, id.getIdentityKey()))) { - continue; - } - - account.getSignalProtocolStore() - .setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); - try { - sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); - } catch (IOException | UntrustedIdentityException e) { - e.printStackTrace(); - } - account.save(); - return true; - } - return false; - } - - /** - * Trust all keys of this identity without verification - * - * @param name username of the identity - */ - public boolean trustIdentityAllKeys(String name) { - SignalServiceAddress address = resolveSignalServiceAddress(name); - List ids = account.getSignalProtocolStore().getIdentities(address); - if (ids == null) { - return false; - } - for (JsonIdentityKeyStore.Identity id : ids) { - if (id.getTrustLevel() == TrustLevel.UNTRUSTED) { - account.getSignalProtocolStore() - .setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); - try { - sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); - } catch (IOException | UntrustedIdentityException e) { - e.printStackTrace(); - } - } - } - account.save(); - return true; - } - - public String computeSafetyNumber( - SignalServiceAddress theirAddress, IdentityKey theirIdentityKey - ) { - return Utils.computeSafetyNumber(account.getSelfAddress(), - getIdentityKeyPair().getPublicKey(), - theirAddress, - theirIdentityKey); - } - - void saveAccount() { - account.save(); - } - - public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException { - String canonicalizedNumber = UuidUtil.isUuid(identifier) - ? identifier - : Util.canonicalizeNumber(identifier, account.getUsername()); - return resolveSignalServiceAddress(canonicalizedNumber); - } - - public SignalServiceAddress resolveSignalServiceAddress(String identifier) { - SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier); - - return resolveSignalServiceAddress(address); - } - - public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) { - if (address.matches(account.getSelfAddress())) { - return account.getSelfAddress(); - } - - return account.getRecipientStore().resolveServiceAddress(address); - } - - @Override - public void close() throws IOException { - if (messagePipe != null) { - messagePipe.shutdown(); - messagePipe = null; - } - - if (unidentifiedMessagePipe != null) { - unidentifiedMessagePipe.shutdown(); - unidentifiedMessagePipe = null; - } - - account.close(); - } - - public interface ReceiveMessageHandler { - - void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent decryptedContent, Throwable e); - } -} diff --git a/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java b/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java deleted file mode 100644 index 2c9b3f33..00000000 --- a/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager; - -public class NotAGroupMemberException extends Exception { - - public NotAGroupMemberException(GroupId groupId, String groupName) { - super("User is not a member in group: " + groupName + " (" + groupId.toBase64() + ")"); - } -} diff --git a/src/main/java/org/asamk/signal/manager/PathConfig.java b/src/main/java/org/asamk/signal/manager/PathConfig.java deleted file mode 100644 index d96034df..00000000 --- a/src/main/java/org/asamk/signal/manager/PathConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.asamk.signal.manager; - -import java.io.File; - -public class PathConfig { - - private final File dataPath; - private final File attachmentsPath; - private final File avatarsPath; - - public static PathConfig createDefault(final File settingsPath) { - return new PathConfig(new File(settingsPath, "data"), - new File(settingsPath, "attachments"), - new File(settingsPath, "avatars")); - } - - private PathConfig(final File dataPath, final File attachmentsPath, final File avatarsPath) { - this.dataPath = dataPath; - this.attachmentsPath = attachmentsPath; - this.avatarsPath = avatarsPath; - } - - public File getDataPath() { - return dataPath; - } - - public File getAttachmentsPath() { - return attachmentsPath; - } - - public File getAvatarsPath() { - return avatarsPath; - } -} diff --git a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java deleted file mode 100644 index f81cfa49..00000000 --- a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - Copyright (C) 2015-2020 AsamK and contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -package org.asamk.signal.manager; - -import org.asamk.signal.storage.SignalAccount; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.profiles.ProfileKey; -import org.whispersystems.libsignal.IdentityKeyPair; -import org.whispersystems.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.util.KeyHelper; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.SleepTimer; -import org.whispersystems.signalservice.api.util.UptimeSleepTimer; -import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; -import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeoutException; - -public class ProvisioningManager { - - private final PathConfig pathConfig; - private final SignalServiceConfiguration serviceConfiguration; - private final String userAgent; - - private final SignalServiceAccountManager accountManager; - private final IdentityKeyPair identityKey; - private final int registrationId; - private final String password; - - public ProvisioningManager(File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) { - this.pathConfig = PathConfig.createDefault(settingsPath); - this.serviceConfiguration = serviceConfiguration; - this.userAgent = userAgent; - - identityKey = KeyHelper.generateIdentityKeyPair(); - registrationId = KeyHelper.generateRegistrationId(false); - password = KeyUtils.createPassword(); - final SleepTimer timer = new UptimeSleepTimer(); - GroupsV2Operations groupsV2Operations; - try { - groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceConfiguration)); - } catch (Throwable ignored) { - groupsV2Operations = null; - } - accountManager = new SignalServiceAccountManager(serviceConfiguration, - new DynamicCredentialsProvider(null, null, password, null, SignalServiceAddress.DEFAULT_DEVICE_ID), - userAgent, - groupsV2Operations, - timer); - } - - public String getDeviceLinkUri() throws TimeoutException, IOException { - String deviceUuid = accountManager.getNewDeviceUuid(); - - return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(deviceUuid, - identityKey.getPublicKey().getPublicKey())); - } - - public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists { - String signalingKey = KeyUtils.createSignalingKey(); - SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration( - identityKey, - signalingKey, - false, - true, - registrationId, - deviceName); - - String username = ret.getNumber(); - // TODO do this check before actually registering - if (SignalAccount.userExists(pathConfig.getDataPath(), username)) { - throw new UserAlreadyExists(username, SignalAccount.getFileName(pathConfig.getDataPath(), username)); - } - - // Create new account with the synced identity - byte[] profileKeyBytes = ret.getProfileKey(); - ProfileKey profileKey; - if (profileKeyBytes == null) { - profileKey = KeyUtils.createProfileKey(); - } else { - try { - profileKey = new ProfileKey(profileKeyBytes); - } catch (InvalidInputException e) { - throw new IOException("Received invalid profileKey", e); - } - } - - try (SignalAccount account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(), - username, - ret.getUuid(), - password, - ret.getDeviceId(), - ret.getIdentity(), - registrationId, - signalingKey, - profileKey)) { - account.save(); - - try (Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent)) { - - m.refreshPreKeys(); - - m.requestSyncGroups(); - m.requestSyncContacts(); - m.requestSyncBlocked(); - m.requestSyncConfiguration(); - - m.saveAccount(); - } - } - - return username; - } -} diff --git a/src/main/java/org/asamk/signal/manager/ServiceConfig.java b/src/main/java/org/asamk/signal/manager/ServiceConfig.java deleted file mode 100644 index 353670ae..00000000 --- a/src/main/java/org/asamk/signal/manager/ServiceConfig.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.asamk.signal.manager; - -import org.signal.zkgroup.ServerPublicParams; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.account.AccountAttributes; -import org.whispersystems.signalservice.api.push.TrustStore; -import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; -import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl; -import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl; -import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; -import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl; -import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl; -import org.whispersystems.util.Base64; - -import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import okhttp3.Dns; -import okhttp3.Interceptor; - -public class ServiceConfig { - - final static String UNIDENTIFIED_SENDER_TRUST_ROOT = "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"; - final static int PREKEY_MINIMUM_COUNT = 20; - final static int PREKEY_BATCH_SIZE = 100; - final static int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024; - final static int MAX_ENVELOPE_SIZE = 0; - final static long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = 10 * 1024 * 1024; - - final static String CDS_MRENCLAVE = "c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15"; - - private final static String URL = "https://textsecure-service.whispersystems.org"; - private final static String CDN_URL = "https://cdn.signal.org"; - private final static String CDN2_URL = "https://cdn2.signal.org"; - private final static String SIGNAL_CONTACT_DISCOVERY_URL = "https://api.directory.signal.org"; - private final static String SIGNAL_KEY_BACKUP_URL = "https://api.backup.signal.org"; - private final static String STORAGE_URL = "https://storage.signal.org"; - private final static TrustStore TRUST_STORE = new WhisperTrustStore(); - private final static TrustStore IAS_TRUST_STORE = new IasTrustStore(); - - private final static Optional dns = Optional.absent(); - - private final static String zkGroupServerPublicParamsHex = "AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0="; - private final static byte[] zkGroupServerPublicParams; - - static final AccountAttributes.Capabilities capabilities; - - static { - try { - zkGroupServerPublicParams = Base64.decode(zkGroupServerPublicParamsHex); - } catch (IOException e) { - throw new AssertionError(e); - } - - boolean zkGroupAvailable; - try { - new ServerPublicParams(zkGroupServerPublicParams); - zkGroupAvailable = true; - } catch (Throwable ignored) { - zkGroupAvailable = false; - } - capabilities = new AccountAttributes.Capabilities(false, zkGroupAvailable, false, zkGroupAvailable); - } - - public static SignalServiceConfiguration createDefaultServiceConfiguration(String userAgent) { - final Interceptor userAgentInterceptor = chain -> chain.proceed(chain.request() - .newBuilder() - .header("User-Agent", userAgent) - .build()); - - final List interceptors = Collections.singletonList(userAgentInterceptor); - - return new SignalServiceConfiguration(new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}, - makeSignalCdnUrlMapFor(new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)}, - new SignalCdnUrl[]{new SignalCdnUrl(CDN2_URL, TRUST_STORE)}), - new SignalContactDiscoveryUrl[]{new SignalContactDiscoveryUrl(SIGNAL_CONTACT_DISCOVERY_URL, - TRUST_STORE)}, - new SignalKeyBackupServiceUrl[]{new SignalKeyBackupServiceUrl(SIGNAL_KEY_BACKUP_URL, TRUST_STORE)}, - new SignalStorageUrl[]{new SignalStorageUrl(STORAGE_URL, TRUST_STORE)}, - interceptors, - dns, - zkGroupServerPublicParams); - } - - public static AccountAttributes.Capabilities getCapabilities() { - return capabilities; - } - - static KeyStore getIasKeyStore() { - try { - TrustStore contactTrustStore = IAS_TRUST_STORE; - - KeyStore keyStore = KeyStore.getInstance("BKS"); - keyStore.load(contactTrustStore.getKeyStoreInputStream(), - contactTrustStore.getKeyStorePassword().toCharArray()); - - return keyStore; - } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - - private static Map makeSignalCdnUrlMapFor( - SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls - ) { - return Map.of(0, cdn0Urls, 2, cdn2Urls); - } - - private ServiceConfig() { - } -} diff --git a/src/main/java/org/asamk/signal/manager/StickerPackInvalidException.java b/src/main/java/org/asamk/signal/manager/StickerPackInvalidException.java deleted file mode 100644 index 52869acd..00000000 --- a/src/main/java/org/asamk/signal/manager/StickerPackInvalidException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager; - -public class StickerPackInvalidException extends Exception { - - public StickerPackInvalidException(String message) { - super(message); - } -} diff --git a/src/main/java/org/asamk/signal/manager/TrustLevel.java b/src/main/java/org/asamk/signal/manager/TrustLevel.java deleted file mode 100644 index c9fa7a5e..00000000 --- a/src/main/java/org/asamk/signal/manager/TrustLevel.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.asamk.signal.manager; - -import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; - -public enum TrustLevel { - UNTRUSTED, - TRUSTED_UNVERIFIED, - TRUSTED_VERIFIED; - - private static TrustLevel[] cachedValues = null; - - public static TrustLevel fromInt(int i) { - if (TrustLevel.cachedValues == null) { - TrustLevel.cachedValues = TrustLevel.values(); - } - return TrustLevel.cachedValues[i]; - } - - public static TrustLevel fromVerifiedState(VerifiedMessage.VerifiedState verifiedState) { - switch (verifiedState) { - case DEFAULT: - return TRUSTED_UNVERIFIED; - case UNVERIFIED: - return UNTRUSTED; - case VERIFIED: - return TRUSTED_VERIFIED; - } - throw new RuntimeException("Unknown verified state: " + verifiedState); - } - - public VerifiedMessage.VerifiedState toVerifiedState() { - switch (this) { - case TRUSTED_UNVERIFIED: - return VerifiedMessage.VerifiedState.DEFAULT; - case UNTRUSTED: - return VerifiedMessage.VerifiedState.UNVERIFIED; - case TRUSTED_VERIFIED: - return VerifiedMessage.VerifiedState.VERIFIED; - } - throw new RuntimeException("Unknown verified state: " + this); - } -} diff --git a/src/main/java/org/asamk/signal/manager/UserAlreadyExists.java b/src/main/java/org/asamk/signal/manager/UserAlreadyExists.java deleted file mode 100644 index d506f0c6..00000000 --- a/src/main/java/org/asamk/signal/manager/UserAlreadyExists.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.asamk.signal.manager; - -import java.io.File; - -public class UserAlreadyExists extends Exception { - - private final String username; - private final File fileName; - - public UserAlreadyExists(String username, File fileName) { - this.username = username; - this.fileName = fileName; - } - - public String getUsername() { - return username; - } - - public File getFileName() { - return fileName; - } -} diff --git a/src/main/java/org/asamk/signal/manager/Utils.java b/src/main/java/org/asamk/signal/manager/Utils.java deleted file mode 100644 index 0a815ea9..00000000 --- a/src/main/java/org/asamk/signal/manager/Utils.java +++ /dev/null @@ -1,304 +0,0 @@ -package org.asamk.signal.manager; - -import org.signal.libsignal.metadata.certificate.CertificateValidator; -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.ecc.Curve; -import org.whispersystems.libsignal.ecc.ECPublicKey; -import org.whispersystems.libsignal.fingerprint.Fingerprint; -import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.StreamDetails; -import org.whispersystems.signalservice.api.util.UuidUtil; -import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec; -import org.whispersystems.util.Base64; - -import java.io.BufferedInputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.net.URLConnection; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import static org.whispersystems.signalservice.internal.util.Util.isEmpty; - -class Utils { - - static List getSignalServiceAttachments(List attachments) throws AttachmentInvalidException { - List signalServiceAttachments = null; - if (attachments != null) { - signalServiceAttachments = new ArrayList<>(attachments.size()); - for (String attachment : attachments) { - try { - signalServiceAttachments.add(createAttachment(new File(attachment))); - } catch (IOException e) { - throw new AttachmentInvalidException(attachment, e); - } - } - } - return signalServiceAttachments; - } - - static String getFileMimeType(File file, String defaultMimeType) throws IOException { - String mime = Files.probeContentType(file.toPath()); - if (mime == null) { - try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) { - mime = URLConnection.guessContentTypeFromStream(bufferedStream); - } - } - if (mime == null) { - return defaultMimeType; - } - return mime; - } - - static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException { - InputStream attachmentStream = new FileInputStream(attachmentFile); - final long attachmentSize = attachmentFile.length(); - final String mime = getFileMimeType(attachmentFile, "application/octet-stream"); - // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option - final long uploadTimestamp = System.currentTimeMillis(); - Optional preview = Optional.absent(); - Optional caption = Optional.absent(); - Optional blurHash = Optional.absent(); - final Optional resumableUploadSpec = Optional.absent(); - return new SignalServiceAttachmentStream(attachmentStream, - mime, - attachmentSize, - Optional.of(attachmentFile.getName()), - false, - false, - preview, - 0, - 0, - uploadTimestamp, - caption, - blurHash, - null, - null, - resumableUploadSpec); - } - - static StreamDetails createStreamDetailsFromFile(File file) throws IOException { - InputStream stream = new FileInputStream(file); - final long size = file.length(); - String mime = Files.probeContentType(file.toPath()); - if (mime == null) { - mime = "application/octet-stream"; - } - return new StreamDetails(stream, mime, size); - } - - static CertificateValidator getCertificateValidator() { - try { - ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(ServiceConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), - 0); - return new CertificateValidator(unidentifiedSenderTrustRoot); - } catch (InvalidKeyException | IOException e) { - throw new AssertionError(e); - } - } - - private static Map getQueryMap(String query) { - String[] params = query.split("&"); - Map map = new HashMap<>(); - for (String param : params) { - final String[] paramParts = param.split("="); - String name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8); - String value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8); - map.put(name, value); - } - return map; - } - - static String createDeviceLinkUri(DeviceLinkInfo info) { - return "tsdevice:/?uuid=" - + URLEncoder.encode(info.deviceIdentifier, StandardCharsets.UTF_8) - + "&pub_key=" - + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), - StandardCharsets.UTF_8); - } - - static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException { - Map query = getQueryMap(linkUri.getRawQuery()); - String deviceIdentifier = query.get("uuid"); - String publicKeyEncoded = query.get("pub_key"); - - if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) { - throw new RuntimeException("Invalid device link uri"); - } - - ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0); - - return new DeviceLinkInfo(deviceIdentifier, deviceKey); - } - - static SignalServiceEnvelope loadEnvelope(File file) throws IOException { - try (FileInputStream f = new FileInputStream(file)) { - DataInputStream in = new DataInputStream(f); - int version = in.readInt(); - if (version > 4) { - return null; - } - int type = in.readInt(); - String source = in.readUTF(); - UUID sourceUuid = null; - if (version >= 3) { - sourceUuid = UuidUtil.parseOrNull(in.readUTF()); - } - int sourceDevice = in.readInt(); - if (version == 1) { - // read legacy relay field - in.readUTF(); - } - long timestamp = in.readLong(); - byte[] content = null; - int contentLen = in.readInt(); - if (contentLen > 0) { - content = new byte[contentLen]; - in.readFully(content); - } - byte[] legacyMessage = null; - int legacyMessageLen = in.readInt(); - if (legacyMessageLen > 0) { - legacyMessage = new byte[legacyMessageLen]; - in.readFully(legacyMessage); - } - long serverReceivedTimestamp = 0; - String uuid = null; - if (version >= 2) { - serverReceivedTimestamp = in.readLong(); - uuid = in.readUTF(); - if ("".equals(uuid)) { - uuid = null; - } - } - long serverDeliveredTimestamp = 0; - if (version >= 4) { - serverDeliveredTimestamp = in.readLong(); - } - Optional addressOptional = sourceUuid == null && source.isEmpty() - ? Optional.absent() - : Optional.of(new SignalServiceAddress(sourceUuid, source)); - return new SignalServiceEnvelope(type, - addressOptional, - sourceDevice, - timestamp, - legacyMessage, - content, - serverReceivedTimestamp, - serverDeliveredTimestamp, - uuid); - } - } - - static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException { - try (FileOutputStream f = new FileOutputStream(file)) { - try (DataOutputStream out = new DataOutputStream(f)) { - out.writeInt(4); // version - out.writeInt(envelope.getType()); - out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : ""); - out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : ""); - out.writeInt(envelope.getSourceDevice()); - out.writeLong(envelope.getTimestamp()); - if (envelope.hasContent()) { - out.writeInt(envelope.getContent().length); - out.write(envelope.getContent()); - } else { - out.writeInt(0); - } - if (envelope.hasLegacyMessage()) { - out.writeInt(envelope.getLegacyMessage().length); - out.write(envelope.getLegacyMessage()); - } else { - out.writeInt(0); - } - out.writeLong(envelope.getServerReceivedTimestamp()); - String uuid = envelope.getUuid(); - out.writeUTF(uuid == null ? "" : uuid); - out.writeLong(envelope.getServerDeliveredTimestamp()); - } - } - } - - static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException { - InputStream input = stream.getInputStream(); - - try (OutputStream output = new FileOutputStream(outputFile)) { - byte[] buffer = new byte[4096]; - int read; - - while ((read = input.read(buffer)) != -1) { - output.write(buffer, 0, read); - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; - } - return outputFile; - } - - static String computeSafetyNumber( - SignalServiceAddress ownAddress, - IdentityKey ownIdentityKey, - SignalServiceAddress theirAddress, - IdentityKey theirIdentityKey - ) { - int version; - byte[] ownId; - byte[] theirId; - - if (ServiceConfig.capabilities.isUuid() && ownAddress.getUuid().isPresent() && theirAddress.getUuid() - .isPresent()) { - // Version 2: UUID user - version = 2; - ownId = UuidUtil.toByteArray(ownAddress.getUuid().get()); - theirId = UuidUtil.toByteArray(theirAddress.getUuid().get()); - } else { - // Version 1: E164 user - version = 1; - if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) { - return "INVALID ID"; - } - ownId = ownAddress.getNumber().get().getBytes(); - theirId = theirAddress.getNumber().get().getBytes(); - } - - Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version, - ownId, - ownIdentityKey, - theirId, - theirIdentityKey); - return fingerprint.getDisplayableFingerprint().getDisplayText(); - } - - static class DeviceLinkInfo { - - final String deviceIdentifier; - final ECPublicKey deviceKey; - - DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) { - this.deviceIdentifier = deviceIdentifier; - this.deviceKey = deviceKey; - } - } -} diff --git a/src/main/java/org/asamk/signal/manager/WhisperTrustStore.java b/src/main/java/org/asamk/signal/manager/WhisperTrustStore.java deleted file mode 100644 index 185ab599..00000000 --- a/src/main/java/org/asamk/signal/manager/WhisperTrustStore.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.asamk.signal.manager; - -import org.whispersystems.signalservice.api.push.TrustStore; - -import java.io.InputStream; - -class WhisperTrustStore implements TrustStore { - - @Override - public InputStream getKeyStoreInputStream() { - return WhisperTrustStore.class.getResourceAsStream("whisper.store"); - } - - @Override - public String getKeyStorePassword() { - return "whisper"; - } -} diff --git a/src/main/java/org/asamk/signal/manager/helper/GroupAuthorizationProvider.java b/src/main/java/org/asamk/signal/manager/helper/GroupAuthorizationProvider.java deleted file mode 100644 index d26ebb06..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/GroupAuthorizationProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.signal.zkgroup.groups.GroupSecretParams; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; - -import java.io.IOException; - -public interface GroupAuthorizationProvider { - - GroupsV2AuthorizationString getAuthorizationForToday(GroupSecretParams groupSecretParams) throws IOException; -} diff --git a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java deleted file mode 100644 index 5a88bc66..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ /dev/null @@ -1,398 +0,0 @@ -package org.asamk.signal.manager.helper; - -import com.google.protobuf.InvalidProtocolBufferException; - -import org.asamk.signal.manager.GroupIdV2; -import org.asamk.signal.manager.GroupLinkPassword; -import org.asamk.signal.manager.GroupUtils; -import org.asamk.signal.storage.groups.GroupInfoV2; -import org.asamk.signal.storage.profiles.SignalProfile; -import org.asamk.signal.util.IOUtils; -import org.signal.storageservice.protos.groups.AccessControl; -import org.signal.storageservice.protos.groups.GroupChange; -import org.signal.storageservice.protos.groups.Member; -import org.signal.storageservice.protos.groups.local.DecryptedGroup; -import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; -import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; -import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.VerificationFailedException; -import org.signal.zkgroup.groups.GroupMasterKey; -import org.signal.zkgroup.groups.GroupSecretParams; -import org.signal.zkgroup.groups.UuidCiphertext; -import org.signal.zkgroup.profiles.ProfileKeyCredential; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.util.Pair; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; -import org.whispersystems.signalservice.api.groupsv2.GroupCandidate; -import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; -import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException; -import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -public class GroupHelper { - - final static Logger logger = LoggerFactory.getLogger(GroupHelper.class); - - private final ProfileKeyCredentialProvider profileKeyCredentialProvider; - - private final ProfileProvider profileProvider; - - private final SelfAddressProvider selfAddressProvider; - - private final GroupsV2Operations groupsV2Operations; - - private final GroupsV2Api groupsV2Api; - - private final GroupAuthorizationProvider groupAuthorizationProvider; - - public GroupHelper( - final ProfileKeyCredentialProvider profileKeyCredentialProvider, - final ProfileProvider profileProvider, - final SelfAddressProvider selfAddressProvider, - final GroupsV2Operations groupsV2Operations, - final GroupsV2Api groupsV2Api, - final GroupAuthorizationProvider groupAuthorizationProvider - ) { - this.profileKeyCredentialProvider = profileKeyCredentialProvider; - this.profileProvider = profileProvider; - this.selfAddressProvider = selfAddressProvider; - this.groupsV2Operations = groupsV2Operations; - this.groupsV2Api = groupsV2Api; - this.groupAuthorizationProvider = groupAuthorizationProvider; - } - - public DecryptedGroup getDecryptedGroup(final GroupSecretParams groupSecretParams) { - try { - final GroupsV2AuthorizationString groupsV2AuthorizationString = groupAuthorizationProvider.getAuthorizationForToday( - groupSecretParams); - return groupsV2Api.getGroup(groupSecretParams, groupsV2AuthorizationString); - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { - logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage()); - return null; - } - } - - public DecryptedGroupJoinInfo getDecryptedGroupJoinInfo( - GroupMasterKey groupMasterKey, GroupLinkPassword password - ) throws IOException, GroupLinkNotActiveException { - GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - - return groupsV2Api.getGroupJoinInfo(groupSecretParams, - Optional.fromNullable(password).transform(GroupLinkPassword::serialize), - groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams)); - } - - public GroupInfoV2 createGroupV2( - String name, Collection members, String avatarFile - ) throws IOException { - final byte[] avatarBytes = readAvatarBytes(avatarFile); - final GroupsV2Operations.NewGroup newGroup = buildNewGroupV2(name, members, avatarBytes); - if (newGroup == null) { - return null; - } - - final GroupSecretParams groupSecretParams = newGroup.getGroupSecretParams(); - - final GroupsV2AuthorizationString groupAuthForToday; - final DecryptedGroup decryptedGroup; - try { - groupAuthForToday = groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams); - groupsV2Api.putNewGroup(newGroup, groupAuthForToday); - decryptedGroup = groupsV2Api.getGroup(groupSecretParams, groupAuthForToday); - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { - logger.warn("Failed to create V2 group: {}", e.getMessage()); - return null; - } - if (decryptedGroup == null) { - logger.warn("Failed to create V2 group, unknown error!"); - return null; - } - - final GroupIdV2 groupId = GroupUtils.getGroupIdV2(groupSecretParams); - final GroupMasterKey masterKey = groupSecretParams.getMasterKey(); - GroupInfoV2 g = new GroupInfoV2(groupId, masterKey); - g.setGroup(decryptedGroup); - - return g; - } - - private byte[] readAvatarBytes(final String avatarFile) throws IOException { - final byte[] avatarBytes; - try (InputStream avatar = avatarFile == null ? null : new FileInputStream(avatarFile)) { - avatarBytes = avatar == null ? null : IOUtils.readFully(avatar); - } - return avatarBytes; - } - - private GroupsV2Operations.NewGroup buildNewGroupV2( - String name, Collection members, byte[] avatar - ) { - final ProfileKeyCredential profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential( - selfAddressProvider.getSelfAddress()); - if (profileKeyCredential == null) { - logger.warn("Cannot create a V2 group as self does not have a versioned profile"); - return null; - } - - if (!areMembersValid(members)) return null; - - GroupCandidate self = new GroupCandidate(selfAddressProvider.getSelfAddress().getUuid().orNull(), - Optional.fromNullable(profileKeyCredential)); - Set candidates = members.stream() - .map(member -> new GroupCandidate(member.getUuid().get(), - Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member)))) - .collect(Collectors.toSet()); - - final GroupSecretParams groupSecretParams = GroupSecretParams.generate(); - return groupsV2Operations.createNewGroup(groupSecretParams, - name, - Optional.fromNullable(avatar), - self, - candidates, - Member.Role.DEFAULT, - 0); - } - - private boolean areMembersValid(final Collection members) { - final Set noUuidCapability = members.stream() - .filter(address -> !address.getUuid().isPresent()) - .map(SignalServiceAddress::getLegacyIdentifier) - .collect(Collectors.toSet()); - if (noUuidCapability.size() > 0) { - logger.warn("Cannot create a V2 group as some members don't have a UUID: {}", - String.join(", ", noUuidCapability)); - return false; - } - - final Set noGv2Capability = members.stream() - .map(profileProvider::getProfile) - .filter(profile -> profile != null && !profile.getCapabilities().gv2) - .collect(Collectors.toSet()); - if (noGv2Capability.size() > 0) { - logger.warn("Cannot create a V2 group as some members don't support Groups V2: {}", - noGv2Capability.stream().map(SignalProfile::getName).collect(Collectors.joining(", "))); - return false; - } - - return true; - } - - public Pair updateGroupV2( - GroupInfoV2 groupInfoV2, String name, String avatarFile - ) throws IOException { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); - GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - - GroupChange.Actions.Builder change = name != null - ? groupOperations.createModifyGroupTitle(name) - : GroupChange.Actions.newBuilder(); - - if (avatarFile != null) { - final byte[] avatarBytes = readAvatarBytes(avatarFile); - String avatarCdnKey = groupsV2Api.uploadAvatar(avatarBytes, - groupSecretParams, - groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams)); - change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().setAvatar(avatarCdnKey)); - } - - final Optional uuid = this.selfAddressProvider.getSelfAddress().getUuid(); - if (uuid.isPresent()) { - change.setSourceUuid(UuidUtil.toByteString(uuid.get())); - } - - return commitChange(groupInfoV2, change); - } - - public Pair updateGroupV2( - GroupInfoV2 groupInfoV2, Set newMembers - ) throws IOException { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); - GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - - if (!areMembersValid(newMembers)) { - throw new IOException("Failed to update group"); - } - - Set candidates = newMembers.stream() - .map(member -> new GroupCandidate(member.getUuid().get(), - Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member)))) - .collect(Collectors.toSet()); - - final GroupChange.Actions.Builder change = groupOperations.createModifyGroupMembershipChange(candidates, - selfAddressProvider.getSelfAddress().getUuid().get()); - - final Optional uuid = this.selfAddressProvider.getSelfAddress().getUuid(); - if (uuid.isPresent()) { - change.setSourceUuid(UuidUtil.toByteString(uuid.get())); - } - - return commitChange(groupInfoV2, change); - } - - public Pair leaveGroup(GroupInfoV2 groupInfoV2) throws IOException { - List pendingMembersList = groupInfoV2.getGroup().getPendingMembersList(); - final UUID selfUuid = selfAddressProvider.getSelfAddress().getUuid().get(); - Optional selfPendingMember = DecryptedGroupUtil.findPendingByUuid(pendingMembersList, - selfUuid); - - if (selfPendingMember.isPresent()) { - return revokeInvites(groupInfoV2, Set.of(selfPendingMember.get())); - } else { - return ejectMembers(groupInfoV2, Set.of(selfUuid)); - } - } - - public GroupChange joinGroup( - GroupMasterKey groupMasterKey, - GroupLinkPassword groupLinkPassword, - DecryptedGroupJoinInfo decryptedGroupJoinInfo - ) throws IOException { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - - final SignalServiceAddress selfAddress = this.selfAddressProvider.getSelfAddress(); - final ProfileKeyCredential profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential( - selfAddress); - if (profileKeyCredential == null) { - throw new IOException("Cannot join a V2 group as self does not have a versioned profile"); - } - - boolean requestToJoin = decryptedGroupJoinInfo.getAddFromInviteLink() - == AccessControl.AccessRequired.ADMINISTRATOR; - GroupChange.Actions.Builder change = requestToJoin - ? groupOperations.createGroupJoinRequest(profileKeyCredential) - : groupOperations.createGroupJoinDirect(profileKeyCredential); - - change.setSourceUuid(UuidUtil.toByteString(selfAddress.getUuid().get())); - - return commitChange(groupSecretParams, decryptedGroupJoinInfo.getRevision(), change, groupLinkPassword); - } - - public Pair acceptInvite(GroupInfoV2 groupInfoV2) throws IOException { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); - final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - - final SignalServiceAddress selfAddress = this.selfAddressProvider.getSelfAddress(); - final ProfileKeyCredential profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential( - selfAddress); - if (profileKeyCredential == null) { - throw new IOException("Cannot join a V2 group as self does not have a versioned profile"); - } - - final GroupChange.Actions.Builder change = groupOperations.createAcceptInviteChange(profileKeyCredential); - - final Optional uuid = selfAddress.getUuid(); - if (uuid.isPresent()) { - change.setSourceUuid(UuidUtil.toByteString(uuid.get())); - } - - return commitChange(groupInfoV2, change); - } - - public Pair revokeInvites( - GroupInfoV2 groupInfoV2, Set pendingMembers - ) throws IOException { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); - final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - final Set uuidCipherTexts = pendingMembers.stream().map(member -> { - try { - return new UuidCiphertext(member.getUuidCipherText().toByteArray()); - } catch (InvalidInputException e) { - throw new AssertionError(e); - } - }).collect(Collectors.toSet()); - return commitChange(groupInfoV2, groupOperations.createRemoveInvitationChange(uuidCipherTexts)); - } - - public Pair ejectMembers(GroupInfoV2 groupInfoV2, Set uuids) throws IOException { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); - final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(uuids)); - } - - private Pair commitChange( - GroupInfoV2 groupInfoV2, GroupChange.Actions.Builder change - ) throws IOException { - final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); - final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - final DecryptedGroup previousGroupState = groupInfoV2.getGroup(); - final int nextRevision = previousGroupState.getRevision() + 1; - final GroupChange.Actions changeActions = change.setRevision(nextRevision).build(); - final DecryptedGroupChange decryptedChange; - final DecryptedGroup decryptedGroupState; - - try { - decryptedChange = groupOperations.decryptChange(changeActions, - selfAddressProvider.getSelfAddress().getUuid().get()); - decryptedGroupState = DecryptedGroupUtil.apply(previousGroupState, decryptedChange); - } catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) { - throw new IOException(e); - } - - GroupChange signedGroupChange = groupsV2Api.patchGroup(changeActions, - groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams), - Optional.absent()); - - return new Pair<>(decryptedGroupState, signedGroupChange); - } - - private GroupChange commitChange( - GroupSecretParams groupSecretParams, - int currentRevision, - GroupChange.Actions.Builder change, - GroupLinkPassword password - ) throws IOException { - final int nextRevision = currentRevision + 1; - final GroupChange.Actions changeActions = change.setRevision(nextRevision).build(); - - return groupsV2Api.patchGroup(changeActions, - groupAuthorizationProvider.getAuthorizationForToday(groupSecretParams), - Optional.fromNullable(password).transform(GroupLinkPassword::serialize)); - } - - public DecryptedGroup getUpdatedDecryptedGroup( - DecryptedGroup group, byte[] signedGroupChange, GroupMasterKey groupMasterKey - ) { - try { - final DecryptedGroupChange decryptedGroupChange = getDecryptedGroupChange(signedGroupChange, - groupMasterKey); - if (decryptedGroupChange == null) { - return null; - } - return DecryptedGroupUtil.apply(group, decryptedGroupChange); - } catch (NotAbleToApplyGroupV2ChangeException e) { - return null; - } - } - - private DecryptedGroupChange getDecryptedGroupChange(byte[] signedGroupChange, GroupMasterKey groupMasterKey) { - if (signedGroupChange != null) { - GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey( - groupMasterKey)); - - try { - return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true).orNull(); - } catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) { - return null; - } - } - - return null; - } -} diff --git a/src/main/java/org/asamk/signal/manager/helper/MessagePipeProvider.java b/src/main/java/org/asamk/signal/manager/helper/MessagePipeProvider.java deleted file mode 100644 index 7739928c..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/MessagePipeProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.whispersystems.signalservice.api.SignalServiceMessagePipe; - -public interface MessagePipeProvider { - - SignalServiceMessagePipe getMessagePipe(boolean unidentified); -} diff --git a/src/main/java/org/asamk/signal/manager/helper/MessageReceiverProvider.java b/src/main/java/org/asamk/signal/manager/helper/MessageReceiverProvider.java deleted file mode 100644 index 9a18a5e4..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/MessageReceiverProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; - -public interface MessageReceiverProvider { - - SignalServiceMessageReceiver getMessageReceiver(); -} diff --git a/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java deleted file mode 100644 index c81e2ff7..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.signal.zkgroup.profiles.ProfileKey; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.SignalServiceMessagePipe; -import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; -import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; -import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; -import org.whispersystems.signalservice.internal.util.concurrent.CascadingFuture; -import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture; - -import java.io.IOException; -import java.util.Arrays; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public final class ProfileHelper { - - private final ProfileKeyProvider profileKeyProvider; - - private final UnidentifiedAccessProvider unidentifiedAccessProvider; - - private final MessagePipeProvider messagePipeProvider; - - private final MessageReceiverProvider messageReceiverProvider; - - public ProfileHelper( - final ProfileKeyProvider profileKeyProvider, - final UnidentifiedAccessProvider unidentifiedAccessProvider, - final MessagePipeProvider messagePipeProvider, - final MessageReceiverProvider messageReceiverProvider - ) { - this.profileKeyProvider = profileKeyProvider; - this.unidentifiedAccessProvider = unidentifiedAccessProvider; - this.messagePipeProvider = messagePipeProvider; - this.messageReceiverProvider = messageReceiverProvider; - } - - public ProfileAndCredential retrieveProfileSync( - SignalServiceAddress recipient, SignalServiceProfile.RequestType requestType - ) throws IOException { - try { - return retrieveProfile(recipient, requestType).get(10, TimeUnit.SECONDS); - } catch (ExecutionException e) { - if (e.getCause() instanceof PushNetworkException) { - throw (PushNetworkException) e.getCause(); - } else if (e.getCause() instanceof NotFoundException) { - throw (NotFoundException) e.getCause(); - } else { - throw new IOException(e); - } - } catch (InterruptedException | TimeoutException e) { - throw new PushNetworkException(e); - } - } - - public ListenableFuture retrieveProfile( - SignalServiceAddress address, SignalServiceProfile.RequestType requestType - ) { - Optional unidentifiedAccess = getUnidentifiedAccess(address); - Optional profileKey = Optional.fromNullable(profileKeyProvider.getProfileKey(address)); - - if (unidentifiedAccess.isPresent()) { - return new CascadingFuture<>(Arrays.asList(() -> getPipeRetrievalFuture(address, - profileKey, - unidentifiedAccess, - requestType), - () -> getSocketRetrievalFuture(address, profileKey, unidentifiedAccess, requestType), - () -> getPipeRetrievalFuture(address, profileKey, Optional.absent(), requestType), - () -> getSocketRetrievalFuture(address, profileKey, Optional.absent(), requestType)), - e -> !(e instanceof NotFoundException)); - } else { - return new CascadingFuture<>(Arrays.asList(() -> getPipeRetrievalFuture(address, - profileKey, - Optional.absent(), - requestType), () -> getSocketRetrievalFuture(address, profileKey, Optional.absent(), requestType)), - e -> !(e instanceof NotFoundException)); - } - } - - private ListenableFuture getPipeRetrievalFuture( - SignalServiceAddress address, - Optional profileKey, - Optional unidentifiedAccess, - SignalServiceProfile.RequestType requestType - ) throws IOException { - SignalServiceMessagePipe unidentifiedPipe = messagePipeProvider.getMessagePipe(true); - SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() - ? unidentifiedPipe - : messagePipeProvider.getMessagePipe(false); - if (pipe != null) { - return pipe.getProfile(address, profileKey, unidentifiedAccess, requestType); - } - - throw new IOException("No pipe available!"); - } - - private ListenableFuture getSocketRetrievalFuture( - SignalServiceAddress address, - Optional profileKey, - Optional unidentifiedAccess, - SignalServiceProfile.RequestType requestType - ) { - SignalServiceMessageReceiver receiver = messageReceiverProvider.getMessageReceiver(); - return receiver.retrieveProfile(address, profileKey, unidentifiedAccess, requestType); - } - - private Optional getUnidentifiedAccess(SignalServiceAddress recipient) { - Optional unidentifiedAccess = unidentifiedAccessProvider.getAccessFor(recipient); - - if (unidentifiedAccess.isPresent()) { - return unidentifiedAccess.get().getTargetUnidentifiedAccess(); - } - - return Optional.absent(); - } -} diff --git a/src/main/java/org/asamk/signal/manager/helper/ProfileKeyCredentialProvider.java b/src/main/java/org/asamk/signal/manager/helper/ProfileKeyCredentialProvider.java deleted file mode 100644 index ebb728c1..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/ProfileKeyCredentialProvider.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.signal.zkgroup.profiles.ProfileKeyCredential; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -public interface ProfileKeyCredentialProvider { - - ProfileKeyCredential getProfileKeyCredential(SignalServiceAddress address); -} diff --git a/src/main/java/org/asamk/signal/manager/helper/ProfileKeyProvider.java b/src/main/java/org/asamk/signal/manager/helper/ProfileKeyProvider.java deleted file mode 100644 index 9172710e..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/ProfileKeyProvider.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.signal.zkgroup.profiles.ProfileKey; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -public interface ProfileKeyProvider { - - ProfileKey getProfileKey(SignalServiceAddress address); -} diff --git a/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java b/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java deleted file mode 100644 index 1ff4cb05..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.asamk.signal.storage.profiles.SignalProfile; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -public interface ProfileProvider { - - SignalProfile getProfile(SignalServiceAddress address); -} diff --git a/src/main/java/org/asamk/signal/manager/helper/SelfAddressProvider.java b/src/main/java/org/asamk/signal/manager/helper/SelfAddressProvider.java deleted file mode 100644 index 3591064f..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/SelfAddressProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -public interface SelfAddressProvider { - - SignalServiceAddress getSelfAddress(); -} diff --git a/src/main/java/org/asamk/signal/manager/helper/SelfProfileKeyProvider.java b/src/main/java/org/asamk/signal/manager/helper/SelfProfileKeyProvider.java deleted file mode 100644 index 8fa51835..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/SelfProfileKeyProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.signal.zkgroup.profiles.ProfileKey; - -public interface SelfProfileKeyProvider { - - ProfileKey getProfileKey(); -} diff --git a/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java b/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java deleted file mode 100644 index 97331cf3..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.asamk.signal.storage.profiles.SignalProfile; -import org.signal.libsignal.metadata.certificate.InvalidCertificateException; -import org.signal.zkgroup.profiles.ProfileKey; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -import static org.whispersystems.signalservice.internal.util.Util.getSecretBytes; - -public class UnidentifiedAccessHelper { - - private final SelfProfileKeyProvider selfProfileKeyProvider; - - private final ProfileKeyProvider profileKeyProvider; - - private final ProfileProvider profileProvider; - - private final UnidentifiedAccessSenderCertificateProvider senderCertificateProvider; - - public UnidentifiedAccessHelper( - final SelfProfileKeyProvider selfProfileKeyProvider, - final ProfileKeyProvider profileKeyProvider, - final ProfileProvider profileProvider, - final UnidentifiedAccessSenderCertificateProvider senderCertificateProvider - ) { - this.selfProfileKeyProvider = selfProfileKeyProvider; - this.profileKeyProvider = profileKeyProvider; - this.profileProvider = profileProvider; - this.senderCertificateProvider = senderCertificateProvider; - } - - public byte[] getSelfUnidentifiedAccessKey() { - return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey()); - } - - public byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) { - ProfileKey theirProfileKey = profileKeyProvider.getProfileKey(recipient); - if (theirProfileKey == null) { - return null; - } - - SignalProfile targetProfile = profileProvider.getProfile(recipient); - if (targetProfile == null || targetProfile.getUnidentifiedAccess() == null) { - return null; - } - - if (targetProfile.isUnrestrictedUnidentifiedAccess()) { - return createUnrestrictedUnidentifiedAccess(); - } - - return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); - } - - public Optional getAccessForSync() { - byte[] selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(); - byte[] selfUnidentifiedAccessCertificate = senderCertificateProvider.getSenderCertificate(); - - if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) { - return Optional.absent(); - } - - try { - return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey, - selfUnidentifiedAccessCertificate), - new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate))); - } catch (InvalidCertificateException e) { - return Optional.absent(); - } - } - - public List> getAccessFor(Collection recipients) { - return recipients.stream().map(this::getAccessFor).collect(Collectors.toList()); - } - - public Optional getAccessFor(SignalServiceAddress recipient) { - byte[] recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient); - byte[] selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(); - byte[] selfUnidentifiedAccessCertificate = senderCertificateProvider.getSenderCertificate(); - - if (recipientUnidentifiedAccessKey == null - || selfUnidentifiedAccessKey == null - || selfUnidentifiedAccessCertificate == null) { - return Optional.absent(); - } - - try { - return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey, - selfUnidentifiedAccessCertificate), - new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate))); - } catch (InvalidCertificateException e) { - return Optional.absent(); - } - } - - private static byte[] createUnrestrictedUnidentifiedAccess() { - return getSecretBytes(16); - } -} diff --git a/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessProvider.java b/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessProvider.java deleted file mode 100644 index a4b65a6f..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessProvider.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.asamk.signal.manager.helper; - -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -public interface UnidentifiedAccessProvider { - - Optional getAccessFor(SignalServiceAddress address); -} diff --git a/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessSenderCertificateProvider.java b/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessSenderCertificateProvider.java deleted file mode 100644 index b0597346..00000000 --- a/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessSenderCertificateProvider.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.asamk.signal.manager.helper; - -public interface UnidentifiedAccessSenderCertificateProvider { - - byte[] getSenderCertificate(); -} diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java deleted file mode 100644 index 3af52708..00000000 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ /dev/null @@ -1,517 +0,0 @@ -package org.asamk.signal.storage; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import org.asamk.signal.manager.GroupId; -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.GroupInfoV1; -import org.asamk.signal.storage.groups.JsonGroupStore; -import org.asamk.signal.storage.profiles.ProfileStore; -import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; -import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; -import org.asamk.signal.storage.protocol.RecipientStore; -import org.asamk.signal.storage.protocol.SessionInfo; -import org.asamk.signal.storage.protocol.SignalServiceAddressResolver; -import org.asamk.signal.storage.stickers.StickerStore; -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.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.IdentityKeyPair; -import org.whispersystems.libsignal.state.PreKeyRecord; -import org.whispersystems.libsignal.state.SignedPreKeyRecord; -import org.whispersystems.libsignal.util.Medium; -import org.whispersystems.libsignal.util.Pair; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.util.Base64; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.Channels; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.util.Collection; -import java.util.UUID; -import java.util.stream.Collectors; - -public class SignalAccount implements Closeable { - - final static Logger logger = LoggerFactory.getLogger(SignalAccount.class); - - private final ObjectMapper jsonProcessor = new ObjectMapper(); - private final FileChannel fileChannel; - private final 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 ProfileKey profileKey; - private int preKeyIdOffset; - private int nextSignedPreKeyId; - - private boolean registered = false; - - private JsonSignalProtocolStore signalProtocolStore; - private JsonGroupStore groupStore; - private JsonContactsStore contactStore; - private RecipientStore recipientStore; - private ProfileStore profileStore; - private StickerStore stickerStore; - - private SignalAccount(final FileChannel fileChannel, final FileLock lock) { - this.fileChannel = fileChannel; - this.lock = lock; - jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect - jsonProcessor.enable(SerializationFeature.INDENT_OUTPUT); // for pretty print, you can disable it. - jsonProcessor.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - jsonProcessor.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); - jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); - } - - public static SignalAccount load(File dataPath, String username) throws IOException { - final File fileName = getFileName(dataPath, username); - final Pair pair = openFileChannel(fileName); - try { - SignalAccount account = new SignalAccount(pair.first(), pair.second()); - account.load(dataPath); - return account; - } catch (Throwable e) { - pair.second().close(); - pair.first().close(); - throw e; - } - } - - public static SignalAccount create( - File dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey - ) throws IOException { - IOUtils.createPrivateDirectories(dataPath); - File fileName = getFileName(dataPath, username); - if (!fileName.exists()) { - IOUtils.createPrivateFile(fileName); - } - - final Pair pair = openFileChannel(fileName); - SignalAccount account = new SignalAccount(pair.first(), pair.second()); - - account.username = username; - account.profileKey = profileKey; - account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); - account.groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username)); - account.contactStore = new JsonContactsStore(); - account.recipientStore = new RecipientStore(); - account.profileStore = new ProfileStore(); - account.stickerStore = new StickerStore(); - account.registered = false; - - return account; - } - - public static SignalAccount createLinkedAccount( - File dataPath, - String username, - UUID uuid, - String password, - int deviceId, - IdentityKeyPair identityKey, - int registrationId, - String signalingKey, - ProfileKey profileKey - ) throws IOException { - IOUtils.createPrivateDirectories(dataPath); - File fileName = getFileName(dataPath, username); - if (!fileName.exists()) { - IOUtils.createPrivateFile(fileName); - } - - final Pair pair = openFileChannel(fileName); - SignalAccount account = new SignalAccount(pair.first(), pair.second()); - - 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(getGroupCachePath(dataPath, username)); - account.contactStore = new JsonContactsStore(); - account.recipientStore = new RecipientStore(); - account.profileStore = new ProfileStore(); - account.stickerStore = new StickerStore(); - account.registered = true; - account.isMultiDevice = true; - - return account; - } - - public static File getFileName(File dataPath, String username) { - return new File(dataPath, username); - } - - private static File getUserPath(final File dataPath, final String username) { - return new File(dataPath, username + ".d"); - } - - public static File getMessageCachePath(File dataPath, String username) { - return new File(getUserPath(dataPath, username), "msg-cache"); - } - - private static File getGroupCachePath(File dataPath, String username) { - return new File(getUserPath(dataPath, username), "group-cache"); - } - - public static boolean userExists(File dataPath, String username) { - if (username == null) { - return false; - } - File f = getFileName(dataPath, username); - return !(!f.exists() || f.isDirectory()); - } - - private void load(File dataPath) throws IOException { - JsonNode rootNode; - synchronized (fileChannel) { - fileChannel.position(0); - 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(); - } - if (rootNode.has("isMultiDevice")) { - isMultiDevice = Util.getNotNullNode(rootNode, "isMultiDevice").asBoolean(); - } - username = Util.getNotNullNode(rootNode, "username").asText(); - password = Util.getNotNullNode(rootNode, "password").asText(); - JsonNode pinNode = rootNode.get("registrationLockPin"); - registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText(); - if (rootNode.has("signalingKey")) { - signalingKey = Util.getNotNullNode(rootNode, "signalingKey").asText(); - } - if (rootNode.has("preKeyIdOffset")) { - preKeyIdOffset = Util.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0); - } else { - preKeyIdOffset = 0; - } - if (rootNode.has("nextSignedPreKeyId")) { - nextSignedPreKeyId = Util.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt(); - } else { - nextSignedPreKeyId = 0; - } - if (rootNode.has("profileKey")) { - 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); - registered = Util.getNotNullNode(rootNode, "registered").asBoolean(); - JsonNode groupStoreNode = rootNode.get("groupStore"); - if (groupStoreNode != null) { - groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class); - groupStore.groupCachePath = getGroupCachePath(dataPath, username); - } - if (groupStore == null) { - groupStore = new JsonGroupStore(getGroupCachePath(dataPath, username)); - } - - JsonNode contactStoreNode = rootNode.get("contactStore"); - if (contactStoreNode != null) { - contactStore = jsonProcessor.convertValue(contactStoreNode, JsonContactsStore.class); - } - if (contactStore == null) { - contactStore = new JsonContactsStore(); - } - - JsonNode recipientStoreNode = rootNode.get("recipientStore"); - if (recipientStoreNode != null) { - recipientStore = jsonProcessor.convertValue(recipientStoreNode, RecipientStore.class); - } - if (recipientStore == null) { - recipientStore = new RecipientStore(); - - recipientStore.resolveServiceAddress(getSelfAddress()); - - for (ContactInfo contact : contactStore.getContacts()) { - recipientStore.resolveServiceAddress(contact.getAddress()); - } - - for (GroupInfo group : groupStore.getGroups()) { - if (group instanceof GroupInfoV1) { - GroupInfoV1 groupInfoV1 = (GroupInfoV1) group; - groupInfoV1.members = groupInfoV1.members.stream() - .map(m -> recipientStore.resolveServiceAddress(m)) - .collect(Collectors.toSet()); - } - } - - for (SessionInfo session : signalProtocolStore.getSessions()) { - session.address = recipientStore.resolveServiceAddress(session.address); - } - - for (JsonIdentityKeyStore.Identity identity : signalProtocolStore.getIdentities()) { - identity.setAddress(recipientStore.resolveServiceAddress(identity.getAddress())); - } - } - - JsonNode profileStoreNode = rootNode.get("profileStore"); - if (profileStoreNode != null) { - profileStore = jsonProcessor.convertValue(profileStoreNode, ProfileStore.class); - } - if (profileStore == null) { - profileStore = new ProfileStore(); - } - - JsonNode stickerStoreNode = rootNode.get("stickerStore"); - if (stickerStoreNode != null) { - stickerStore = jsonProcessor.convertValue(stickerStoreNode, StickerStore.class); - } - if (stickerStore == null) { - stickerStore = new StickerStore(); - } - - JsonNode threadStoreNode = rootNode.get("threadStore"); - if (threadStoreNode != null) { - 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(GroupId.fromBase64(thread.id)); - if (groupInfo instanceof GroupInfoV1) { - ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime; - groupStore.updateGroup(groupInfo); - } - } - } catch (Exception ignored) { - } - } - } - } - - public void save() { - if (fileChannel == null) { - return; - } - ObjectNode rootNode = jsonProcessor.createObjectNode(); - rootNode.put("username", username) - .put("uuid", uuid == null ? null : uuid.toString()) - .put("deviceId", deviceId) - .put("isMultiDevice", isMultiDevice) - .put("password", password) - .put("registrationLockPin", registrationLockPin) - .put("signalingKey", signalingKey) - .put("preKeyIdOffset", preKeyIdOffset) - .put("nextSignedPreKeyId", nextSignedPreKeyId) - .put("profileKey", Base64.encodeBytes(profileKey.serialize())) - .put("registered", registered) - .putPOJO("axolotlStore", signalProtocolStore) - .putPOJO("groupStore", groupStore) - .putPOJO("contactStore", contactStore) - .putPOJO("recipientStore", recipientStore) - .putPOJO("profileStore", profileStore) - .putPOJO("stickerStore", stickerStore); - try { - try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { - // Write to memory first to prevent corrupting the file in case of serialization errors - jsonProcessor.writeValue(output, rootNode); - ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); - synchronized (fileChannel) { - fileChannel.position(0); - input.transferTo(Channels.newOutputStream(fileChannel)); - fileChannel.truncate(fileChannel.position()); - fileChannel.force(false); - } - } - } catch (Exception e) { - logger.error("Error saving file: {}", e.getMessage()); - } - } - - private static Pair openFileChannel(File fileName) throws IOException { - FileChannel fileChannel = new RandomAccessFile(fileName, "rw").getChannel(); - FileLock lock = fileChannel.tryLock(); - if (lock == null) { - logger.info("Config file is in use by another instance, waiting…"); - lock = fileChannel.lock(); - logger.info("Config file lock acquired."); - } - return new Pair<>(fileChannel, lock); - } - - public void setResolver(final SignalServiceAddressResolver resolver) { - signalProtocolStore.setResolver(resolver); - } - - public void addPreKeys(Collection records) { - for (PreKeyRecord record : records) { - signalProtocolStore.storePreKey(record.getId(), record); - } - preKeyIdOffset = (preKeyIdOffset + records.size()) % Medium.MAX_VALUE; - } - - public void addSignedPreKey(SignedPreKeyRecord record) { - signalProtocolStore.storeSignedPreKey(record.getId(), record); - nextSignedPreKeyId = (nextSignedPreKeyId + 1) % Medium.MAX_VALUE; - } - - public JsonSignalProtocolStore getSignalProtocolStore() { - return signalProtocolStore; - } - - public JsonGroupStore getGroupStore() { - return groupStore; - } - - public JsonContactsStore getContactStore() { - return contactStore; - } - - public RecipientStore getRecipientStore() { - return recipientStore; - } - - public ProfileStore getProfileStore() { - return profileStore; - } - - public StickerStore getStickerStore() { - return stickerStore; - } - - 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(uuid, username); - } - - public int getDeviceId() { - return deviceId; - } - - public String getPassword() { - return password; - } - - public void setPassword(final String password) { - this.password = password; - } - - public String getRegistrationLockPin() { - return registrationLockPin; - } - - public String getRegistrationLock() { - return null; // TODO implement KBS - } - - public void setRegistrationLockPin(final String registrationLockPin) { - this.registrationLockPin = registrationLockPin; - } - - public String getSignalingKey() { - return signalingKey; - } - - public void setSignalingKey(final String signalingKey) { - this.signalingKey = signalingKey; - } - - public ProfileKey getProfileKey() { - return profileKey; - } - - public void setProfileKey(final ProfileKey profileKey) { - this.profileKey = profileKey; - } - - public int getPreKeyIdOffset() { - return preKeyIdOffset; - } - - public int getNextSignedPreKeyId() { - return nextSignedPreKeyId; - } - - public boolean isRegistered() { - return registered; - } - - public void setRegistered(final boolean registered) { - this.registered = registered; - } - - public boolean isMultiDevice() { - return isMultiDevice; - } - - public void setMultiDevice(final boolean multiDevice) { - isMultiDevice = multiDevice; - } - - @Override - public void close() throws IOException { - synchronized (fileChannel) { - try { - lock.close(); - } catch (ClosedChannelException ignored) { - } - fileChannel.close(); - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java b/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java deleted file mode 100644 index 3b155210..00000000 --- a/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.asamk.signal.storage.contacts; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.util.UUID; - -import static com.fasterxml.jackson.annotation.JsonProperty.Access.WRITE_ONLY; - -public class ContactInfo { - - @JsonProperty - public String name; - - @JsonProperty - public String number; - - @JsonProperty - public UUID uuid; - - @JsonProperty - public String color; - - @JsonProperty(defaultValue = "0") - public int messageExpirationTime; - - @JsonProperty(access = WRITE_ONLY) - public String profileKey; - - @JsonProperty(defaultValue = "false") - public boolean blocked; - - @JsonProperty - public Integer inboxPosition; - - @JsonProperty(defaultValue = "false") - public boolean archived; - - public ContactInfo() { - } - - public ContactInfo(SignalServiceAddress address) { - this.number = address.getNumber().orNull(); - this.uuid = address.getUuid().orNull(); - } - - @JsonIgnore - public SignalServiceAddress getAddress() { - return new SignalServiceAddress(uuid, number); - } -} diff --git a/src/main/java/org/asamk/signal/storage/contacts/JsonContactsStore.java b/src/main/java/org/asamk/signal/storage/contacts/JsonContactsStore.java deleted file mode 100644 index bb81b0c9..00000000 --- a/src/main/java/org/asamk/signal/storage/contacts/JsonContactsStore.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.asamk.signal.storage.contacts; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.util.ArrayList; -import java.util.List; - -public class JsonContactsStore { - - @JsonProperty("contacts") - private List contacts = new ArrayList<>(); - - public void updateContact(ContactInfo contact) { - final SignalServiceAddress contactAddress = contact.getAddress(); - for (int i = 0; i < contacts.size(); i++) { - if (contacts.get(i).getAddress().matches(contactAddress)) { - contacts.set(i, contact); - return; - } - } - - contacts.add(contact); - } - - public ContactInfo getContact(SignalServiceAddress address) { - for (ContactInfo contact : contacts) { - if (contact.getAddress().matches(address)) { - if (contact.uuid == null) { - contact.uuid = address.getUuid().orNull(); - } else if (contact.number == null) { - contact.number = address.getNumber().orNull(); - } - - return contact; - } - } - return null; - } - - public List getContacts() { - return new ArrayList<>(contacts); - } - - /** - * Remove all contacts from the store - */ - public void clear() { - contacts.clear(); - } -} diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java deleted file mode 100644 index 40b8c884..00000000 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.asamk.signal.storage.groups; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupInviteLinkUrl; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public abstract class GroupInfo { - - @JsonIgnore - public abstract GroupId getGroupId(); - - @JsonIgnore - public abstract String getTitle(); - - @JsonIgnore - public abstract GroupInviteLinkUrl getGroupInviteLink(); - - @JsonIgnore - public abstract Set getMembers(); - - @JsonIgnore - public Set getPendingMembers() { - return Set.of(); - } - - @JsonIgnore - public Set getRequestingMembers() { - return Set.of(); - } - - @JsonIgnore - public abstract boolean isBlocked(); - - @JsonIgnore - public abstract void setBlocked(boolean blocked); - - @JsonIgnore - public abstract int getMessageExpirationTime(); - - @JsonIgnore - public Set getMembersWithout(SignalServiceAddress address) { - return getMembers().stream().filter(member -> !member.matches(address)).collect(Collectors.toSet()); - } - - @JsonIgnore - public Set getMembersIncludingPendingWithout(SignalServiceAddress address) { - return Stream.concat(getMembers().stream(), getPendingMembers().stream()) - .filter(member -> !member.matches(address)) - .collect(Collectors.toSet()); - } - - @JsonIgnore - public boolean isMember(SignalServiceAddress address) { - for (SignalServiceAddress member : getMembers()) { - if (member.matches(address)) { - return true; - } - } - return false; - } - - @JsonIgnore - public boolean isPendingMember(SignalServiceAddress address) { - for (SignalServiceAddress member : getPendingMembers()) { - if (member.matches(address)) { - return true; - } - } - return false; - } -} diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java deleted file mode 100644 index 90b26b81..00000000 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java +++ /dev/null @@ -1,212 +0,0 @@ -package org.asamk.signal.storage.groups; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdV1; -import org.asamk.signal.manager.GroupIdV2; -import org.asamk.signal.manager.GroupInviteLinkUrl; -import org.asamk.signal.manager.GroupUtils; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -public class GroupInfoV1 extends GroupInfo { - - private static final ObjectMapper jsonProcessor = new ObjectMapper(); - - private final GroupIdV1 groupId; - - private GroupIdV2 expectedV2Id; - - @JsonProperty - public String name; - - @JsonProperty - @JsonDeserialize(using = MembersDeserializer.class) - @JsonSerialize(using = MembersSerializer.class) - public Set members = new HashSet<>(); - @JsonProperty - public String color; - @JsonProperty(defaultValue = "0") - public int messageExpirationTime; - @JsonProperty(defaultValue = "false") - public boolean blocked; - @JsonProperty - public Integer inboxPosition; - @JsonProperty(defaultValue = "false") - public boolean archived; - - public GroupInfoV1(GroupIdV1 groupId) { - this.groupId = groupId; - } - - public GroupInfoV1( - @JsonProperty("groupId") byte[] groupId, - @JsonProperty("expectedV2Id") byte[] expectedV2Id, - @JsonProperty("name") String name, - @JsonProperty("members") Collection members, - @JsonProperty("avatarId") long _ignored_avatarId, - @JsonProperty("color") String color, - @JsonProperty("blocked") boolean blocked, - @JsonProperty("inboxPosition") Integer inboxPosition, - @JsonProperty("archived") boolean archived, - @JsonProperty("messageExpirationTime") int messageExpirationTime, - @JsonProperty("active") boolean _ignored_active - ) { - this.groupId = GroupId.v1(groupId); - this.expectedV2Id = GroupId.v2(expectedV2Id); - this.name = name; - this.members.addAll(members); - this.color = color; - this.blocked = blocked; - this.inboxPosition = inboxPosition; - this.archived = archived; - this.messageExpirationTime = messageExpirationTime; - } - - @Override - @JsonIgnore - public GroupIdV1 getGroupId() { - return groupId; - } - - @JsonProperty("groupId") - private byte[] getGroupIdJackson() { - return groupId.serialize(); - } - - @JsonIgnore - public GroupIdV2 getExpectedV2Id() { - if (expectedV2Id == null) { - expectedV2Id = GroupUtils.getGroupIdV2(groupId); - } - return expectedV2Id; - } - - @JsonProperty("expectedV2Id") - private byte[] getExpectedV2IdJackson() { - return expectedV2Id.serialize(); - } - - @Override - public String getTitle() { - return name; - } - - @Override - public GroupInviteLinkUrl getGroupInviteLink() { - return null; - } - - @JsonIgnore - public Set getMembers() { - return members; - } - - @Override - public boolean isBlocked() { - return blocked; - } - - @Override - public void setBlocked(final boolean blocked) { - this.blocked = blocked; - } - - @Override - public int getMessageExpirationTime() { - return messageExpirationTime; - } - - public void addMembers(Collection addresses) { - for (SignalServiceAddress address : addresses) { - if (this.members.contains(address)) { - continue; - } - removeMember(address); - this.members.add(address); - } - } - - public void removeMember(SignalServiceAddress address) { - this.members.removeIf(member -> member.matches(address)); - } - - private static final class JsonSignalServiceAddress { - - @JsonProperty - private UUID uuid; - - @JsonProperty - private String number; - - JsonSignalServiceAddress(@JsonProperty("uuid") final UUID uuid, @JsonProperty("number") final String number) { - this.uuid = uuid; - this.number = number; - } - - JsonSignalServiceAddress(SignalServiceAddress address) { - this.uuid = address.getUuid().orNull(); - this.number = address.getNumber().orNull(); - } - - SignalServiceAddress toSignalServiceAddress() { - return new SignalServiceAddress(uuid, number); - } - } - - private static class MembersSerializer extends JsonSerializer> { - - @Override - public void serialize( - final Set value, final JsonGenerator jgen, final SerializerProvider provider - ) throws IOException { - jgen.writeStartArray(value.size()); - for (SignalServiceAddress address : value) { - if (address.getUuid().isPresent()) { - jgen.writeObject(new JsonSignalServiceAddress(address)); - } else { - jgen.writeString(address.getNumber().get()); - } - } - jgen.writeEndArray(); - } - } - - private static class MembersDeserializer extends JsonDeserializer> { - - @Override - public Set deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - Set addresses = new HashSet<>(); - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - for (JsonNode n : node) { - if (n.isTextual()) { - addresses.add(new SignalServiceAddress(null, n.textValue())); - } else { - JsonSignalServiceAddress address = jsonProcessor.treeToValue(n, JsonSignalServiceAddress.class); - addresses.add(address.toSignalServiceAddress()); - } - } - - return addresses; - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java deleted file mode 100644 index 1b00caaa..00000000 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.asamk.signal.storage.groups; - -import org.asamk.signal.manager.GroupIdV2; -import org.asamk.signal.manager.GroupInviteLinkUrl; -import org.signal.storageservice.protos.groups.AccessControl; -import org.signal.storageservice.protos.groups.local.DecryptedGroup; -import org.signal.zkgroup.groups.GroupMasterKey; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; - -import java.util.Collections; -import java.util.Set; -import java.util.stream.Collectors; - -public class GroupInfoV2 extends GroupInfo { - - private final GroupIdV2 groupId; - private final GroupMasterKey masterKey; - - private boolean blocked; - private DecryptedGroup group; // stored as a file with hexadecimal groupId as name - - public GroupInfoV2(final GroupIdV2 groupId, final GroupMasterKey masterKey) { - this.groupId = groupId; - this.masterKey = masterKey; - } - - @Override - public GroupIdV2 getGroupId() { - return groupId; - } - - public GroupMasterKey getMasterKey() { - return masterKey; - } - - public void setGroup(final DecryptedGroup group) { - this.group = group; - } - - public DecryptedGroup getGroup() { - return group; - } - - @Override - public String getTitle() { - if (this.group == null) { - return null; - } - return this.group.getTitle(); - } - - @Override - public GroupInviteLinkUrl getGroupInviteLink() { - if (this.group == null || this.group.getInviteLinkPassword() == null || ( - this.group.getAccessControl().getAddFromInviteLink() != AccessControl.AccessRequired.ANY - && this.group.getAccessControl().getAddFromInviteLink() - != AccessControl.AccessRequired.ADMINISTRATOR - )) { - return null; - } - - return GroupInviteLinkUrl.forGroup(masterKey, group); - } - - @Override - public Set getMembers() { - if (this.group == null) { - return Collections.emptySet(); - } - return group.getMembersList() - .stream() - .map(m -> new SignalServiceAddress(UuidUtil.parseOrThrow(m.getUuid().toByteArray()), null)) - .collect(Collectors.toSet()); - } - - @Override - public Set getPendingMembers() { - if (this.group == null) { - return Collections.emptySet(); - } - return group.getPendingMembersList() - .stream() - .map(m -> new SignalServiceAddress(UuidUtil.parseOrThrow(m.getUuid().toByteArray()), null)) - .collect(Collectors.toSet()); - } - - @Override - public Set getRequestingMembers() { - if (this.group == null) { - return Collections.emptySet(); - } - return group.getRequestingMembersList() - .stream() - .map(m -> new SignalServiceAddress(UuidUtil.parseOrThrow(m.getUuid().toByteArray()), null)) - .collect(Collectors.toSet()); - } - - @Override - public boolean isBlocked() { - return blocked; - } - - @Override - public void setBlocked(final boolean blocked) { - this.blocked = blocked; - } - - @Override - public int getMessageExpirationTime() { - return this.group != null && this.group.hasDisappearingMessagesTimer() - ? this.group.getDisappearingMessagesTimer().getDuration() - : 0; - } -} diff --git a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java deleted file mode 100644 index 18bf5ed0..00000000 --- a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.asamk.signal.storage.groups; - -import com.fasterxml.jackson.annotation.JsonProperty; -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdV1; -import org.asamk.signal.manager.GroupIdV2; -import org.asamk.signal.manager.GroupUtils; -import org.asamk.signal.util.Hex; -import org.asamk.signal.util.IOUtils; -import org.signal.storageservice.protos.groups.local.DecryptedGroup; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.groups.GroupMasterKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.util.Base64; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class JsonGroupStore { - - final static Logger logger = LoggerFactory.getLogger(JsonGroupStore.class); - - private static final ObjectMapper jsonProcessor = new ObjectMapper(); - public File groupCachePath; - - @JsonProperty("groups") - @JsonSerialize(using = GroupsSerializer.class) - @JsonDeserialize(using = GroupsDeserializer.class) - private final Map groups = new HashMap<>(); - - private JsonGroupStore() { - } - - public JsonGroupStore(final File groupCachePath) { - this.groupCachePath = groupCachePath; - } - - public void updateGroup(GroupInfo group) { - groups.put(group.getGroupId(), group); - if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() != null) { - try { - IOUtils.createPrivateDirectories(groupCachePath); - try (FileOutputStream stream = new FileOutputStream(getGroupFile(group.getGroupId()))) { - ((GroupInfoV2) group).getGroup().writeTo(stream); - } - final File groupFileLegacy = getGroupFileLegacy(group.getGroupId()); - if (groupFileLegacy.exists()) { - groupFileLegacy.delete(); - } - } catch (IOException e) { - logger.warn("Failed to cache group, ignoring: {}", e.getMessage()); - } - } - } - - public void deleteGroup(GroupId groupId) { - groups.remove(groupId); - } - - public GroupInfo getGroup(GroupId groupId) { - GroupInfo group = groups.get(groupId); - if (group == null) { - if (groupId instanceof GroupIdV1) { - group = groups.get(GroupUtils.getGroupIdV2((GroupIdV1) groupId)); - } else if (groupId instanceof GroupIdV2) { - group = getGroupV1ByV2Id((GroupIdV2) groupId); - } - } - loadDecryptedGroup(group); - return group; - } - - private GroupInfoV1 getGroupV1ByV2Id(GroupIdV2 groupIdV2) { - for (GroupInfo g : groups.values()) { - if (g instanceof GroupInfoV1) { - final GroupInfoV1 gv1 = (GroupInfoV1) g; - if (groupIdV2.equals(gv1.getExpectedV2Id())) { - return gv1; - } - } - } - return null; - } - - private void loadDecryptedGroup(final GroupInfo group) { - if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() == null) { - File groupFile = getGroupFile(group.getGroupId()); - if (!groupFile.exists()) { - groupFile = getGroupFileLegacy(group.getGroupId()); - } - if (!groupFile.exists()) { - return; - } - try (FileInputStream stream = new FileInputStream(groupFile)) { - ((GroupInfoV2) group).setGroup(DecryptedGroup.parseFrom(stream)); - } catch (IOException ignored) { - } - } - } - - private File getGroupFileLegacy(final GroupId groupId) { - return new File(groupCachePath, Hex.toStringCondensed(groupId.serialize())); - } - - private File getGroupFile(final GroupId groupId) { - return new File(groupCachePath, groupId.toBase64().replace("/", "_")); - } - - public GroupInfoV1 getOrCreateGroupV1(GroupIdV1 groupId) { - GroupInfo group = getGroup(groupId); - if (group instanceof GroupInfoV1) { - return (GroupInfoV1) group; - } - - if (group == null) { - return new GroupInfoV1(groupId); - } - - return null; - } - - public List getGroups() { - final Collection groups = this.groups.values(); - for (GroupInfo group : groups) { - loadDecryptedGroup(group); - } - return new ArrayList<>(groups); - } - - private static class GroupsSerializer extends JsonSerializer> { - - @Override - public void serialize( - final Map value, final JsonGenerator jgen, final SerializerProvider provider - ) throws IOException { - final Collection groups = value.values(); - jgen.writeStartArray(groups.size()); - for (GroupInfo group : groups) { - if (group instanceof GroupInfoV1) { - jgen.writeObject(group); - } else if (group instanceof GroupInfoV2) { - final GroupInfoV2 groupV2 = (GroupInfoV2) group; - jgen.writeStartObject(); - jgen.writeStringField("groupId", groupV2.getGroupId().toBase64()); - jgen.writeStringField("masterKey", Base64.encodeBytes(groupV2.getMasterKey().serialize())); - jgen.writeBooleanField("blocked", groupV2.isBlocked()); - jgen.writeEndObject(); - } else { - throw new AssertionError("Unknown group version"); - } - } - jgen.writeEndArray(); - } - } - - private static class GroupsDeserializer extends JsonDeserializer> { - - @Override - public Map deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - Map groups = new HashMap<>(); - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - for (JsonNode n : node) { - GroupInfo g; - if (n.has("masterKey")) { - // a v2 group - GroupIdV2 groupId = GroupIdV2.fromBase64(n.get("groupId").asText()); - try { - GroupMasterKey masterKey = new GroupMasterKey(Base64.decode(n.get("masterKey").asText())); - g = new GroupInfoV2(groupId, masterKey); - } catch (InvalidInputException e) { - throw new AssertionError("Invalid master key for group " + groupId.toBase64()); - } - g.setBlocked(n.get("blocked").asBoolean(false)); - } else { - GroupInfoV1 gv1 = jsonProcessor.treeToValue(n, GroupInfoV1.class); - g = gv1; - } - groups.put(g.getGroupId(), g); - } - - return groups; - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java b/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java deleted file mode 100644 index 3b3d3f9f..00000000 --- a/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.asamk.signal.storage.profiles; - -import com.fasterxml.jackson.annotation.JsonProperty; -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.profiles.ProfileKey; -import org.signal.zkgroup.profiles.ProfileKeyCredential; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; -import org.whispersystems.util.Base64; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -public class ProfileStore { - - private static final ObjectMapper jsonProcessor = new ObjectMapper(); - - @JsonProperty("profiles") - @JsonDeserialize(using = ProfileStoreDeserializer.class) - @JsonSerialize(using = ProfileStoreSerializer.class) - private final List profiles = new ArrayList<>(); - - public SignalProfileEntry getProfileEntry(SignalServiceAddress serviceAddress) { - for (SignalProfileEntry entry : profiles) { - if (entry.getServiceAddress().matches(serviceAddress)) { - return entry; - } - } - return null; - } - - public ProfileKey getProfileKey(SignalServiceAddress serviceAddress) { - for (SignalProfileEntry entry : profiles) { - if (entry.getServiceAddress().matches(serviceAddress)) { - return entry.getProfileKey(); - } - } - return null; - } - - public void updateProfile( - SignalServiceAddress serviceAddress, - ProfileKey profileKey, - long now, - SignalProfile profile, - ProfileKeyCredential profileKeyCredential - ) { - SignalProfileEntry newEntry = new SignalProfileEntry(serviceAddress, - profileKey, - now, - profile, - profileKeyCredential); - for (int i = 0; i < profiles.size(); i++) { - if (profiles.get(i).getServiceAddress().matches(serviceAddress)) { - profiles.set(i, newEntry); - return; - } - } - - profiles.add(newEntry); - } - - public void storeProfileKey(SignalServiceAddress serviceAddress, ProfileKey profileKey) { - SignalProfileEntry newEntry = new SignalProfileEntry(serviceAddress, profileKey, 0, null, null); - for (int i = 0; i < profiles.size(); i++) { - if (profiles.get(i).getServiceAddress().matches(serviceAddress)) { - if (!profiles.get(i).getProfileKey().equals(profileKey)) { - profiles.set(i, newEntry); - } - return; - } - } - - profiles.add(newEntry); - } - - public static class ProfileStoreDeserializer extends JsonDeserializer> { - - @Override - public List deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - - List addresses = new ArrayList<>(); - - if (node.isArray()) { - for (JsonNode entry : node) { - String name = entry.hasNonNull("name") ? entry.get("name").asText() : null; - UUID uuid = entry.hasNonNull("uuid") ? UuidUtil.parseOrNull(entry.get("uuid").asText()) : null; - final SignalServiceAddress serviceAddress = new SignalServiceAddress(uuid, name); - ProfileKey profileKey = null; - try { - profileKey = new ProfileKey(Base64.decode(entry.get("profileKey").asText())); - } catch (InvalidInputException ignored) { - } - ProfileKeyCredential profileKeyCredential = null; - if (entry.hasNonNull("profileKeyCredential")) { - try { - profileKeyCredential = new ProfileKeyCredential(Base64.decode(entry.get( - "profileKeyCredential").asText())); - } catch (Throwable ignored) { - } - } - long lastUpdateTimestamp = entry.get("lastUpdateTimestamp").asLong(); - SignalProfile profile = jsonProcessor.treeToValue(entry.get("profile"), SignalProfile.class); - addresses.add(new SignalProfileEntry(serviceAddress, - profileKey, - lastUpdateTimestamp, - profile, - profileKeyCredential)); - } - } - - return addresses; - } - } - - public static class ProfileStoreSerializer extends JsonSerializer> { - - @Override - public void serialize( - List profiles, JsonGenerator json, SerializerProvider serializerProvider - ) throws IOException { - json.writeStartArray(); - for (SignalProfileEntry profileEntry : profiles) { - final SignalServiceAddress address = profileEntry.getServiceAddress(); - json.writeStartObject(); - if (address.getNumber().isPresent()) { - json.writeStringField("name", address.getNumber().get()); - } - if (address.getUuid().isPresent()) { - json.writeStringField("uuid", address.getUuid().get().toString()); - } - json.writeStringField("profileKey", Base64.encodeBytes(profileEntry.getProfileKey().serialize())); - json.writeNumberField("lastUpdateTimestamp", profileEntry.getLastUpdateTimestamp()); - json.writeObjectField("profile", profileEntry.getProfile()); - if (profileEntry.getProfileKeyCredential() != null) { - json.writeStringField("profileKeyCredential", - Base64.encodeBytes(profileEntry.getProfileKeyCredential().serialize())); - } - json.writeEndObject(); - } - json.writeEndArray(); - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java b/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java deleted file mode 100644 index 023458ed..00000000 --- a/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.asamk.signal.storage.profiles; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; - -import java.io.File; - -public class SignalProfile { - - @JsonProperty - private final String identityKey; - - @JsonProperty - private final String name; - - private final File avatarFile; - - @JsonProperty - private final String unidentifiedAccess; - - @JsonProperty - private final boolean unrestrictedUnidentifiedAccess; - - @JsonProperty - private final Capabilities capabilities; - - public SignalProfile( - final String identityKey, - final String name, - final File avatarFile, - final String unidentifiedAccess, - final boolean unrestrictedUnidentifiedAccess, - final SignalServiceProfile.Capabilities capabilities - ) { - this.identityKey = identityKey; - this.name = name; - this.avatarFile = avatarFile; - this.unidentifiedAccess = unidentifiedAccess; - this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess; - this.capabilities = new Capabilities(); - this.capabilities.storage = capabilities.isStorage(); - this.capabilities.gv1Migration = capabilities.isGv1Migration(); - this.capabilities.gv2 = capabilities.isGv2(); - } - - public SignalProfile( - @JsonProperty("identityKey") final String identityKey, - @JsonProperty("name") final String name, - @JsonProperty("unidentifiedAccess") final String unidentifiedAccess, - @JsonProperty("unrestrictedUnidentifiedAccess") final boolean unrestrictedUnidentifiedAccess, - @JsonProperty("capabilities") final Capabilities capabilities - ) { - this.identityKey = identityKey; - this.name = name; - this.avatarFile = null; - this.unidentifiedAccess = unidentifiedAccess; - this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess; - this.capabilities = capabilities; - } - - public String getIdentityKey() { - return identityKey; - } - - public String getName() { - return name; - } - - public File getAvatarFile() { - return avatarFile; - } - - public String getUnidentifiedAccess() { - return unidentifiedAccess; - } - - public boolean isUnrestrictedUnidentifiedAccess() { - return unrestrictedUnidentifiedAccess; - } - - public Capabilities getCapabilities() { - return capabilities; - } - - @Override - public String toString() { - return "SignalProfile{" - + "identityKey='" - + identityKey - + '\'' - + ", name='" - + name - + '\'' - + ", avatarFile=" - + avatarFile - + ", unidentifiedAccess='" - + unidentifiedAccess - + '\'' - + ", unrestrictedUnidentifiedAccess=" - + unrestrictedUnidentifiedAccess - + ", capabilities=" - + capabilities - + '}'; - } - - public static class Capabilities { - - @JsonIgnore - public boolean uuid; - - @JsonProperty - public boolean gv2; - - @JsonProperty - public boolean storage; - - @JsonProperty - public boolean gv1Migration; - } -} diff --git a/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java b/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java deleted file mode 100644 index e6acf30d..00000000 --- a/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.asamk.signal.storage.profiles; - -import org.signal.zkgroup.profiles.ProfileKey; -import org.signal.zkgroup.profiles.ProfileKeyCredential; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -public class SignalProfileEntry { - - private final SignalServiceAddress serviceAddress; - - private final ProfileKey profileKey; - - private final long lastUpdateTimestamp; - - private final SignalProfile profile; - - private final ProfileKeyCredential profileKeyCredential; - - private boolean requestPending; - - public SignalProfileEntry( - final SignalServiceAddress serviceAddress, - final ProfileKey profileKey, - final long lastUpdateTimestamp, - final SignalProfile profile, - final ProfileKeyCredential profileKeyCredential - ) { - this.serviceAddress = serviceAddress; - this.profileKey = profileKey; - this.lastUpdateTimestamp = lastUpdateTimestamp; - this.profile = profile; - this.profileKeyCredential = profileKeyCredential; - } - - public SignalServiceAddress getServiceAddress() { - return serviceAddress; - } - - public ProfileKey getProfileKey() { - return profileKey; - } - - public long getLastUpdateTimestamp() { - return lastUpdateTimestamp; - } - - public SignalProfile getProfile() { - return profile; - } - - public ProfileKeyCredential getProfileKeyCredential() { - return profileKeyCredential; - } - - public boolean isRequestPending() { - return requestPending; - } - - public void setRequestPending(final boolean requestPending) { - this.requestPending = requestPending; - } -} diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java deleted file mode 100644 index 29160cf1..00000000 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java +++ /dev/null @@ -1,316 +0,0 @@ -package org.asamk.signal.storage.protocol; - -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import org.asamk.signal.manager.TrustLevel; -import org.asamk.signal.util.Util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.IdentityKeyPair; -import org.whispersystems.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.SignalProtocolAddress; -import org.whispersystems.libsignal.state.IdentityKeyStore; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; -import org.whispersystems.util.Base64; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -public class JsonIdentityKeyStore implements IdentityKeyStore { - - final static Logger logger = LoggerFactory.getLogger(JsonIdentityKeyStore.class); - - private final List identities = new ArrayList<>(); - - private final IdentityKeyPair identityKeyPair; - private final int localRegistrationId; - - private SignalServiceAddressResolver resolver; - - public JsonIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) { - this.identityKeyPair = identityKeyPair; - this.localRegistrationId = localRegistrationId; - } - - public void setResolver(final SignalServiceAddressResolver resolver) { - this.resolver = resolver; - } - - private SignalServiceAddress resolveSignalServiceAddress(String identifier) { - if (resolver != null) { - return resolver.resolveSignalServiceAddress(identifier); - } else { - return Util.getSignalServiceAddressFromIdentifier(identifier); - } - } - - @Override - public IdentityKeyPair getIdentityKeyPair() { - return identityKeyPair; - } - - @Override - public int getLocalRegistrationId() { - return localRegistrationId; - } - - @Override - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { - return saveIdentity(resolveSignalServiceAddress(address.getName()), - identityKey, - TrustLevel.TRUSTED_UNVERIFIED, - null); - } - - /** - * Adds the given identityKey for the user name and sets the trustLevel and added timestamp. - * If the identityKey already exists, the trustLevel and added timestamp are NOT updated. - * - * @param serviceAddress User address, i.e. phone number and/or uuid - * @param identityKey The user's public key - * @param trustLevel Level of trust: untrusted, trusted, trusted and verified - * @param added Added timestamp, if null and the key is newly added, the current time is used. - */ - public boolean saveIdentity( - SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel, Date added - ) { - for (Identity id : identities) { - if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) { - continue; - } - - if (!id.address.getUuid().isPresent() || !id.address.getNumber().isPresent()) { - id.address = serviceAddress; - } - // Identity already exists, not updating the trust level - return true; - } - - identities.add(new Identity(serviceAddress, identityKey, trustLevel, added != null ? added : new Date())); - return false; - } - - /** - * Update trustLevel for the given identityKey for the user name. - * - * @param serviceAddress User address, i.e. phone number and/or uuid - * @param identityKey The user's public key - * @param trustLevel Level of trust: untrusted, trusted, trusted and verified - */ - public void setIdentityTrustLevel( - SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel - ) { - for (Identity id : identities) { - if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) { - continue; - } - - if (!id.address.getUuid().isPresent() || !id.address.getNumber().isPresent()) { - id.address = serviceAddress; - } - id.trustLevel = trustLevel; - return; - } - - identities.add(new Identity(serviceAddress, identityKey, trustLevel, new Date())); - } - - @Override - public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { - // TODO implement possibility for different handling of incoming/outgoing trust decisions - SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); - boolean trustOnFirstUse = true; - - for (Identity id : identities) { - if (!id.address.matches(serviceAddress)) { - continue; - } - - if (id.identityKey.equals(identityKey)) { - return id.isTrusted(); - } else { - trustOnFirstUse = false; - } - } - - return trustOnFirstUse; - } - - @Override - public IdentityKey getIdentity(SignalProtocolAddress address) { - SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); - Identity identity = getIdentity(serviceAddress); - return identity == null ? null : identity.getIdentityKey(); - } - - public Identity getIdentity(SignalServiceAddress serviceAddress) { - long maxDate = 0; - Identity maxIdentity = null; - for (Identity id : this.identities) { - if (!id.address.matches(serviceAddress)) { - continue; - } - - final long time = id.getDateAdded().getTime(); - if (maxIdentity == null || maxDate <= time) { - maxDate = time; - maxIdentity = id; - } - } - return maxIdentity; - } - - public List getIdentities() { - // TODO deep copy - return identities; - } - - public List getIdentities(SignalServiceAddress serviceAddress) { - List identities = new ArrayList<>(); - for (Identity identity : this.identities) { - if (identity.address.matches(serviceAddress)) { - identities.add(identity); - } - } - return identities; - } - - public static class JsonIdentityKeyStoreDeserializer extends JsonDeserializer { - - @Override - public JsonIdentityKeyStore deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - - try { - int localRegistrationId = node.get("registrationId").asInt(); - IdentityKeyPair identityKeyPair = new IdentityKeyPair(Base64.decode(node.get("identityKey").asText())); - - JsonIdentityKeyStore keyStore = new JsonIdentityKeyStore(identityKeyPair, localRegistrationId); - - JsonNode trustedKeysNode = node.get("trustedKeys"); - if (trustedKeysNode.isArray()) { - for (JsonNode trustedKey : trustedKeysNode) { - String trustedKeyName = trustedKey.hasNonNull("name") ? trustedKey.get("name").asText() : null; - - if (UuidUtil.isUuid(trustedKeyName)) { - // Ignore identities that were incorrectly created with UUIDs as name - continue; - } - - UUID uuid = trustedKey.hasNonNull("uuid") ? UuidUtil.parseOrNull(trustedKey.get("uuid") - .asText()) : null; - final SignalServiceAddress serviceAddress = uuid == null - ? Util.getSignalServiceAddressFromIdentifier(trustedKeyName) - : new SignalServiceAddress(uuid, trustedKeyName); - try { - IdentityKey id = new IdentityKey(Base64.decode(trustedKey.get("identityKey").asText()), 0); - TrustLevel trustLevel = trustedKey.has("trustLevel") ? TrustLevel.fromInt(trustedKey.get( - "trustLevel").asInt()) : TrustLevel.TRUSTED_UNVERIFIED; - Date added = trustedKey.has("addedTimestamp") ? new Date(trustedKey.get("addedTimestamp") - .asLong()) : new Date(); - keyStore.saveIdentity(serviceAddress, id, trustLevel, added); - } catch (InvalidKeyException | IOException e) { - logger.warn("Error while decoding key for {}: {}", trustedKeyName, e.getMessage()); - } - } - } - - return keyStore; - } catch (InvalidKeyException e) { - throw new IOException(e); - } - } - } - - public static class JsonIdentityKeyStoreSerializer extends JsonSerializer { - - @Override - public void serialize( - JsonIdentityKeyStore jsonIdentityKeyStore, JsonGenerator json, SerializerProvider serializerProvider - ) throws IOException { - json.writeStartObject(); - json.writeNumberField("registrationId", jsonIdentityKeyStore.getLocalRegistrationId()); - json.writeStringField("identityKey", - Base64.encodeBytes(jsonIdentityKeyStore.getIdentityKeyPair().serialize())); - json.writeArrayFieldStart("trustedKeys"); - for (Identity trustedKey : jsonIdentityKeyStore.identities) { - json.writeStartObject(); - if (trustedKey.getAddress().getNumber().isPresent()) { - json.writeStringField("name", trustedKey.getAddress().getNumber().get()); - } - if (trustedKey.getAddress().getUuid().isPresent()) { - json.writeStringField("uuid", trustedKey.getAddress().getUuid().get().toString()); - } - json.writeStringField("identityKey", Base64.encodeBytes(trustedKey.identityKey.serialize())); - json.writeNumberField("trustLevel", trustedKey.trustLevel.ordinal()); - json.writeNumberField("addedTimestamp", trustedKey.added.getTime()); - json.writeEndObject(); - } - json.writeEndArray(); - json.writeEndObject(); - } - } - - public static class Identity { - - SignalServiceAddress address; - IdentityKey identityKey; - TrustLevel trustLevel; - Date added; - - public Identity(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel) { - this.address = address; - this.identityKey = identityKey; - this.trustLevel = trustLevel; - this.added = new Date(); - } - - Identity(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) { - this.address = address; - this.identityKey = identityKey; - this.trustLevel = trustLevel; - this.added = added; - } - - public SignalServiceAddress getAddress() { - return address; - } - - public void setAddress(final SignalServiceAddress address) { - this.address = address; - } - - boolean isTrusted() { - return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED; - } - - public IdentityKey getIdentityKey() { - return this.identityKey; - } - - public TrustLevel getTrustLevel() { - return this.trustLevel; - } - - public Date getDateAdded() { - return this.added; - } - - public byte[] getFingerprint() { - return identityKey.getPublicKey().serialize(); - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java deleted file mode 100644 index 523809c1..00000000 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.asamk.signal.storage.protocol; - -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.InvalidKeyIdException; -import org.whispersystems.libsignal.state.PreKeyRecord; -import org.whispersystems.libsignal.state.PreKeyStore; -import org.whispersystems.util.Base64; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -class JsonPreKeyStore implements PreKeyStore { - - final static Logger logger = LoggerFactory.getLogger(JsonPreKeyStore.class); - - private final Map store = new HashMap<>(); - - public JsonPreKeyStore() { - - } - - private void addPreKeys(Map preKeys) { - store.putAll(preKeys); - } - - @Override - public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { - try { - if (!store.containsKey(preKeyId)) { - throw new InvalidKeyIdException("No such prekeyrecord!"); - } - - return new PreKeyRecord(store.get(preKeyId)); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - @Override - public void storePreKey(int preKeyId, PreKeyRecord record) { - store.put(preKeyId, record.serialize()); - } - - @Override - public boolean containsPreKey(int preKeyId) { - return store.containsKey(preKeyId); - } - - @Override - public void removePreKey(int preKeyId) { - store.remove(preKeyId); - } - - public static class JsonPreKeyStoreDeserializer extends JsonDeserializer { - - @Override - public JsonPreKeyStore deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - - Map preKeyMap = new HashMap<>(); - if (node.isArray()) { - for (JsonNode preKey : node) { - Integer preKeyId = preKey.get("id").asInt(); - try { - preKeyMap.put(preKeyId, Base64.decode(preKey.get("record").asText())); - } catch (IOException e) { - logger.warn("Error while decoding prekey for {}: {}", preKeyId, e.getMessage()); - } - } - } - - JsonPreKeyStore keyStore = new JsonPreKeyStore(); - keyStore.addPreKeys(preKeyMap); - - return keyStore; - - } - } - - public static class JsonPreKeyStoreSerializer extends JsonSerializer { - - @Override - public void serialize( - JsonPreKeyStore jsonPreKeyStore, JsonGenerator json, SerializerProvider serializerProvider - ) throws IOException { - json.writeStartArray(); - for (Map.Entry preKey : jsonPreKeyStore.store.entrySet()) { - json.writeStartObject(); - json.writeNumberField("id", preKey.getKey()); - json.writeStringField("record", Base64.encodeBytes(preKey.getValue())); - json.writeEndObject(); - } - json.writeEndArray(); - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java deleted file mode 100644 index 24e4594e..00000000 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.asamk.signal.storage.protocol; - -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import org.asamk.signal.util.Util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.SignalProtocolAddress; -import org.whispersystems.libsignal.state.SessionRecord; -import org.whispersystems.libsignal.state.SessionStore; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; -import org.whispersystems.util.Base64; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -class JsonSessionStore implements SessionStore { - - final static Logger logger = LoggerFactory.getLogger(JsonSessionStore.class); - - private final List sessions = new ArrayList<>(); - - private SignalServiceAddressResolver resolver; - - public JsonSessionStore() { - } - - public void setResolver(final SignalServiceAddressResolver resolver) { - this.resolver = resolver; - } - - private SignalServiceAddress resolveSignalServiceAddress(String identifier) { - if (resolver != null) { - return resolver.resolveSignalServiceAddress(identifier); - } else { - return Util.getSignalServiceAddressFromIdentifier(identifier); - } - } - - @Override - public synchronized SessionRecord loadSession(SignalProtocolAddress address) { - SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); - for (SessionInfo info : sessions) { - if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) { - try { - return new SessionRecord(info.sessionRecord); - } catch (IOException e) { - logger.warn("Failed to load session, resetting session: {}", e.getMessage()); - final SessionRecord sessionRecord = new SessionRecord(); - info.sessionRecord = sessionRecord.serialize(); - return sessionRecord; - } - } - } - - return new SessionRecord(); - } - - public synchronized List getSessions() { - return sessions; - } - - @Override - public synchronized List getSubDeviceSessions(String name) { - SignalServiceAddress serviceAddress = resolveSignalServiceAddress(name); - - List deviceIds = new LinkedList<>(); - for (SessionInfo info : sessions) { - if (info.address.matches(serviceAddress) && info.deviceId != 1) { - deviceIds.add(info.deviceId); - } - } - - return deviceIds; - } - - @Override - public synchronized void storeSession(SignalProtocolAddress address, SessionRecord record) { - SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); - for (SessionInfo info : sessions) { - if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) { - if (!info.address.getUuid().isPresent() || !info.address.getNumber().isPresent()) { - info.address = serviceAddress; - } - info.sessionRecord = record.serialize(); - return; - } - } - - sessions.add(new SessionInfo(serviceAddress, address.getDeviceId(), record.serialize())); - } - - @Override - public synchronized boolean containsSession(SignalProtocolAddress address) { - SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); - for (SessionInfo info : sessions) { - if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) { - return true; - } - } - return false; - } - - @Override - public synchronized void deleteSession(SignalProtocolAddress address) { - SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); - sessions.removeIf(info -> info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()); - } - - @Override - public synchronized void deleteAllSessions(String name) { - SignalServiceAddress serviceAddress = resolveSignalServiceAddress(name); - deleteAllSessions(serviceAddress); - } - - public synchronized void deleteAllSessions(SignalServiceAddress serviceAddress) { - sessions.removeIf(info -> info.address.matches(serviceAddress)); - } - - public static class JsonSessionStoreDeserializer extends JsonDeserializer { - - @Override - public JsonSessionStore deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - - JsonSessionStore sessionStore = new JsonSessionStore(); - - if (node.isArray()) { - for (JsonNode session : node) { - String sessionName = session.hasNonNull("name") ? session.get("name").asText() : null; - if (UuidUtil.isUuid(sessionName)) { - // Ignore sessions that were incorrectly created with UUIDs as name - continue; - } - - UUID uuid = session.hasNonNull("uuid") ? UuidUtil.parseOrNull(session.get("uuid").asText()) : null; - final SignalServiceAddress serviceAddress = uuid == null - ? Util.getSignalServiceAddressFromIdentifier(sessionName) - : new SignalServiceAddress(uuid, sessionName); - final int deviceId = session.get("deviceId").asInt(); - final String record = session.get("record").asText(); - try { - SessionInfo sessionInfo = new SessionInfo(serviceAddress, deviceId, Base64.decode(record)); - sessionStore.sessions.add(sessionInfo); - } catch (IOException e) { - logger.warn("Error while decoding session for {}: {}", sessionName, e.getMessage()); - } - } - } - - return sessionStore; - } - } - - public static class JsonSessionStoreSerializer extends JsonSerializer { - - @Override - public void serialize( - JsonSessionStore jsonSessionStore, JsonGenerator json, SerializerProvider serializerProvider - ) throws IOException { - json.writeStartArray(); - for (SessionInfo sessionInfo : jsonSessionStore.sessions) { - json.writeStartObject(); - if (sessionInfo.address.getNumber().isPresent()) { - json.writeStringField("name", sessionInfo.address.getNumber().get()); - } - if (sessionInfo.address.getUuid().isPresent()) { - json.writeStringField("uuid", sessionInfo.address.getUuid().get().toString()); - } - json.writeNumberField("deviceId", sessionInfo.deviceId); - json.writeStringField("record", Base64.encodeBytes(sessionInfo.sessionRecord)); - json.writeEndObject(); - } - json.writeEndArray(); - } - } - -} diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonSignalProtocolStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonSignalProtocolStore.java deleted file mode 100644 index 5939749d..00000000 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonSignalProtocolStore.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.asamk.signal.storage.protocol; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import org.asamk.signal.manager.TrustLevel; -import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.IdentityKeyPair; -import org.whispersystems.libsignal.InvalidKeyIdException; -import org.whispersystems.libsignal.SignalProtocolAddress; -import org.whispersystems.libsignal.state.PreKeyRecord; -import org.whispersystems.libsignal.state.SessionRecord; -import org.whispersystems.libsignal.state.SignalProtocolStore; -import org.whispersystems.libsignal.state.SignedPreKeyRecord; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.util.List; - -public class JsonSignalProtocolStore implements SignalProtocolStore { - - @JsonProperty("preKeys") - @JsonDeserialize(using = JsonPreKeyStore.JsonPreKeyStoreDeserializer.class) - @JsonSerialize(using = JsonPreKeyStore.JsonPreKeyStoreSerializer.class) - private JsonPreKeyStore preKeyStore; - - @JsonProperty("sessionStore") - @JsonDeserialize(using = JsonSessionStore.JsonSessionStoreDeserializer.class) - @JsonSerialize(using = JsonSessionStore.JsonSessionStoreSerializer.class) - private JsonSessionStore sessionStore; - - @JsonProperty("signedPreKeyStore") - @JsonDeserialize(using = JsonSignedPreKeyStore.JsonSignedPreKeyStoreDeserializer.class) - @JsonSerialize(using = JsonSignedPreKeyStore.JsonSignedPreKeyStoreSerializer.class) - private JsonSignedPreKeyStore signedPreKeyStore; - - @JsonProperty("identityKeyStore") - @JsonDeserialize(using = JsonIdentityKeyStore.JsonIdentityKeyStoreDeserializer.class) - @JsonSerialize(using = JsonIdentityKeyStore.JsonIdentityKeyStoreSerializer.class) - private JsonIdentityKeyStore identityKeyStore; - - public JsonSignalProtocolStore() { - } - - public JsonSignalProtocolStore( - JsonPreKeyStore preKeyStore, - JsonSessionStore sessionStore, - JsonSignedPreKeyStore signedPreKeyStore, - JsonIdentityKeyStore identityKeyStore - ) { - this.preKeyStore = preKeyStore; - this.sessionStore = sessionStore; - this.signedPreKeyStore = signedPreKeyStore; - this.identityKeyStore = identityKeyStore; - } - - public JsonSignalProtocolStore(IdentityKeyPair identityKeyPair, int registrationId) { - preKeyStore = new JsonPreKeyStore(); - sessionStore = new JsonSessionStore(); - signedPreKeyStore = new JsonSignedPreKeyStore(); - this.identityKeyStore = new JsonIdentityKeyStore(identityKeyPair, registrationId); - } - - public void setResolver(final SignalServiceAddressResolver resolver) { - sessionStore.setResolver(resolver); - identityKeyStore.setResolver(resolver); - } - - @Override - public IdentityKeyPair getIdentityKeyPair() { - return identityKeyStore.getIdentityKeyPair(); - } - - @Override - public int getLocalRegistrationId() { - return identityKeyStore.getLocalRegistrationId(); - } - - @Override - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { - return identityKeyStore.saveIdentity(address, identityKey); - } - - public void saveIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel) { - identityKeyStore.saveIdentity(serviceAddress, identityKey, trustLevel, null); - } - - public void setIdentityTrustLevel( - SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel - ) { - identityKeyStore.setIdentityTrustLevel(serviceAddress, identityKey, trustLevel); - } - - public List getIdentities() { - return identityKeyStore.getIdentities(); - } - - public List getIdentities(SignalServiceAddress serviceAddress) { - return identityKeyStore.getIdentities(serviceAddress); - } - - @Override - public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { - return identityKeyStore.isTrustedIdentity(address, identityKey, direction); - } - - @Override - public IdentityKey getIdentity(SignalProtocolAddress address) { - return identityKeyStore.getIdentity(address); - } - - public JsonIdentityKeyStore.Identity getIdentity(SignalServiceAddress serviceAddress) { - return identityKeyStore.getIdentity(serviceAddress); - } - - @Override - public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { - return preKeyStore.loadPreKey(preKeyId); - } - - @Override - public void storePreKey(int preKeyId, PreKeyRecord record) { - preKeyStore.storePreKey(preKeyId, record); - } - - @Override - public boolean containsPreKey(int preKeyId) { - return preKeyStore.containsPreKey(preKeyId); - } - - @Override - public void removePreKey(int preKeyId) { - preKeyStore.removePreKey(preKeyId); - } - - @Override - public SessionRecord loadSession(SignalProtocolAddress address) { - return sessionStore.loadSession(address); - } - - public List getSessions() { - return sessionStore.getSessions(); - } - - @Override - public List getSubDeviceSessions(String name) { - return sessionStore.getSubDeviceSessions(name); - } - - @Override - public void storeSession(SignalProtocolAddress address, SessionRecord record) { - sessionStore.storeSession(address, record); - } - - @Override - public boolean containsSession(SignalProtocolAddress address) { - return sessionStore.containsSession(address); - } - - @Override - public void deleteSession(SignalProtocolAddress address) { - sessionStore.deleteSession(address); - } - - @Override - public void deleteAllSessions(String name) { - sessionStore.deleteAllSessions(name); - } - - public void deleteAllSessions(SignalServiceAddress serviceAddress) { - sessionStore.deleteAllSessions(serviceAddress); - } - - @Override - public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { - return signedPreKeyStore.loadSignedPreKey(signedPreKeyId); - } - - @Override - public List loadSignedPreKeys() { - return signedPreKeyStore.loadSignedPreKeys(); - } - - @Override - public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { - signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); - } - - @Override - public boolean containsSignedPreKey(int signedPreKeyId) { - return signedPreKeyStore.containsSignedPreKey(signedPreKeyId); - } - - @Override - public void removeSignedPreKey(int signedPreKeyId) { - signedPreKeyStore.removeSignedPreKey(signedPreKeyId); - } -} diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java deleted file mode 100644 index 7accf5a1..00000000 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.asamk.signal.storage.protocol; - -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.InvalidKeyIdException; -import org.whispersystems.libsignal.state.SignedPreKeyRecord; -import org.whispersystems.libsignal.state.SignedPreKeyStore; -import org.whispersystems.util.Base64; - -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -class JsonSignedPreKeyStore implements SignedPreKeyStore { - - final static Logger logger = LoggerFactory.getLogger(JsonSignedPreKeyStore.class); - - private final Map store = new HashMap<>(); - - public JsonSignedPreKeyStore() { - - } - - private void addSignedPreKeys(Map preKeys) { - store.putAll(preKeys); - } - - @Override - public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { - try { - if (!store.containsKey(signedPreKeyId)) { - throw new InvalidKeyIdException("No such signedprekeyrecord! " + signedPreKeyId); - } - - return new SignedPreKeyRecord(store.get(signedPreKeyId)); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - @Override - public List loadSignedPreKeys() { - try { - List results = new LinkedList<>(); - - for (byte[] serialized : store.values()) { - results.add(new SignedPreKeyRecord(serialized)); - } - - return results; - } catch (IOException e) { - throw new AssertionError(e); - } - } - - @Override - public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { - store.put(signedPreKeyId, record.serialize()); - } - - @Override - public boolean containsSignedPreKey(int signedPreKeyId) { - return store.containsKey(signedPreKeyId); - } - - @Override - public void removeSignedPreKey(int signedPreKeyId) { - store.remove(signedPreKeyId); - } - - public static class JsonSignedPreKeyStoreDeserializer extends JsonDeserializer { - - @Override - public JsonSignedPreKeyStore deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - - Map preKeyMap = new HashMap<>(); - if (node.isArray()) { - for (JsonNode preKey : node) { - Integer preKeyId = preKey.get("id").asInt(); - try { - preKeyMap.put(preKeyId, Base64.decode(preKey.get("record").asText())); - } catch (IOException e) { - logger.warn("Error while decoding prekey for {}: {}", preKeyId, e.getMessage()); - } - } - } - - JsonSignedPreKeyStore keyStore = new JsonSignedPreKeyStore(); - keyStore.addSignedPreKeys(preKeyMap); - - return keyStore; - - } - } - - public static class JsonSignedPreKeyStoreSerializer extends JsonSerializer { - - @Override - public void serialize( - JsonSignedPreKeyStore jsonPreKeyStore, JsonGenerator json, SerializerProvider serializerProvider - ) throws IOException { - json.writeStartArray(); - for (Map.Entry signedPreKey : jsonPreKeyStore.store.entrySet()) { - json.writeStartObject(); - json.writeNumberField("id", signedPreKey.getKey()); - json.writeStringField("record", Base64.encodeBytes(signedPreKey.getValue())); - json.writeEndObject(); - } - json.writeEndArray(); - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java b/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java deleted file mode 100644 index 701eca34..00000000 --- a/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.asamk.signal.storage.protocol; - -import com.fasterxml.jackson.annotation.JsonProperty; -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -public class RecipientStore { - - @JsonProperty("recipientStore") - @JsonDeserialize(using = RecipientStoreDeserializer.class) - @JsonSerialize(using = RecipientStoreSerializer.class) - private final Set addresses = new HashSet<>(); - - public SignalServiceAddress resolveServiceAddress(SignalServiceAddress serviceAddress) { - if (addresses.contains(serviceAddress)) { - // If the Set already contains the exact address with UUID and Number, - // we can just return it here. - return serviceAddress; - } - - for (SignalServiceAddress address : addresses) { - if (address.matches(serviceAddress)) { - return address; - } - } - - if (serviceAddress.getNumber().isPresent() && serviceAddress.getUuid().isPresent()) { - addresses.add(serviceAddress); - } - - return serviceAddress; - } - - public static class RecipientStoreDeserializer extends JsonDeserializer> { - - @Override - public Set deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - - Set addresses = new HashSet<>(); - - if (node.isArray()) { - for (JsonNode recipient : node) { - String recipientName = recipient.get("name").asText(); - UUID uuid = UuidUtil.parseOrThrow(recipient.get("uuid").asText()); - final SignalServiceAddress serviceAddress = new SignalServiceAddress(uuid, recipientName); - addresses.add(serviceAddress); - } - } - - return addresses; - } - } - - public static class RecipientStoreSerializer extends JsonSerializer> { - - @Override - public void serialize( - Set addresses, JsonGenerator json, SerializerProvider serializerProvider - ) throws IOException { - json.writeStartArray(); - for (SignalServiceAddress address : addresses) { - json.writeStartObject(); - json.writeStringField("name", address.getNumber().get()); - json.writeStringField("uuid", address.getUuid().get().toString()); - json.writeEndObject(); - } - json.writeEndArray(); - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/protocol/SessionInfo.java b/src/main/java/org/asamk/signal/storage/protocol/SessionInfo.java deleted file mode 100644 index 00221233..00000000 --- a/src/main/java/org/asamk/signal/storage/protocol/SessionInfo.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.asamk.signal.storage.protocol; - -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -public class SessionInfo { - - public SignalServiceAddress address; - - public int deviceId; - - public byte[] sessionRecord; - - public SessionInfo(final SignalServiceAddress address, final int deviceId, final byte[] sessionRecord) { - this.address = address; - this.deviceId = deviceId; - this.sessionRecord = sessionRecord; - } -} diff --git a/src/main/java/org/asamk/signal/storage/protocol/SignalServiceAddressResolver.java b/src/main/java/org/asamk/signal/storage/protocol/SignalServiceAddressResolver.java deleted file mode 100644 index b1c5fb38..00000000 --- a/src/main/java/org/asamk/signal/storage/protocol/SignalServiceAddressResolver.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.asamk.signal.storage.protocol; - -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -public interface SignalServiceAddressResolver { - - /** - * Get a SignalServiceAddress with number and/or uuid from an identifier name. - * - * @param identifier can be either a serialized uuid or a e164 phone number - */ - SignalServiceAddress resolveSignalServiceAddress(String identifier); -} diff --git a/src/main/java/org/asamk/signal/storage/stickers/Sticker.java b/src/main/java/org/asamk/signal/storage/stickers/Sticker.java deleted file mode 100644 index 386924c4..00000000 --- a/src/main/java/org/asamk/signal/storage/stickers/Sticker.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.asamk.signal.storage.stickers; - -public class Sticker { - - private final byte[] packId; - private final byte[] packKey; - private boolean installed; - - public Sticker(final byte[] packId, final byte[] packKey) { - this.packId = packId; - this.packKey = packKey; - } - - public Sticker(final byte[] packId, final byte[] packKey, final boolean installed) { - this.packId = packId; - this.packKey = packKey; - this.installed = installed; - } - - public byte[] getPackId() { - return packId; - } - - public byte[] getPackKey() { - return packKey; - } - - public boolean isInstalled() { - return installed; - } - - public void setInstalled(final boolean installed) { - this.installed = installed; - } -} diff --git a/src/main/java/org/asamk/signal/storage/stickers/StickerStore.java b/src/main/java/org/asamk/signal/storage/stickers/StickerStore.java deleted file mode 100644 index e5d817d2..00000000 --- a/src/main/java/org/asamk/signal/storage/stickers/StickerStore.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.asamk.signal.storage.stickers; - -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import org.whispersystems.util.Base64; - -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -public class StickerStore { - - private static final ObjectMapper jsonProcessor = new ObjectMapper(); - - @JsonSerialize(using = StickersSerializer.class) - @JsonDeserialize(using = StickersDeserializer.class) - private final Map stickers = new HashMap<>(); - - public Sticker getSticker(byte[] packId) { - return stickers.get(packId); - } - - public void updateSticker(Sticker sticker) { - stickers.put(sticker.getPackId(), sticker); - } - - private static class StickersSerializer extends JsonSerializer> { - - @Override - public void serialize( - final Map value, final JsonGenerator jgen, final SerializerProvider provider - ) throws IOException { - final Collection stickers = value.values(); - jgen.writeStartArray(stickers.size()); - for (Sticker sticker : stickers) { - jgen.writeStartObject(); - jgen.writeStringField("packId", Base64.encodeBytes(sticker.getPackId())); - jgen.writeStringField("packKey", Base64.encodeBytes(sticker.getPackKey())); - jgen.writeBooleanField("installed", sticker.isInstalled()); - jgen.writeEndObject(); - } - jgen.writeEndArray(); - } - } - - private static class StickersDeserializer extends JsonDeserializer> { - - @Override - public Map deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - Map stickers = new HashMap<>(); - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - for (JsonNode n : node) { - byte[] packId = Base64.decode(n.get("packId").asText()); - byte[] packKey = Base64.decode(n.get("packKey").asText()); - boolean installed = n.get("installed").asBoolean(false); - stickers.put(packId, new Sticker(packId, packKey, installed)); - } - - return stickers; - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/threads/LegacyJsonThreadStore.java b/src/main/java/org/asamk/signal/storage/threads/LegacyJsonThreadStore.java deleted file mode 100644 index 24463933..00000000 --- a/src/main/java/org/asamk/signal/storage/threads/LegacyJsonThreadStore.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.asamk.signal.storage.threads; - -import com.fasterxml.jackson.annotation.JsonProperty; -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.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class LegacyJsonThreadStore { - - private static final ObjectMapper jsonProcessor = new ObjectMapper(); - - @JsonProperty("threads") - @JsonSerialize(using = MapToListSerializer.class) - @JsonDeserialize(using = ThreadsDeserializer.class) - private Map threads = new HashMap<>(); - - public List getThreads() { - return new ArrayList<>(threads.values()); - } - - private static class MapToListSerializer extends JsonSerializer> { - - @Override - public void serialize( - final Map value, final JsonGenerator jgen, final SerializerProvider provider - ) throws IOException { - jgen.writeObject(value.values()); - } - } - - private static class ThreadsDeserializer extends JsonDeserializer> { - - @Override - public Map deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext - ) throws IOException { - Map threads = new HashMap<>(); - JsonNode node = jsonParser.getCodec().readTree(jsonParser); - for (JsonNode n : node) { - ThreadInfo t = jsonProcessor.treeToValue(n, ThreadInfo.class); - threads.put(t.id, t); - } - - return threads; - } - } -} diff --git a/src/main/java/org/asamk/signal/storage/threads/ThreadInfo.java b/src/main/java/org/asamk/signal/storage/threads/ThreadInfo.java deleted file mode 100644 index 67e6b474..00000000 --- a/src/main/java/org/asamk/signal/storage/threads/ThreadInfo.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.asamk.signal.storage.threads; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class ThreadInfo { - - @JsonProperty - public String id; - - @JsonProperty - public int messageExpirationTime; -} diff --git a/src/main/resources/org/asamk/signal/manager/ias.store b/src/main/resources/org/asamk/signal/manager/ias.store deleted file mode 100644 index e0b8ec8ccacc48459adb2384402b887682cf45da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1441 zcmZQzU|?ckU=WcLo>6|#P&tUfG}q(0mH|~V zFtA4GnHpFEX@7$zR&RqQ=FJP3nHZUvI2kUom&F<_-Y~_0myJ`a&7dghg+ z<|sJl7Zv0eC6;97=NTFp=z-*zdDJ0t!R`?XjwL0j#U)^Qg`iZR(h`N>%=Em>ymSR; zM*{_MUPCiOLqiioQ)6>u!zc-UBLfplLnC7#GPN|dj2dtkHZd+j4m3tq2IeNleg=ak z#xABN#zuzuHYs&m%WZZC>Z%(Zxb7IYJ9NRXdH3{ECeD1;B)e1Ks$sb~XSrKeR-Aj= znM-$HUlEy`rJt-CH1{IU$#(M%<#VLiAI))JxQ@Zo!#&4izU$J3u~jGD99whDb?p}0 z_Vl@5=WtpdUGy?DC!``odiuOl?{G_zeW922zZ{r*UtMvg$=R8#>YIP+UGLwosgkwt zbfF60=Q+BC>|5MJqUwJOyk_IdSlQa3IL~Y%XHbU4q-#qbt>9pmv~wsmbnr9VESh9K zb5&D7iNo}eMfYEPU4QjovYh;(^%fix@->!ExOsQ}gM`Vmg~BHsZHQ&A{hBp1SbEyM z%zMUfm2wU$)CxFlJ-NEx!T-46fq(9qzo%Agyu)`S^Ocn9ga0!{I`Tec&Q^}z(o%cv zzr!}RXX5OW5B;98StO!<&R^Byr2DTna$8@zSysGo$zGQu!K=R1AN+Lr!snLnUTi<+ zaLngjc6R%pcP~yhN%e(%ieVAE*yGkbE51SK_g&lLzN$%;YY!c`8udS)P01-%nu(c_ zfpKx;NrT2?1_{8_Co9hqX%KD@x*&K#V4HtNNlAf~zJ5tjX>mzvN^xpYS!Qx-v0f%9 z>FOot=jtct1DSax`p!W<`oQF>kCa>?sTG=7ogMX(i*gKPK{oKQh_Q%NI9Hb~tJ`y7 zeVvo}YttI*cE&&(19_0NGK++PSOazid>{q9Px zoDrv>YgGS16DLEx@@1A&Zcd+i;+1Pzlg+EHiw`HBUy<}{$4aBiH#Y=Fu`^u=jW2ns z@R-@)vq{7AwI{EikW(lyblT5wRAU9Vn{ngc1g)my%t4K-o=#0Vv(?yd#RR)lZbr7o zujl%<>8+XL=-HuS^=8ti=#Mt{zrQ}C+4od>SCpy6wrsW2N1_7nG1#m%NHttr^YzMm z{tniQJB4jkgrhbeNs-d;;4qn6Y^)i_SN6{Ps8W$pQs1wJ>vJcq_OlgK`20KSqU71$ z5U+(-mbJxYhwobxUT9FKXS0)?cS*JPpN`B2BEqjYO RcZ)scQZTsQY7d+&%4M^ zza6~JavyCuJ*PQ@rPznB?pQ+1{qy=?EVo@LoL`{N$78?1|F+Pw(^?l-%gp-t1Bm$#Naxay6{qM3DBmjOA4C9ePg diff --git a/src/main/resources/org/asamk/signal/manager/whisper.store b/src/main/resources/org/asamk/signal/manager/whisper.store deleted file mode 100644 index 664ca9563ce30963c1481225f08a7e8438a917c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1107 zcmZQzU|?imU=VpInV_H0#dv$!H|7tD`ioaY?=z@jU|`?Q$RJpfT2WG*np|3xs+*pi ztDBq%)MC4MWBy8@J_ZKX2t88+O9lq!&jwA*?+u!mt}kF_Vq{|CWa!+byV~o?=OhDO zHcqWJkGAi;jEtT!G^*Hf*=kD50`UdPG(wuQC?=Ep_qXP zNRXX}H#jj*!L2AUFFCU~Ip0v$KpG^@#UtWhkea6uo{?ExkXob=Tv=R_np+H$=Efln zwThXCD+K7NV6dkQ)!LfD15%4 z*Umlv;52>a8D?ejY@fI_j5)TY1#fYCS9oK=5eM6upY-<>FbnRE)4Q5*Kyt79?5q3k zX!9>1uJ5n^k4ZRqrY_CoD1zhH!?6T4lwXDkOd|cSw0pq77@m7iI4WbEhSy@THE$b zNi|w-WHZ4)9we>IB4HrbfL#F(NP#d5s{u13O8kyb$Y#W`|}K!gM;_>0*S!DD+U(oQd=*zOusR=S5@{Nwb;<3G^N(vzasL`C zb{(@+-*kSm(01LNGiqf&1)7{^N`I0+RpidP;3em-Rf??{ed_v0L>_G0dOUsk1HBjhEM!iBdvzfUeMcy~b+0PQK5Z2$lO