mirror of
https://github.com/AsamK/signal-cli
synced 2025-09-02 12:30:39 +00:00
Merge branch 'master' of https://github.com/AsamK/signal-cli
This commit is contained in:
commit
edb184ba97
46 changed files with 1264 additions and 585 deletions
22
.idea/codeStyles/Project.xml
generated
22
.idea/codeStyles/Project.xml
generated
|
@ -4,6 +4,28 @@
|
||||||
<JavaCodeStyleSettings>
|
<JavaCodeStyleSettings>
|
||||||
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
||||||
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
|
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
|
||||||
|
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="50" />
|
||||||
|
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="50" />
|
||||||
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
|
<value>
|
||||||
|
<package name="com" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="junit" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="net" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="org" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="java" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="javax" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="true" />
|
||||||
|
<emptyLine />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
<option name="JD_P_AT_EMPTY_LINES" value="false" />
|
<option name="JD_P_AT_EMPTY_LINES" value="false" />
|
||||||
</JavaCodeStyleSettings>
|
</JavaCodeStyleSettings>
|
||||||
<XML>
|
<XML>
|
||||||
|
|
|
@ -7,7 +7,7 @@ targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
|
||||||
mainClassName = 'org.asamk.signal.Main'
|
mainClassName = 'org.asamk.signal.Main'
|
||||||
|
|
||||||
version = '0.6.5'
|
version = '0.6.6'
|
||||||
|
|
||||||
compileJava.options.encoding = 'UTF-8'
|
compileJava.options.encoding = 'UTF-8'
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.github.turasa:signal-service-java:2.15.3_unofficial_3'
|
compile 'com.github.turasa:signal-service-java:2.15.3_unofficial_7'
|
||||||
compile 'org.bouncycastle:bcprov-jdk15on:1.64'
|
compile 'org.bouncycastle:bcprov-jdk15on:1.64'
|
||||||
compile 'net.sourceforge.argparse4j:argparse4j:0.8.1'
|
compile 'net.sourceforge.argparse4j:argparse4j:0.8.1'
|
||||||
compile 'org.freedesktop.dbus:dbus-java:2.7.0'
|
compile 'org.freedesktop.dbus:dbus-java:2.7.0'
|
||||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -287,6 +287,34 @@ sendContacts
|
||||||
Send a synchronization message with the local contacts list to all linked devices.
|
Send a synchronization message with the local contacts list to all linked devices.
|
||||||
This command should only be used if this is the master device.
|
This command should only be used if this is the master device.
|
||||||
|
|
||||||
|
uploadStickerPack
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
Upload a new sticker pack, consisting of a manifest file and the stickers in WebP
|
||||||
|
format (maximum size for a sticker file is 100KiB).
|
||||||
|
The required manifest.json has the following format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "<STICKER_PACK_TITLE>",
|
||||||
|
"author": "<STICKER_PACK_AUTHOR>",
|
||||||
|
"cover": { // Optional cover, by default the first sticker is used as cover
|
||||||
|
"file": "<name of webp file, mandatory>",
|
||||||
|
"emoji": "<optional>"
|
||||||
|
},
|
||||||
|
"stickers": [
|
||||||
|
{
|
||||||
|
"file": "<name of webp file, mandatory>",
|
||||||
|
"emoji": "<optional>"
|
||||||
|
}
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
PATH::
|
||||||
|
The path of the manifest.json or a zip file containing the sticker pack you
|
||||||
|
wish to upload.
|
||||||
|
|
||||||
daemon
|
daemon
|
||||||
~~~~~~
|
~~~~~~
|
||||||
signal-cli can run in daemon mode and provides an experimental dbus interface. For
|
signal-cli can run in daemon mode and provides an experimental dbus interface. For
|
||||||
|
|
|
@ -13,11 +13,11 @@ import java.util.List;
|
||||||
|
|
||||||
public interface Signal extends DBusInterface {
|
public interface Signal extends DBusInterface {
|
||||||
|
|
||||||
void sendMessage(String message, List<String> attachments, String recipient) throws EncapsulatedExceptions, AttachmentInvalidException, IOException;
|
void sendMessage(String message, List<String> attachments, String recipient) throws EncapsulatedExceptions, AttachmentInvalidException, IOException, InvalidNumberException;
|
||||||
|
|
||||||
void sendMessage(String message, List<String> attachments, List<String> recipients) throws EncapsulatedExceptions, AttachmentInvalidException, IOException;
|
void sendMessage(String message, List<String> attachments, List<String> recipients) throws EncapsulatedExceptions, AttachmentInvalidException, IOException, InvalidNumberException;
|
||||||
|
|
||||||
void sendEndSessionMessage(List<String> recipients) throws IOException, EncapsulatedExceptions;
|
void sendEndSessionMessage(List<String> recipients) throws IOException, EncapsulatedExceptions, InvalidNumberException;
|
||||||
|
|
||||||
void sendGroupMessage(String message, List<String> attachments, byte[] groupId) throws EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, IOException;
|
void sendGroupMessage(String message, List<String> attachments, byte[] groupId) throws EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, IOException;
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ public interface Signal extends DBusInterface {
|
||||||
|
|
||||||
List<String> getGroupMembers(byte[] groupId);
|
List<String> getGroupMembers(byte[] groupId);
|
||||||
|
|
||||||
byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException;
|
byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException;
|
||||||
|
|
||||||
boolean isRegistered();
|
boolean isRegistered();
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.asamk.signal;
|
||||||
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -16,8 +17,9 @@ class JsonDataMessage {
|
||||||
|
|
||||||
JsonDataMessage(SignalServiceDataMessage dataMessage) {
|
JsonDataMessage(SignalServiceDataMessage dataMessage) {
|
||||||
this.timestamp = dataMessage.getTimestamp();
|
this.timestamp = dataMessage.getTimestamp();
|
||||||
if (dataMessage.getGroupInfo().isPresent()) {
|
if (dataMessage.getGroupContext().isPresent() && dataMessage.getGroupContext().get().getGroupV1().isPresent()) {
|
||||||
this.groupInfo = new JsonGroupInfo(dataMessage.getGroupInfo().get());
|
SignalServiceGroup groupInfo = dataMessage.getGroupContext().get().getGroupV1().get();
|
||||||
|
this.groupInfo = new JsonGroupInfo(groupInfo);
|
||||||
}
|
}
|
||||||
if (dataMessage.getBody().isPresent()) {
|
if (dataMessage.getBody().isPresent()) {
|
||||||
this.message = dataMessage.getBody().get();
|
this.message = dataMessage.getBody().get();
|
||||||
|
|
|
@ -47,14 +47,15 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
|
||||||
System.out.println(message.getBody().get());
|
System.out.println(message.getBody().get());
|
||||||
|
|
||||||
if (!message.isEndSession() &&
|
if (!message.isEndSession() &&
|
||||||
!(message.getGroupInfo().isPresent() &&
|
!(message.getGroupContext().isPresent() &&
|
||||||
message.getGroupInfo().get().getType() != SignalServiceGroup.Type.DELIVER)) {
|
message.getGroupContext().get().getGroupV1Type() != SignalServiceGroup.Type.DELIVER)) {
|
||||||
try {
|
try {
|
||||||
conn.sendSignal(new Signal.MessageReceived(
|
conn.sendSignal(new Signal.MessageReceived(
|
||||||
objectPath,
|
objectPath,
|
||||||
message.getTimestamp(),
|
message.getTimestamp(),
|
||||||
envelope.isUnidentifiedSender() || !envelope.hasSource() ? content.getSender().getNumber().get() : envelope.getSourceE164().get(),
|
envelope.isUnidentifiedSender() || !envelope.hasSource() ? content.getSender().getNumber().get() : envelope.getSourceE164().get(),
|
||||||
message.getGroupInfo().isPresent() ? message.getGroupInfo().get().getGroupId() : new byte[0],
|
message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()
|
||||||
|
? message.getGroupContext().get().getGroupV1().get().getGroupId() : new byte[0],
|
||||||
message.getBody().isPresent() ? message.getBody().get() : "",
|
message.getBody().isPresent() ? message.getBody().get() : "",
|
||||||
JsonDbusReceiveMessageHandler.getAttachments(message, m)));
|
JsonDbusReceiveMessageHandler.getAttachments(message, m)));
|
||||||
} catch (DBusException e) {
|
} catch (DBusException e) {
|
||||||
|
@ -66,7 +67,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
|
||||||
if (sync_message.getSent().isPresent()) {
|
if (sync_message.getSent().isPresent()) {
|
||||||
SentTranscriptMessage transcript = sync_message.getSent().get();
|
SentTranscriptMessage transcript = sync_message.getSent().get();
|
||||||
|
|
||||||
if (!envelope.isUnidentifiedSender() && envelope.hasSource() && (transcript.getDestination().isPresent() || transcript.getMessage().getGroupInfo().isPresent())) {
|
if (!envelope.isUnidentifiedSender() && envelope.hasSource() && (transcript.getDestination().isPresent() || transcript.getMessage().getGroupContext().isPresent())) {
|
||||||
SignalServiceDataMessage message = transcript.getMessage();
|
SignalServiceDataMessage message = transcript.getMessage();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -75,7 +76,8 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
|
||||||
transcript.getTimestamp(),
|
transcript.getTimestamp(),
|
||||||
envelope.getSourceAddress().getNumber().get(),
|
envelope.getSourceAddress().getNumber().get(),
|
||||||
transcript.getDestination().isPresent() ? transcript.getDestination().get().getNumber().get() : "",
|
transcript.getDestination().isPresent() ? transcript.getDestination().get().getNumber().get() : "",
|
||||||
message.getGroupInfo().isPresent() ? message.getGroupInfo().get().getGroupId() : new byte[0],
|
message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()
|
||||||
|
? message.getGroupContext().get().getGroupV1().get().getGroupId() : new byte[0],
|
||||||
message.getBody().isPresent() ? message.getBody().get() : "",
|
message.getBody().isPresent() ? message.getBody().get() : "",
|
||||||
JsonDbusReceiveMessageHandler.getAttachments(message, m)));
|
JsonDbusReceiveMessageHandler.getAttachments(message, m)));
|
||||||
} catch (DBusException e) {
|
} catch (DBusException e) {
|
||||||
|
|
29
src/main/java/org/asamk/signal/JsonStickerPack.java
Normal file
29
src/main/java/org/asamk/signal/JsonStickerPack.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package org.asamk.signal;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class JsonStickerPack {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String title;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String author;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public JsonSticker cover;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public List<JsonSticker> stickers;
|
||||||
|
|
||||||
|
public static class JsonSticker {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String emoji;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String file;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.asamk.signal.util.SecurityProvider;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.freedesktop.dbus.DBusConnection;
|
import org.freedesktop.dbus.DBusConnection;
|
||||||
import org.freedesktop.dbus.exceptions.DBusException;
|
import org.freedesktop.dbus.exceptions.DBusException;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -105,6 +106,12 @@ public class Main {
|
||||||
ts = m;
|
ts = m;
|
||||||
try {
|
try {
|
||||||
m.init();
|
m.init();
|
||||||
|
} catch (AuthorizationFailedException e) {
|
||||||
|
if (!"register".equals(ns.getString("command"))) {
|
||||||
|
// Register command should still be possible, if current authorization fails
|
||||||
|
System.err.println("Authorization failed, was the number registered elsewhere?");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("Error loading state file: " + e.getMessage());
|
System.err.println("Error loading state file: " + e.getMessage());
|
||||||
return 2;
|
return 2;
|
||||||
|
|
|
@ -5,7 +5,6 @@ import org.asamk.signal.storage.contacts.ContactInfo;
|
||||||
import org.asamk.signal.storage.groups.GroupInfo;
|
import org.asamk.signal.storage.groups.GroupInfo;
|
||||||
import org.asamk.signal.util.DateUtils;
|
import org.asamk.signal.util.DateUtils;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.util.Util;
|
||||||
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||||
|
@ -70,11 +69,6 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
System.out.println("The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
|
System.out.println("The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
|
||||||
System.out.println("Use 'signal-cli -u " + m.getUsername() + " listIdentities -n " + e.getName() + "', verify the key and run 'signal-cli -u " + m.getUsername() + " trust -v \"FINGER_PRINT\" " + e.getName() + "' to mark it as trusted");
|
System.out.println("Use 'signal-cli -u " + m.getUsername() + " listIdentities -n " + e.getName() + "', verify the key and run 'signal-cli -u " + m.getUsername() + " trust -v \"FINGER_PRINT\" " + e.getName() + "' to mark it as trusted");
|
||||||
System.out.println("If you don't care about security, use 'signal-cli -u " + m.getUsername() + " trust -a " + e.getName() + "' to trust it without verification");
|
System.out.println("If you don't care about security, use 'signal-cli -u " + m.getUsername() + " trust -a " + e.getName() + "' to trust it without verification");
|
||||||
} else if (exception instanceof ProtocolUntrustedIdentityException) {
|
|
||||||
ProtocolUntrustedIdentityException e = (ProtocolUntrustedIdentityException) exception;
|
|
||||||
System.out.println("The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
|
|
||||||
System.out.println("Use 'signal-cli -u " + m.getUsername() + " listIdentities -n " + e.getSender() + "', verify the key and run 'signal-cli -u " + m.getUsername() + " trust -v \"FINGER_PRINT\" " + e.getSender() + "' to mark it as trusted");
|
|
||||||
System.out.println("If you don't care about security, use 'signal-cli -u " + m.getUsername() + " trust -a " + e.getSender() + "' to trust it without verification");
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Exception: " + exception.getMessage() + " (" + exception.getClass().getSimpleName() + ")");
|
System.out.println("Exception: " + exception.getMessage() + " (" + exception.getClass().getSimpleName() + ")");
|
||||||
}
|
}
|
||||||
|
@ -109,7 +103,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
System.out.println("Received sync read messages list");
|
System.out.println("Received sync read messages list");
|
||||||
for (ReadMessage rm : syncMessage.getRead().get()) {
|
for (ReadMessage rm : syncMessage.getRead().get()) {
|
||||||
ContactInfo fromContact = m.getContact(rm.getSender().getNumber().get());
|
ContactInfo fromContact = m.getContact(rm.getSender().getNumber().get());
|
||||||
System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender().getNumber() + " Message timestamp: " + DateUtils.formatTimestamp(rm.getTimestamp()));
|
System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender().getNumber().get() + " Message timestamp: " + DateUtils.formatTimestamp(rm.getTimestamp()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (syncMessage.getRequest().isPresent()) {
|
if (syncMessage.getRequest().isPresent()) {
|
||||||
|
@ -129,6 +123,13 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
String dest = sentTranscriptMessage.getDestination().get().getNumber().get();
|
String dest = sentTranscriptMessage.getDestination().get().getNumber().get();
|
||||||
ContactInfo destContact = m.getContact(dest);
|
ContactInfo destContact = m.getContact(dest);
|
||||||
to = (destContact == null ? "" : "“" + destContact.name + "” ") + dest;
|
to = (destContact == null ? "" : "“" + destContact.name + "” ") + dest;
|
||||||
|
} else if (sentTranscriptMessage.getRecipients().size() > 0) {
|
||||||
|
StringBuilder toBuilder = new StringBuilder();
|
||||||
|
for (SignalServiceAddress dest : sentTranscriptMessage.getRecipients()) {
|
||||||
|
ContactInfo destContact = m.getContact(dest.getNumber().get());
|
||||||
|
toBuilder.append(destContact == null ? "" : "“" + destContact.name + "” ").append(dest.getNumber().get()).append(" ");
|
||||||
|
}
|
||||||
|
to = toBuilder.toString();
|
||||||
} else {
|
} else {
|
||||||
to = "Unknown";
|
to = "Unknown";
|
||||||
}
|
}
|
||||||
|
@ -144,14 +145,14 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
System.out.println("Blocked numbers:");
|
System.out.println("Blocked numbers:");
|
||||||
final BlockedListMessage blockedList = syncMessage.getBlockedList().get();
|
final BlockedListMessage blockedList = syncMessage.getBlockedList().get();
|
||||||
for (SignalServiceAddress address : blockedList.getAddresses()) {
|
for (SignalServiceAddress address : blockedList.getAddresses()) {
|
||||||
System.out.println(" - " + address.getNumber());
|
System.out.println(" - " + address.getNumber().get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (syncMessage.getVerified().isPresent()) {
|
if (syncMessage.getVerified().isPresent()) {
|
||||||
System.out.println("Received sync message with verified identities:");
|
System.out.println("Received sync message with verified identities:");
|
||||||
final VerifiedMessage verifiedMessage = syncMessage.getVerified().get();
|
final VerifiedMessage verifiedMessage = syncMessage.getVerified().get();
|
||||||
System.out.println(" - " + verifiedMessage.getDestination() + ": " + verifiedMessage.getVerified());
|
System.out.println(" - " + verifiedMessage.getDestination() + ": " + verifiedMessage.getVerified());
|
||||||
String safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey()));
|
String safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey()));
|
||||||
System.out.println(" " + safetyNumber);
|
System.out.println(" " + safetyNumber);
|
||||||
}
|
}
|
||||||
if (syncMessage.getConfiguration().isPresent()) {
|
if (syncMessage.getConfiguration().isPresent()) {
|
||||||
|
@ -168,7 +169,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
if (syncMessage.getViewOnceOpen().isPresent()) {
|
if (syncMessage.getViewOnceOpen().isPresent()) {
|
||||||
final ViewOnceOpenMessage viewOnceOpenMessage = syncMessage.getViewOnceOpen().get();
|
final ViewOnceOpenMessage viewOnceOpenMessage = syncMessage.getViewOnceOpen().get();
|
||||||
System.out.println("Received sync message with view once open message:");
|
System.out.println("Received sync message with view once open message:");
|
||||||
System.out.println(" - Sender:" + viewOnceOpenMessage.getSender().getNumber());
|
System.out.println(" - Sender:" + viewOnceOpenMessage.getSender().getNumber().get());
|
||||||
System.out.println(" - Timestamp:" + viewOnceOpenMessage.getTimestamp());
|
System.out.println(" - Timestamp:" + viewOnceOpenMessage.getTimestamp());
|
||||||
}
|
}
|
||||||
if (syncMessage.getStickerPackOperations().isPresent()) {
|
if (syncMessage.getStickerPackOperations().isPresent()) {
|
||||||
|
@ -253,8 +254,8 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
if (message.getBody().isPresent()) {
|
if (message.getBody().isPresent()) {
|
||||||
System.out.println("Body: " + message.getBody().get());
|
System.out.println("Body: " + message.getBody().get());
|
||||||
}
|
}
|
||||||
if (message.getGroupInfo().isPresent()) {
|
if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) {
|
||||||
SignalServiceGroup groupInfo = message.getGroupInfo().get();
|
SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
|
||||||
System.out.println("Group info:");
|
System.out.println("Group info:");
|
||||||
System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId()));
|
System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId()));
|
||||||
if (groupInfo.getType() == SignalServiceGroup.Type.UPDATE && groupInfo.getName().isPresent()) {
|
if (groupInfo.getType() == SignalServiceGroup.Type.UPDATE && groupInfo.getName().isPresent()) {
|
||||||
|
@ -322,7 +323,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
final SignalServiceDataMessage.Reaction reaction = message.getReaction().get();
|
final SignalServiceDataMessage.Reaction reaction = message.getReaction().get();
|
||||||
System.out.println("Reaction:");
|
System.out.println("Reaction:");
|
||||||
System.out.println(" - Emoji: " + reaction.getEmoji());
|
System.out.println(" - Emoji: " + reaction.getEmoji());
|
||||||
System.out.println(" - Target author: " + reaction.getTargetAuthor().getNumber());
|
System.out.println(" - Target author: " + reaction.getTargetAuthor().getNumber().get());
|
||||||
System.out.println(" - Target timestamp: " + reaction.getTargetSentTimestamp());
|
System.out.println(" - Target timestamp: " + reaction.getTargetSentTimestamp());
|
||||||
System.out.println(" - Is remove: " + reaction.isRemove());
|
System.out.println(" - Is remove: " + reaction.isRemove());
|
||||||
}
|
}
|
||||||
|
@ -330,7 +331,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
if (message.getQuote().isPresent()) {
|
if (message.getQuote().isPresent()) {
|
||||||
SignalServiceDataMessage.Quote quote = message.getQuote().get();
|
SignalServiceDataMessage.Quote quote = message.getQuote().get();
|
||||||
System.out.println("Quote: (" + quote.getId() + ")");
|
System.out.println("Quote: (" + quote.getId() + ")");
|
||||||
System.out.println(" Author: " + quote.getAuthor().getNumber());
|
System.out.println(" Author: " + quote.getAuthor().getNumber().get());
|
||||||
System.out.println(" Text: " + quote.getText());
|
System.out.println(" Text: " + quote.getText());
|
||||||
if (quote.getAttachments().size() > 0) {
|
if (quote.getAttachments().size() > 0) {
|
||||||
System.out.println(" Attachments: ");
|
System.out.println(" Attachments: ");
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.asamk.signal;
|
||||||
|
|
||||||
|
public class StickerPackInvalidException extends Exception {
|
||||||
|
|
||||||
|
public StickerPackInvalidException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package org.asamk.signal.commands;
|
||||||
|
|
||||||
import net.sourceforge.argparse4j.inf.Namespace;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
import org.asamk.signal.GroupIdFormatException;
|
import org.asamk.signal.GroupIdFormatException;
|
||||||
import org.asamk.signal.GroupNotFoundException;
|
import org.asamk.signal.GroupNotFoundException;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class Commands {
|
||||||
addCommand("updateGroup", new UpdateGroupCommand());
|
addCommand("updateGroup", new UpdateGroupCommand());
|
||||||
addCommand("updateProfile", new UpdateProfileCommand());
|
addCommand("updateProfile", new UpdateProfileCommand());
|
||||||
addCommand("verify", new VerifyCommand());
|
addCommand("verify", new VerifyCommand());
|
||||||
|
addCommand("uploadStickerPack", new UploadStickerPackCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, Command> getCommands() {
|
public static Map<String, Command> getCommands() {
|
||||||
|
|
|
@ -5,9 +5,11 @@ import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.storage.contacts.ContactInfo;
|
import org.asamk.signal.storage.contacts.ContactInfo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ListContactsCommand implements LocalCommand {
|
public class ListContactsCommand implements LocalCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachToSubparser(final Subparser subparser) {
|
public void attachToSubparser(final Subparser subparser) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,20 @@ import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.storage.groups.GroupInfo;
|
import org.asamk.signal.storage.groups.GroupInfo;
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.util.Base64;
|
import org.whispersystems.util.Base64;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ListGroupsCommand implements LocalCommand {
|
public class ListGroupsCommand implements LocalCommand {
|
||||||
|
|
||||||
private static void printGroup(GroupInfo group, boolean detailed, String username) {
|
private static void printGroup(GroupInfo group, boolean detailed, SignalServiceAddress address) {
|
||||||
if (detailed) {
|
if (detailed) {
|
||||||
System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b Members: %s",
|
System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b Members: %s",
|
||||||
Base64.encodeBytes(group.groupId), group.name, group.members.contains(username), group.blocked, group.members));
|
Base64.encodeBytes(group.groupId), group.name, group.isMember(address), group.blocked, group.getMembersE164()));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b",
|
System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b",
|
||||||
Base64.encodeBytes(group.groupId), group.name, group.members.contains(username), group.blocked));
|
Base64.encodeBytes(group.groupId), group.name, group.isMember(address), group.blocked));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ public class ListGroupsCommand implements LocalCommand {
|
||||||
boolean detailed = ns.getBoolean("detailed");
|
boolean detailed = ns.getBoolean("detailed");
|
||||||
|
|
||||||
for (GroupInfo group : groups) {
|
for (GroupInfo group : groups) {
|
||||||
printGroup(group, detailed, m.getUsername());
|
printGroup(group, detailed, m.getSelfAddress());
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,16 @@ import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
|
import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
|
||||||
import org.asamk.signal.util.Hex;
|
import org.asamk.signal.util.Hex;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.util.Util;
|
||||||
import org.whispersystems.libsignal.util.Pair;
|
|
||||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ListIdentitiesCommand implements LocalCommand {
|
public class ListIdentitiesCommand implements LocalCommand {
|
||||||
|
|
||||||
private static void printIdentityFingerprint(Manager m, String theirUsername, JsonIdentityKeyStore.Identity theirId) {
|
private static void printIdentityFingerprint(Manager m, JsonIdentityKeyStore.Identity theirId) {
|
||||||
String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey()));
|
String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirId.getAddress(), theirId.getIdentityKey()));
|
||||||
System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirUsername,
|
System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirId.getAddress().getNumber().orNull(),
|
||||||
theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toStringCondensed(theirId.getFingerprint()), digits));
|
theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toString(theirId.getFingerprint()), digits));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -34,17 +32,15 @@ public class ListIdentitiesCommand implements LocalCommand {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (ns.get("number") == null) {
|
if (ns.get("number") == null) {
|
||||||
for (Map.Entry<String, List<JsonIdentityKeyStore.Identity>> keys : m.getIdentities().entrySet()) {
|
for (JsonIdentityKeyStore.Identity identity : m.getIdentities()) {
|
||||||
for (JsonIdentityKeyStore.Identity id : keys.getValue()) {
|
printIdentityFingerprint(m, identity);
|
||||||
printIdentityFingerprint(m, keys.getKey(), id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String number = ns.getString("number");
|
String number = ns.getString("number");
|
||||||
try {
|
try {
|
||||||
Pair<String, List<JsonIdentityKeyStore.Identity>> key = m.getIdentities(number);
|
List<JsonIdentityKeyStore.Identity> identities = m.getIdentities(number);
|
||||||
for (JsonIdentityKeyStore.Identity id : key.second()) {
|
for (JsonIdentityKeyStore.Identity id : identities) {
|
||||||
printIdentityFingerprint(m, key.first(), id);
|
printIdentityFingerprint(m, id);
|
||||||
}
|
}
|
||||||
} catch (InvalidNumberException e) {
|
} catch (InvalidNumberException e) {
|
||||||
System.out.println("Invalid number: " + e.getMessage());
|
System.out.println("Invalid number: " + e.getMessage());
|
||||||
|
|
|
@ -10,7 +10,6 @@ import org.asamk.signal.ReceiveMessageHandler;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.util.DateUtils;
|
import org.asamk.signal.util.DateUtils;
|
||||||
import org.freedesktop.dbus.DBusConnection;
|
import org.freedesktop.dbus.DBusConnection;
|
||||||
import org.freedesktop.dbus.DBusSigHandler;
|
|
||||||
import org.freedesktop.dbus.exceptions.DBusException;
|
import org.freedesktop.dbus.exceptions.DBusException;
|
||||||
import org.whispersystems.util.Base64;
|
import org.whispersystems.util.Base64;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -22,6 +23,9 @@ public class RegisterCommand implements LocalCommand {
|
||||||
try {
|
try {
|
||||||
m.register(ns.getBoolean("voice"));
|
m.register(ns.getBoolean("voice"));
|
||||||
return 0;
|
return 0;
|
||||||
|
} catch (CaptchaRequiredException e) {
|
||||||
|
System.err.println("Captcha required for verification (" + e.getMessage() + ")");
|
||||||
|
return 1;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println("Request verify error: " + e.getMessage());
|
System.err.println("Request verify error: " + e.getMessage());
|
||||||
return 3;
|
return 3;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.asamk.signal.util.IOUtils;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.util.Util;
|
||||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
||||||
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -25,6 +26,7 @@ import static org.asamk.signal.util.ErrorUtils.handleEncapsulatedExceptions;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
||||||
|
import static org.asamk.signal.util.ErrorUtils.handleInvalidNumberException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
||||||
|
|
||||||
public class SendCommand implements DbusCommand {
|
public class SendCommand implements DbusCommand {
|
||||||
|
@ -75,6 +77,9 @@ public class SendCommand implements DbusCommand {
|
||||||
} catch (DBusExecutionException e) {
|
} catch (DBusExecutionException e) {
|
||||||
handleDBusExecutionException(e);
|
handleDBusExecutionException(e);
|
||||||
return 1;
|
return 1;
|
||||||
|
} catch (InvalidNumberException e) {
|
||||||
|
handleInvalidNumberException(e);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +131,9 @@ public class SendCommand implements DbusCommand {
|
||||||
} catch (GroupIdFormatException e) {
|
} catch (GroupIdFormatException e) {
|
||||||
handleGroupIdFormatException(e);
|
handleGroupIdFormatException(e);
|
||||||
return 1;
|
return 1;
|
||||||
|
} catch (InvalidNumberException e) {
|
||||||
|
handleInvalidNumberException(e);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.asamk.signal.commands;
|
||||||
|
|
||||||
import net.sourceforge.argparse4j.inf.Namespace;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ import org.asamk.signal.GroupNotFoundException;
|
||||||
import org.asamk.signal.NotAGroupMemberException;
|
import org.asamk.signal.NotAGroupMemberException;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.util.Util;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
||||||
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import static org.asamk.signal.util.ErrorUtils.handleEncapsulatedExceptions;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
||||||
|
import static org.asamk.signal.util.ErrorUtils.handleInvalidNumberException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
||||||
|
|
||||||
public class SendReactionCommand implements LocalCommand {
|
public class SendReactionCommand implements LocalCommand {
|
||||||
|
@ -61,7 +62,7 @@ public class SendReactionCommand implements LocalCommand {
|
||||||
|
|
||||||
String emoji = ns.getString("emoji");
|
String emoji = ns.getString("emoji");
|
||||||
boolean isRemove = ns.getBoolean("remove");
|
boolean isRemove = ns.getBoolean("remove");
|
||||||
SignalServiceAddress targetAuthor = new SignalServiceAddress(null, ns.getString("target_author"));
|
String targetAuthor = ns.getString("target_author");
|
||||||
long targetTimestamp = ns.getLong("target_timestamp");
|
long targetTimestamp = ns.getLong("target_timestamp");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -90,6 +91,9 @@ public class SendReactionCommand implements LocalCommand {
|
||||||
} catch (GroupIdFormatException e) {
|
} catch (GroupIdFormatException e) {
|
||||||
handleGroupIdFormatException(e);
|
handleGroupIdFormatException(e);
|
||||||
return 1;
|
return 1;
|
||||||
|
} catch (InvalidNumberException e) {
|
||||||
|
handleInvalidNumberException(e);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@ import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.util.ErrorUtils;
|
||||||
import org.asamk.signal.util.Hex;
|
import org.asamk.signal.util.Hex;
|
||||||
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@ -50,13 +52,25 @@ public class TrustCommand implements LocalCommand {
|
||||||
System.err.println("Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters.");
|
System.err.println("Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
boolean res = m.trustIdentityVerified(number, fingerprintBytes);
|
boolean res;
|
||||||
|
try {
|
||||||
|
res = m.trustIdentityVerified(number, fingerprintBytes);
|
||||||
|
} catch (InvalidNumberException e) {
|
||||||
|
ErrorUtils.handleInvalidNumberException(e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (!res) {
|
if (!res) {
|
||||||
System.err.println("Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct.");
|
System.err.println("Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} else if (fingerprint.length() == 60) {
|
} else if (fingerprint.length() == 60) {
|
||||||
boolean res = m.trustIdentityVerifiedSafetyNumber(number, fingerprint);
|
boolean res;
|
||||||
|
try {
|
||||||
|
res = m.trustIdentityVerifiedSafetyNumber(number, fingerprint);
|
||||||
|
} catch (InvalidNumberException e) {
|
||||||
|
ErrorUtils.handleInvalidNumberException(e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (!res) {
|
if (!res) {
|
||||||
System.err.println("Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct.");
|
System.err.println("Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct.");
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.asamk.signal.commands;
|
||||||
|
|
||||||
import net.sourceforge.argparse4j.inf.Namespace;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
import org.asamk.signal.GroupIdFormatException;
|
import org.asamk.signal.GroupIdFormatException;
|
||||||
import org.asamk.signal.GroupNotFoundException;
|
import org.asamk.signal.GroupNotFoundException;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import org.asamk.signal.GroupNotFoundException;
|
||||||
import org.asamk.signal.NotAGroupMemberException;
|
import org.asamk.signal.NotAGroupMemberException;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.util.Util;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
||||||
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
import org.whispersystems.util.Base64;
|
import org.whispersystems.util.Base64;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -20,6 +21,7 @@ import static org.asamk.signal.util.ErrorUtils.handleEncapsulatedExceptions;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
||||||
|
import static org.asamk.signal.util.ErrorUtils.handleInvalidNumberException;
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
||||||
|
|
||||||
public class UpdateGroupCommand implements DbusCommand {
|
public class UpdateGroupCommand implements DbusCommand {
|
||||||
|
@ -88,6 +90,9 @@ public class UpdateGroupCommand implements DbusCommand {
|
||||||
} catch (GroupIdFormatException e) {
|
} catch (GroupIdFormatException e) {
|
||||||
handleGroupIdFormatException(e);
|
handleGroupIdFormatException(e);
|
||||||
return 1;
|
return 1;
|
||||||
|
} catch (InvalidNumberException e) {
|
||||||
|
handleInvalidNumberException(e);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.asamk.signal.commands;
|
||||||
|
|
||||||
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
|
import org.asamk.signal.StickerPackInvalidException;
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class UploadStickerPackCommand implements LocalCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachToSubparser(final Subparser subparser) {
|
||||||
|
subparser.addArgument("path")
|
||||||
|
.help("The path of the manifest.json or a zip file containing the sticker pack you wish to upload.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleCommand(final Namespace ns, final Manager m) {
|
||||||
|
try {
|
||||||
|
String path = ns.getString("path");
|
||||||
|
String url = m.uploadStickerPack(path);
|
||||||
|
System.out.println(url);
|
||||||
|
return 0;
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Upload error: " + e.getMessage());
|
||||||
|
return 3;
|
||||||
|
} catch (StickerPackInvalidException e) {
|
||||||
|
System.err.println("Invalid sticker pack: " + e.getMessage());
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package org.asamk.signal.manager;
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||||
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
|
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
|
||||||
import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl;
|
import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl;
|
||||||
|
@ -49,6 +50,8 @@ public class BaseConfig {
|
||||||
zkGroupServerPublicParams
|
zkGroupServerPublicParams
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static final SignalServiceProfile.Capabilities capabilities = new SignalServiceProfile.Capabilities(false, false);
|
||||||
|
|
||||||
private BaseConfig() {
|
private BaseConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,10 @@ class KeyUtils {
|
||||||
return getSecretBytes(16);
|
return getSecretBytes(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static byte[] createStickerUploadKey() {
|
||||||
|
return getSecretBytes(32);
|
||||||
|
}
|
||||||
|
|
||||||
private static String getSecret(int size) {
|
private static String getSecret(int size) {
|
||||||
byte[] secret = getSecretBytes(size);
|
byte[] secret = getSecretBytes(size);
|
||||||
return Base64.encodeBytes(secret);
|
return Base64.encodeBytes(secret);
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -13,9 +13,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
|
||||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
|
||||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||||
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
import org.whispersystems.util.Base64;
|
import org.whispersystems.util.Base64;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
@ -35,12 +34,10 @@ import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
|
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
|
||||||
|
|
||||||
|
@ -149,38 +146,19 @@ class Utils {
|
||||||
return new DeviceLinkInfo(deviceIdentifier, deviceKey);
|
return new DeviceLinkInfo(deviceIdentifier, deviceKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Set<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients, String localNumber) {
|
|
||||||
Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
|
|
||||||
for (String recipient : recipients) {
|
|
||||||
try {
|
|
||||||
recipientsTS.add(getPushAddress(recipient, localNumber));
|
|
||||||
} catch (InvalidNumberException e) {
|
|
||||||
System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
|
|
||||||
System.err.println("Aborting sending.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return recipientsTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
|
|
||||||
return PhoneNumberFormatter.formatNumber(number, localNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SignalServiceAddress getPushAddress(String number, String localNumber) throws InvalidNumberException {
|
|
||||||
String e164number = canonicalizeNumber(number, localNumber);
|
|
||||||
return new SignalServiceAddress(null, e164number);
|
|
||||||
}
|
|
||||||
|
|
||||||
static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
|
static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
|
||||||
try (FileInputStream f = new FileInputStream(file)) {
|
try (FileInputStream f = new FileInputStream(file)) {
|
||||||
DataInputStream in = new DataInputStream(f);
|
DataInputStream in = new DataInputStream(f);
|
||||||
int version = in.readInt();
|
int version = in.readInt();
|
||||||
if (version > 2) {
|
if (version > 3) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int type = in.readInt();
|
int type = in.readInt();
|
||||||
String source = in.readUTF();
|
String source = in.readUTF();
|
||||||
|
UUID sourceUuid = null;
|
||||||
|
if (version >= 3) {
|
||||||
|
sourceUuid = UuidUtil.parseOrNull(in.readUTF());
|
||||||
|
}
|
||||||
int sourceDevice = in.readInt();
|
int sourceDevice = in.readInt();
|
||||||
if (version == 1) {
|
if (version == 1) {
|
||||||
// read legacy relay field
|
// read legacy relay field
|
||||||
|
@ -208,16 +186,20 @@ class Utils {
|
||||||
uuid = null;
|
uuid = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new SignalServiceEnvelope(type, Optional.of(new SignalServiceAddress(null, source)), sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
|
Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
|
||||||
|
? Optional.absent()
|
||||||
|
: Optional.of(new SignalServiceAddress(sourceUuid, source));
|
||||||
|
return new SignalServiceEnvelope(type, addressOptional, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
|
static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
|
||||||
try (FileOutputStream f = new FileOutputStream(file)) {
|
try (FileOutputStream f = new FileOutputStream(file)) {
|
||||||
try (DataOutputStream out = new DataOutputStream(f)) {
|
try (DataOutputStream out = new DataOutputStream(f)) {
|
||||||
out.writeInt(2); // version
|
out.writeInt(3); // version
|
||||||
out.writeInt(envelope.getType());
|
out.writeInt(envelope.getType());
|
||||||
out.writeUTF(envelope.getSourceE164().get());
|
out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
|
||||||
|
out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
|
||||||
out.writeInt(envelope.getSourceDevice());
|
out.writeInt(envelope.getSourceDevice());
|
||||||
out.writeLong(envelope.getTimestamp());
|
out.writeLong(envelope.getTimestamp());
|
||||||
if (envelope.hasContent()) {
|
if (envelope.hasContent()) {
|
||||||
|
@ -256,10 +238,25 @@ class Utils {
|
||||||
return outputFile;
|
return outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String computeSafetyNumber(String ownUsername, IdentityKey ownIdentityKey, String theirUsername, IdentityKey theirIdentityKey) {
|
static String computeSafetyNumber(SignalServiceAddress ownAddress, IdentityKey ownIdentityKey, SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
|
||||||
// Version 1: E164 user
|
int version;
|
||||||
|
byte[] ownId;
|
||||||
|
byte[] theirId;
|
||||||
|
|
||||||
|
if (BaseConfig.capabilities.isUuid()
|
||||||
|
&& ownAddress.getUuid().isPresent() && theirAddress.getUuid().isPresent()) {
|
||||||
// Version 2: UUID user
|
// Version 2: UUID user
|
||||||
Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(1, ownUsername.getBytes(), ownIdentityKey, theirUsername.getBytes(), theirIdentityKey);
|
version = 2;
|
||||||
|
ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
|
||||||
|
theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
|
||||||
|
} else {
|
||||||
|
// Version 1: E164 user
|
||||||
|
version = 1;
|
||||||
|
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();
|
return fingerprint.getDisplayableFingerprint().getDisplayText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,14 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import org.asamk.signal.storage.contacts.ContactInfo;
|
||||||
import org.asamk.signal.storage.contacts.JsonContactsStore;
|
import org.asamk.signal.storage.contacts.JsonContactsStore;
|
||||||
|
import org.asamk.signal.storage.groups.GroupInfo;
|
||||||
import org.asamk.signal.storage.groups.JsonGroupStore;
|
import org.asamk.signal.storage.groups.JsonGroupStore;
|
||||||
import org.asamk.signal.storage.protocol.JsonSignalProtocolStore;
|
import org.asamk.signal.storage.protocol.JsonSignalProtocolStore;
|
||||||
import org.asamk.signal.storage.threads.JsonThreadStore;
|
import org.asamk.signal.storage.protocol.SignalServiceAddressResolver;
|
||||||
|
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.IOUtils;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.util.Util;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
|
@ -32,6 +36,7 @@ import java.nio.channels.Channels;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.channels.FileLock;
|
import java.nio.channels.FileLock;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class SignalAccount {
|
public class SignalAccount {
|
||||||
|
|
||||||
|
@ -39,6 +44,7 @@ public class SignalAccount {
|
||||||
private FileChannel fileChannel;
|
private FileChannel fileChannel;
|
||||||
private FileLock lock;
|
private FileLock lock;
|
||||||
private String username;
|
private String username;
|
||||||
|
private UUID uuid;
|
||||||
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||||
private boolean isMultiDevice = false;
|
private boolean isMultiDevice = false;
|
||||||
private String password;
|
private String password;
|
||||||
|
@ -53,7 +59,6 @@ public class SignalAccount {
|
||||||
private JsonSignalProtocolStore signalProtocolStore;
|
private JsonSignalProtocolStore signalProtocolStore;
|
||||||
private JsonGroupStore groupStore;
|
private JsonGroupStore groupStore;
|
||||||
private JsonContactsStore contactStore;
|
private JsonContactsStore contactStore;
|
||||||
private JsonThreadStore threadStore;
|
|
||||||
|
|
||||||
private SignalAccount() {
|
private SignalAccount() {
|
||||||
jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect
|
jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect
|
||||||
|
@ -82,27 +87,26 @@ public class SignalAccount {
|
||||||
account.profileKey = profileKey;
|
account.profileKey = profileKey;
|
||||||
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
|
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
|
||||||
account.groupStore = new JsonGroupStore();
|
account.groupStore = new JsonGroupStore();
|
||||||
account.threadStore = new JsonThreadStore();
|
|
||||||
account.contactStore = new JsonContactsStore();
|
account.contactStore = new JsonContactsStore();
|
||||||
account.registered = false;
|
account.registered = false;
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignalAccount createLinkedAccount(String dataPath, String username, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, ProfileKey profileKey) throws IOException {
|
public static SignalAccount createLinkedAccount(String dataPath, String username, UUID uuid, String password, int deviceId, IdentityKeyPair identityKey, int registrationId, String signalingKey, ProfileKey profileKey) throws IOException {
|
||||||
IOUtils.createPrivateDirectories(dataPath);
|
IOUtils.createPrivateDirectories(dataPath);
|
||||||
|
|
||||||
SignalAccount account = new SignalAccount();
|
SignalAccount account = new SignalAccount();
|
||||||
account.openFileChannel(getFileName(dataPath, username));
|
account.openFileChannel(getFileName(dataPath, username));
|
||||||
|
|
||||||
account.username = username;
|
account.username = username;
|
||||||
|
account.uuid = uuid;
|
||||||
account.password = password;
|
account.password = password;
|
||||||
account.profileKey = profileKey;
|
account.profileKey = profileKey;
|
||||||
account.deviceId = deviceId;
|
account.deviceId = deviceId;
|
||||||
account.signalingKey = signalingKey;
|
account.signalingKey = signalingKey;
|
||||||
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
|
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
|
||||||
account.groupStore = new JsonGroupStore();
|
account.groupStore = new JsonGroupStore();
|
||||||
account.threadStore = new JsonThreadStore();
|
|
||||||
account.contactStore = new JsonContactsStore();
|
account.contactStore = new JsonContactsStore();
|
||||||
account.registered = true;
|
account.registered = true;
|
||||||
account.isMultiDevice = true;
|
account.isMultiDevice = true;
|
||||||
|
@ -138,6 +142,14 @@ public class SignalAccount {
|
||||||
rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
|
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");
|
JsonNode node = rootNode.get("deviceId");
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
deviceId = node.asInt();
|
deviceId = node.asInt();
|
||||||
|
@ -189,10 +201,27 @@ public class SignalAccount {
|
||||||
}
|
}
|
||||||
JsonNode threadStoreNode = rootNode.get("threadStore");
|
JsonNode threadStoreNode = rootNode.get("threadStore");
|
||||||
if (threadStoreNode != null) {
|
if (threadStoreNode != null) {
|
||||||
threadStore = jsonProcessor.convertValue(threadStoreNode, JsonThreadStore.class);
|
LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
|
||||||
|
// Migrate thread info to group and contact store
|
||||||
|
for (ThreadInfo thread : threadStore.getThreads()) {
|
||||||
|
if (thread.id == null || thread.id.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ContactInfo contactInfo = contactStore.getContact(new SignalServiceAddress(null, thread.id));
|
||||||
|
if (contactInfo != null) {
|
||||||
|
contactInfo.messageExpirationTime = thread.messageExpirationTime;
|
||||||
|
contactStore.updateContact(contactInfo);
|
||||||
|
} else {
|
||||||
|
GroupInfo groupInfo = groupStore.getGroup(Base64.decode(thread.id));
|
||||||
|
if (groupInfo != null) {
|
||||||
|
groupInfo.messageExpirationTime = thread.messageExpirationTime;
|
||||||
|
groupStore.updateGroup(groupInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (threadStore == null) {
|
|
||||||
threadStore = new JsonThreadStore();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +231,7 @@ public class SignalAccount {
|
||||||
}
|
}
|
||||||
ObjectNode rootNode = jsonProcessor.createObjectNode();
|
ObjectNode rootNode = jsonProcessor.createObjectNode();
|
||||||
rootNode.put("username", username)
|
rootNode.put("username", username)
|
||||||
|
.put("uuid", uuid == null ? null : uuid.toString())
|
||||||
.put("deviceId", deviceId)
|
.put("deviceId", deviceId)
|
||||||
.put("isMultiDevice", isMultiDevice)
|
.put("isMultiDevice", isMultiDevice)
|
||||||
.put("password", password)
|
.put("password", password)
|
||||||
|
@ -214,7 +244,6 @@ public class SignalAccount {
|
||||||
.putPOJO("axolotlStore", signalProtocolStore)
|
.putPOJO("axolotlStore", signalProtocolStore)
|
||||||
.putPOJO("groupStore", groupStore)
|
.putPOJO("groupStore", groupStore)
|
||||||
.putPOJO("contactStore", contactStore)
|
.putPOJO("contactStore", contactStore)
|
||||||
.putPOJO("threadStore", threadStore)
|
|
||||||
;
|
;
|
||||||
try {
|
try {
|
||||||
synchronized (fileChannel) {
|
synchronized (fileChannel) {
|
||||||
|
@ -245,6 +274,10 @@ public class SignalAccount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResolver(final SignalServiceAddressResolver resolver) {
|
||||||
|
signalProtocolStore.setResolver(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
public void addPreKeys(Collection<PreKeyRecord> records) {
|
public void addPreKeys(Collection<PreKeyRecord> records) {
|
||||||
for (PreKeyRecord record : records) {
|
for (PreKeyRecord record : records) {
|
||||||
signalProtocolStore.storePreKey(record.getId(), record);
|
signalProtocolStore.storePreKey(record.getId(), record);
|
||||||
|
@ -269,16 +302,20 @@ public class SignalAccount {
|
||||||
return contactStore;
|
return contactStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonThreadStore getThreadStore() {
|
|
||||||
return threadStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UUID getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUuid(final UUID uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
public SignalServiceAddress getSelfAddress() {
|
public SignalServiceAddress getSelfAddress() {
|
||||||
return new SignalServiceAddress(null, username);
|
return new SignalServiceAddress(uuid, username);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDeviceId() {
|
public int getDeviceId() {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ContactInfo {
|
public class ContactInfo {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@ -13,9 +15,15 @@ public class ContactInfo {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public String number;
|
public String number;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public UUID uuid;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public String color;
|
public String color;
|
||||||
|
|
||||||
|
@JsonProperty(defaultValue = "0")
|
||||||
|
public int messageExpirationTime;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public String profileKey;
|
public String profileKey;
|
||||||
|
|
||||||
|
@ -28,8 +36,16 @@ public class ContactInfo {
|
||||||
@JsonProperty(defaultValue = "false")
|
@JsonProperty(defaultValue = "false")
|
||||||
public boolean archived;
|
public boolean archived;
|
||||||
|
|
||||||
|
public ContactInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactInfo(SignalServiceAddress address) {
|
||||||
|
this.number = address.getNumber().orNull();
|
||||||
|
this.uuid = address.getUuid().orNull();
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public SignalServiceAddress getAddress() {
|
public SignalServiceAddress getAddress() {
|
||||||
return new SignalServiceAddress(null, number);
|
return new SignalServiceAddress(uuid, number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
package org.asamk.signal.storage.contacts;
|
package org.asamk.signal.storage.contacts;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
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 org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class JsonContactsStore {
|
public class JsonContactsStore {
|
||||||
|
|
||||||
private static final ObjectMapper jsonProcessor = new ObjectMapper();
|
|
||||||
@JsonProperty("contacts")
|
@JsonProperty("contacts")
|
||||||
@JsonSerialize(using = JsonContactsStore.MapToListSerializer.class)
|
private List<ContactInfo> contacts = new ArrayList<>();
|
||||||
@JsonDeserialize(using = ContactsDeserializer.class)
|
|
||||||
private Map<String, ContactInfo> contacts = new HashMap<>();
|
|
||||||
|
|
||||||
public void updateContact(ContactInfo contact) {
|
public void updateContact(ContactInfo contact) {
|
||||||
contacts.put(contact.number, 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactInfo getContact(String number) {
|
contacts.add(contact);
|
||||||
return contacts.get(number);
|
}
|
||||||
|
|
||||||
|
public ContactInfo getContact(SignalServiceAddress address) {
|
||||||
|
for (ContactInfo contact : contacts) {
|
||||||
|
if (contact.getAddress().matches(address)) {
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ContactInfo> getContacts() {
|
public List<ContactInfo> getContacts() {
|
||||||
return new ArrayList<>(contacts.values());
|
return new ArrayList<>(contacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,27 +43,4 @@ public class JsonContactsStore {
|
||||||
public void clear() {
|
public void clear() {
|
||||||
contacts.clear();
|
contacts.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MapToListSerializer extends JsonSerializer<Map<?, ?>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serialize(final Map<?, ?> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
|
|
||||||
jgen.writeObject(value.values());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ContactsDeserializer extends JsonDeserializer<Map<String, ContactInfo>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, ContactInfo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
|
||||||
Map<String, ContactInfo> contacts = new HashMap<>();
|
|
||||||
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
|
|
||||||
for (JsonNode n : node) {
|
|
||||||
ContactInfo c = jsonProcessor.treeToValue(n, ContactInfo.class);
|
|
||||||
contacts.put(c.number, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
return contacts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,29 @@ package org.asamk.signal.storage.groups;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
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.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class GroupInfo {
|
public class GroupInfo {
|
||||||
|
|
||||||
|
private static final ObjectMapper jsonProcessor = new ObjectMapper();
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public final byte[] groupId;
|
public final byte[] groupId;
|
||||||
|
|
||||||
|
@ -18,9 +32,13 @@ public class GroupInfo {
|
||||||
public String name;
|
public String name;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public Set<String> members = new HashSet<>();
|
@JsonDeserialize(using = MembersDeserializer.class)
|
||||||
|
@JsonSerialize(using = MembersSerializer.class)
|
||||||
|
public Set<SignalServiceAddress> members = new HashSet<>();
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public String color;
|
public String color;
|
||||||
|
@JsonProperty(defaultValue = "0")
|
||||||
|
public int messageExpirationTime;
|
||||||
@JsonProperty(defaultValue = "false")
|
@JsonProperty(defaultValue = "false")
|
||||||
public boolean blocked;
|
public boolean blocked;
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@ -38,7 +56,7 @@ public class GroupInfo {
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupInfo(@JsonProperty("groupId") byte[] groupId, @JsonProperty("name") String name, @JsonProperty("members") Collection<String> members, @JsonProperty("avatarId") long avatarId, @JsonProperty("color") String color, @JsonProperty("blocked") boolean blocked, @JsonProperty("inboxPosition") Integer inboxPosition, @JsonProperty("archived") boolean archived) {
|
public GroupInfo(@JsonProperty("groupId") byte[] groupId, @JsonProperty("name") String name, @JsonProperty("members") Collection<SignalServiceAddress> members, @JsonProperty("avatarId") long avatarId, @JsonProperty("color") String color, @JsonProperty("blocked") boolean blocked, @JsonProperty("inboxPosition") Integer inboxPosition, @JsonProperty("archived") boolean archived, @JsonProperty("messageExpirationTime") int messageExpirationTime) {
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.members.addAll(members);
|
this.members.addAll(members);
|
||||||
|
@ -47,6 +65,7 @@ public class GroupInfo {
|
||||||
this.blocked = blocked;
|
this.blocked = blocked;
|
||||||
this.inboxPosition = inboxPosition;
|
this.inboxPosition = inboxPosition;
|
||||||
this.archived = archived;
|
this.archived = archived;
|
||||||
|
this.messageExpirationTime = messageExpirationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
@ -56,16 +75,108 @@ public class GroupInfo {
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public Set<SignalServiceAddress> getMembers() {
|
public Set<SignalServiceAddress> getMembers() {
|
||||||
Set<SignalServiceAddress> addresses = new HashSet<>(members.size());
|
return members;
|
||||||
for (String member : members) {
|
|
||||||
addresses.add(new SignalServiceAddress(null, member));
|
|
||||||
}
|
|
||||||
return addresses;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMembers(Collection<SignalServiceAddress> members) {
|
@JsonIgnore
|
||||||
|
public Set<String> getMembersE164() {
|
||||||
|
Set<String> membersE164 = new HashSet<>();
|
||||||
for (SignalServiceAddress member : members) {
|
for (SignalServiceAddress member : members) {
|
||||||
this.members.add(member.getNumber().get());
|
if (!member.getNumber().isPresent()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
membersE164.add(member.getNumber().get());
|
||||||
|
}
|
||||||
|
return membersE164;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Set<SignalServiceAddress> getMembersWithout(SignalServiceAddress address) {
|
||||||
|
Set<SignalServiceAddress> members = new HashSet<>(this.members.size());
|
||||||
|
for (SignalServiceAddress member : this.members) {
|
||||||
|
if (!member.matches(address)) {
|
||||||
|
members.add(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMembers(Collection<SignalServiceAddress> addresses) {
|
||||||
|
for (SignalServiceAddress address : addresses) {
|
||||||
|
removeMember(address);
|
||||||
|
this.members.add(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMember(SignalServiceAddress address) {
|
||||||
|
this.members.removeIf(member -> member.matches(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isMember(SignalServiceAddress address) {
|
||||||
|
for (SignalServiceAddress member : this.members) {
|
||||||
|
if (member.matches(address)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Set<SignalServiceAddress>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(final Set<SignalServiceAddress> 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<Set<SignalServiceAddress>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<SignalServiceAddress> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
||||||
|
Set<SignalServiceAddress> 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,32 +9,48 @@ import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
|
||||||
import org.asamk.signal.TrustLevel;
|
import org.asamk.signal.TrustLevel;
|
||||||
|
import org.asamk.signal.util.Util;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||||
import org.whispersystems.libsignal.state.IdentityKeyStore;
|
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 org.whispersystems.util.Base64;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class JsonIdentityKeyStore implements IdentityKeyStore {
|
public class JsonIdentityKeyStore implements IdentityKeyStore {
|
||||||
|
|
||||||
private final Map<String, List<Identity>> trustedKeys = new HashMap<>();
|
private final List<Identity> identities = new ArrayList<>();
|
||||||
|
|
||||||
private final IdentityKeyPair identityKeyPair;
|
private final IdentityKeyPair identityKeyPair;
|
||||||
private final int localRegistrationId;
|
private final int localRegistrationId;
|
||||||
|
|
||||||
|
private SignalServiceAddressResolver resolver;
|
||||||
|
|
||||||
public JsonIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) {
|
public JsonIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) {
|
||||||
this.identityKeyPair = identityKeyPair;
|
this.identityKeyPair = identityKeyPair;
|
||||||
this.localRegistrationId = localRegistrationId;
|
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
|
@Override
|
||||||
public IdentityKeyPair getIdentityKeyPair() {
|
public IdentityKeyPair getIdentityKeyPair() {
|
||||||
return identityKeyPair;
|
return identityKeyPair;
|
||||||
|
@ -47,85 +63,116 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||||
return saveIdentity(address.getName(), identityKey, TrustLevel.TRUSTED_UNVERIFIED, null);
|
return saveIdentity(resolveSignalServiceAddress(address.getName()), identityKey, TrustLevel.TRUSTED_UNVERIFIED, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds or updates the given identityKey for the user name and sets the trustLevel and added timestamp.
|
* 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 name User name, i.e. phone number
|
* @param serviceAddress User address, i.e. phone number and/or uuid
|
||||||
* @param identityKey The user's public key
|
* @param identityKey The user's public key
|
||||||
* @param trustLevel
|
* @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.
|
* @param added Added timestamp, if null and the key is newly added, the current time is used.
|
||||||
*/
|
*/
|
||||||
public boolean saveIdentity(String name, IdentityKey identityKey, TrustLevel trustLevel, Date added) {
|
public boolean saveIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel, Date added) {
|
||||||
List<Identity> identities = trustedKeys.get(name);
|
|
||||||
if (identities == null) {
|
|
||||||
identities = new ArrayList<>();
|
|
||||||
trustedKeys.put(name, identities);
|
|
||||||
} else {
|
|
||||||
for (Identity id : identities) {
|
for (Identity id : identities) {
|
||||||
if (!id.identityKey.equals(identityKey))
|
if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (id.trustLevel.compareTo(trustLevel) < 0) {
|
if (!id.address.getUuid().isPresent() || !id.address.getNumber().isPresent()) {
|
||||||
id.trustLevel = trustLevel;
|
id.address = serviceAddress;
|
||||||
}
|
|
||||||
if (added != null) {
|
|
||||||
id.added = added;
|
|
||||||
}
|
}
|
||||||
|
// Identity already exists, not updating the trust level
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
identities.add(new Identity(identityKey, trustLevel, added != null ? added : new Date()));
|
identities.add(new Identity(serviceAddress, identityKey, trustLevel, added != null ? added : new Date()));
|
||||||
return false;
|
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
|
@Override
|
||||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||||
// TODO implement possibility for different handling of incoming/outgoing trust decisions
|
// TODO implement possibility for different handling of incoming/outgoing trust decisions
|
||||||
List<Identity> identities = trustedKeys.get(address.getName());
|
SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName());
|
||||||
if (identities == null) {
|
boolean trustOnFirstUse = true;
|
||||||
// Trust on first use
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Identity id : identities) {
|
for (Identity id : identities) {
|
||||||
|
if (!id.address.matches(serviceAddress)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (id.identityKey.equals(identityKey)) {
|
if (id.identityKey.equals(identityKey)) {
|
||||||
return id.isTrusted();
|
return id.isTrusted();
|
||||||
|
} else {
|
||||||
|
trustOnFirstUse = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return trustOnFirstUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||||
List<Identity> identities = trustedKeys.get(address.getName());
|
SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName());
|
||||||
if (identities == null || identities.size() == 0) {
|
Identity identity = getIdentity(serviceAddress);
|
||||||
return null;
|
return identity == null ? null : identity.getIdentityKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Identity getIdentity(SignalServiceAddress serviceAddress) {
|
||||||
long maxDate = 0;
|
long maxDate = 0;
|
||||||
Identity maxIdentity = null;
|
Identity maxIdentity = null;
|
||||||
for (Identity id : identities) {
|
for (Identity id : this.identities) {
|
||||||
|
if (!id.address.matches(serviceAddress)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
final long time = id.getDateAdded().getTime();
|
final long time = id.getDateAdded().getTime();
|
||||||
if (maxIdentity == null || maxDate <= time) {
|
if (maxIdentity == null || maxDate <= time) {
|
||||||
maxDate = time;
|
maxDate = time;
|
||||||
maxIdentity = id;
|
maxIdentity = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return maxIdentity.getIdentityKey();
|
return maxIdentity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, List<Identity>> getIdentities() {
|
public List<Identity> getIdentities() {
|
||||||
// TODO deep copy
|
// TODO deep copy
|
||||||
return trustedKeys;
|
return identities;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Identity> getIdentities(String name) {
|
public List<Identity> getIdentities(SignalServiceAddress serviceAddress) {
|
||||||
// TODO deep copy
|
List<Identity> identities = new ArrayList<>();
|
||||||
return trustedKeys.get(name);
|
for (Identity identity : this.identities) {
|
||||||
|
if (identity.address.matches(serviceAddress)) {
|
||||||
|
identities.add(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return identities;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class JsonIdentityKeyStoreDeserializer extends JsonDeserializer<JsonIdentityKeyStore> {
|
public static class JsonIdentityKeyStoreDeserializer extends JsonDeserializer<JsonIdentityKeyStore> {
|
||||||
|
@ -143,12 +190,26 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
|
||||||
JsonNode trustedKeysNode = node.get("trustedKeys");
|
JsonNode trustedKeysNode = node.get("trustedKeys");
|
||||||
if (trustedKeysNode.isArray()) {
|
if (trustedKeysNode.isArray()) {
|
||||||
for (JsonNode trustedKey : trustedKeysNode) {
|
for (JsonNode trustedKey : trustedKeysNode) {
|
||||||
String trustedKeyName = trustedKey.get("name").asText();
|
String trustedKeyName = trustedKey.has("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 {
|
try {
|
||||||
IdentityKey id = new IdentityKey(Base64.decode(trustedKey.get("identityKey").asText()), 0);
|
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;
|
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();
|
Date added = trustedKey.has("addedTimestamp") ? new Date(trustedKey.get("addedTimestamp").asLong()) : new Date();
|
||||||
keyStore.saveIdentity(trustedKeyName, id, trustLevel, added);
|
keyStore.saveIdentity(serviceAddress, id, trustLevel, added);
|
||||||
} catch (InvalidKeyException | IOException e) {
|
} catch (InvalidKeyException | IOException e) {
|
||||||
System.out.println(String.format("Error while decoding key for: %s", trustedKeyName));
|
System.out.println(String.format("Error while decoding key for: %s", trustedKeyName));
|
||||||
}
|
}
|
||||||
|
@ -170,15 +231,18 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
|
||||||
json.writeNumberField("registrationId", jsonIdentityKeyStore.getLocalRegistrationId());
|
json.writeNumberField("registrationId", jsonIdentityKeyStore.getLocalRegistrationId());
|
||||||
json.writeStringField("identityKey", Base64.encodeBytes(jsonIdentityKeyStore.getIdentityKeyPair().serialize()));
|
json.writeStringField("identityKey", Base64.encodeBytes(jsonIdentityKeyStore.getIdentityKeyPair().serialize()));
|
||||||
json.writeArrayFieldStart("trustedKeys");
|
json.writeArrayFieldStart("trustedKeys");
|
||||||
for (Map.Entry<String, List<Identity>> trustedKey : jsonIdentityKeyStore.trustedKeys.entrySet()) {
|
for (Identity trustedKey : jsonIdentityKeyStore.identities) {
|
||||||
for (Identity id : trustedKey.getValue()) {
|
|
||||||
json.writeStartObject();
|
json.writeStartObject();
|
||||||
json.writeStringField("name", trustedKey.getKey());
|
if (trustedKey.getAddress().getNumber().isPresent()) {
|
||||||
json.writeStringField("identityKey", Base64.encodeBytes(id.identityKey.serialize()));
|
json.writeStringField("name", trustedKey.getAddress().getNumber().get());
|
||||||
json.writeNumberField("trustLevel", id.trustLevel.ordinal());
|
|
||||||
json.writeNumberField("addedTimestamp", id.added.getTime());
|
|
||||||
json.writeEndObject();
|
|
||||||
}
|
}
|
||||||
|
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.writeEndArray();
|
||||||
json.writeEndObject();
|
json.writeEndObject();
|
||||||
|
@ -187,22 +251,33 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
|
||||||
|
|
||||||
public static class Identity {
|
public static class Identity {
|
||||||
|
|
||||||
|
SignalServiceAddress address;
|
||||||
IdentityKey identityKey;
|
IdentityKey identityKey;
|
||||||
TrustLevel trustLevel;
|
TrustLevel trustLevel;
|
||||||
Date added;
|
Date added;
|
||||||
|
|
||||||
public Identity(IdentityKey identityKey, TrustLevel trustLevel) {
|
public Identity(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel) {
|
||||||
|
this.address = address;
|
||||||
this.identityKey = identityKey;
|
this.identityKey = identityKey;
|
||||||
this.trustLevel = trustLevel;
|
this.trustLevel = trustLevel;
|
||||||
this.added = new Date();
|
this.added = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
Identity(IdentityKey identityKey, TrustLevel trustLevel, Date added) {
|
Identity(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) {
|
||||||
|
this.address = address;
|
||||||
this.identityKey = identityKey;
|
this.identityKey = identityKey;
|
||||||
this.trustLevel = trustLevel;
|
this.trustLevel = trustLevel;
|
||||||
this.added = added;
|
this.added = added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SignalServiceAddress getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(final SignalServiceAddress address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
boolean isTrusted() {
|
boolean isTrusted() {
|
||||||
return trustLevel == TrustLevel.TRUSTED_UNVERIFIED ||
|
return trustLevel == TrustLevel.TRUSTED_UNVERIFIED ||
|
||||||
trustLevel == TrustLevel.TRUSTED_VERIFIED;
|
trustLevel == TrustLevel.TRUSTED_VERIFIED;
|
||||||
|
|
|
@ -8,51 +8,68 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
|
||||||
|
import org.asamk.signal.util.Util;
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||||
import org.whispersystems.libsignal.state.SessionRecord;
|
import org.whispersystems.libsignal.state.SessionRecord;
|
||||||
import org.whispersystems.libsignal.state.SessionStore;
|
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 org.whispersystems.util.Base64;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.UUID;
|
||||||
|
|
||||||
class JsonSessionStore implements SessionStore {
|
class JsonSessionStore implements SessionStore {
|
||||||
|
|
||||||
private final Map<SignalProtocolAddress, byte[]> sessions = new HashMap<>();
|
private final List<SessionInfo> sessions = new ArrayList<>();
|
||||||
|
|
||||||
|
private SignalServiceAddressResolver resolver;
|
||||||
|
|
||||||
public JsonSessionStore() {
|
public JsonSessionStore() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSessions(Map<SignalProtocolAddress, byte[]> sessions) {
|
public void setResolver(final SignalServiceAddressResolver resolver) {
|
||||||
this.sessions.putAll(sessions);
|
this.resolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SignalServiceAddress resolveSignalServiceAddress(String identifier) {
|
||||||
|
if (resolver != null) {
|
||||||
|
return resolver.resolveSignalServiceAddress(identifier);
|
||||||
|
} else {
|
||||||
|
return Util.getSignalServiceAddressFromIdentifier(identifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized SessionRecord loadSession(SignalProtocolAddress remoteAddress) {
|
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 {
|
try {
|
||||||
if (containsSession(remoteAddress)) {
|
return new SessionRecord(info.sessionRecord);
|
||||||
return new SessionRecord(sessions.get(remoteAddress));
|
|
||||||
} else {
|
|
||||||
return new SessionRecord();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new AssertionError(e);
|
System.err.println("Failed to load session, resetting session: " + e);
|
||||||
|
final SessionRecord sessionRecord = new SessionRecord();
|
||||||
|
info.sessionRecord = sessionRecord.serialize();
|
||||||
|
return sessionRecord;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SessionRecord();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized List<Integer> getSubDeviceSessions(String name) {
|
public synchronized List<Integer> getSubDeviceSessions(String name) {
|
||||||
List<Integer> deviceIds = new LinkedList<>();
|
SignalServiceAddress serviceAddress = resolveSignalServiceAddress(name);
|
||||||
|
|
||||||
for (SignalProtocolAddress key : sessions.keySet()) {
|
List<Integer> deviceIds = new LinkedList<>();
|
||||||
if (key.getName().equals(name) &&
|
for (SessionInfo info : sessions) {
|
||||||
key.getDeviceId() != 1) {
|
if (info.address.matches(serviceAddress) && info.deviceId != 1) {
|
||||||
deviceIds.add(key.getDeviceId());
|
deviceIds.add(info.deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,26 +78,45 @@ class JsonSessionStore implements SessionStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void storeSession(SignalProtocolAddress address, SessionRecord record) {
|
public synchronized void storeSession(SignalProtocolAddress address, SessionRecord record) {
|
||||||
sessions.put(address, record.serialize());
|
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
|
@Override
|
||||||
public synchronized boolean containsSession(SignalProtocolAddress address) {
|
public synchronized boolean containsSession(SignalProtocolAddress address) {
|
||||||
return sessions.containsKey(address);
|
SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName());
|
||||||
|
for (SessionInfo info : sessions) {
|
||||||
|
if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void deleteSession(SignalProtocolAddress address) {
|
public synchronized void deleteSession(SignalProtocolAddress address) {
|
||||||
sessions.remove(address);
|
SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName());
|
||||||
|
sessions.removeIf(info -> info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void deleteAllSessions(String name) {
|
public synchronized void deleteAllSessions(String name) {
|
||||||
for (SignalProtocolAddress key : new ArrayList<>(sessions.keySet())) {
|
SignalServiceAddress serviceAddress = resolveSignalServiceAddress(name);
|
||||||
if (key.getName().equals(name)) {
|
deleteAllSessions(serviceAddress);
|
||||||
sessions.remove(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void deleteAllSessions(SignalServiceAddress serviceAddress) {
|
||||||
|
sessions.removeIf(info -> info.address.matches(serviceAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class JsonSessionStoreDeserializer extends JsonDeserializer<JsonSessionStore> {
|
public static class JsonSessionStoreDeserializer extends JsonDeserializer<JsonSessionStore> {
|
||||||
|
@ -89,23 +125,36 @@ class JsonSessionStore implements SessionStore {
|
||||||
public JsonSessionStore deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
public JsonSessionStore deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
||||||
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
|
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
|
||||||
|
|
||||||
Map<SignalProtocolAddress, byte[]> sessionMap = new HashMap<>();
|
JsonSessionStore sessionStore = new JsonSessionStore();
|
||||||
|
|
||||||
if (node.isArray()) {
|
if (node.isArray()) {
|
||||||
for (JsonNode session : node) {
|
for (JsonNode session : node) {
|
||||||
String sessionName = session.get("name").asText();
|
String sessionName = session.has("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 {
|
try {
|
||||||
sessionMap.put(new SignalProtocolAddress(sessionName, session.get("deviceId").asInt()), Base64.decode(session.get("record").asText()));
|
SessionInfo sessionInfo = new SessionInfo(serviceAddress, deviceId, Base64.decode(record));
|
||||||
|
sessionStore.sessions.add(sessionInfo);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.println(String.format("Error while decoding session for: %s", sessionName));
|
System.out.println(String.format("Error while decoding session for: %s", sessionName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonSessionStore sessionStore = new JsonSessionStore();
|
|
||||||
sessionStore.addSessions(sessionMap);
|
|
||||||
|
|
||||||
return sessionStore;
|
return sessionStore;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,14 +163,20 @@ class JsonSessionStore implements SessionStore {
|
||||||
@Override
|
@Override
|
||||||
public void serialize(JsonSessionStore jsonSessionStore, JsonGenerator json, SerializerProvider serializerProvider) throws IOException {
|
public void serialize(JsonSessionStore jsonSessionStore, JsonGenerator json, SerializerProvider serializerProvider) throws IOException {
|
||||||
json.writeStartArray();
|
json.writeStartArray();
|
||||||
for (Map.Entry<SignalProtocolAddress, byte[]> preKey : jsonSessionStore.sessions.entrySet()) {
|
for (SessionInfo sessionInfo : jsonSessionStore.sessions) {
|
||||||
json.writeStartObject();
|
json.writeStartObject();
|
||||||
json.writeStringField("name", preKey.getKey().getName());
|
if (sessionInfo.address.getNumber().isPresent()) {
|
||||||
json.writeNumberField("deviceId", preKey.getKey().getDeviceId());
|
json.writeStringField("name", sessionInfo.address.getNumber().get());
|
||||||
json.writeStringField("record", Base64.encodeBytes(preKey.getValue()));
|
}
|
||||||
|
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.writeEndObject();
|
||||||
}
|
}
|
||||||
json.writeEndArray();
|
json.writeEndArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
import org.whispersystems.libsignal.state.SessionRecord;
|
import org.whispersystems.libsignal.state.SessionRecord;
|
||||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class JsonSignalProtocolStore implements SignalProtocolStore {
|
public class JsonSignalProtocolStore implements SignalProtocolStore {
|
||||||
|
|
||||||
|
@ -56,6 +56,11 @@ public class JsonSignalProtocolStore implements SignalProtocolStore {
|
||||||
this.identityKeyStore = new JsonIdentityKeyStore(identityKeyPair, registrationId);
|
this.identityKeyStore = new JsonIdentityKeyStore(identityKeyPair, registrationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResolver(final SignalServiceAddressResolver resolver) {
|
||||||
|
sessionStore.setResolver(resolver);
|
||||||
|
identityKeyStore.setResolver(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdentityKeyPair getIdentityKeyPair() {
|
public IdentityKeyPair getIdentityKeyPair() {
|
||||||
return identityKeyStore.getIdentityKeyPair();
|
return identityKeyStore.getIdentityKeyPair();
|
||||||
|
@ -71,16 +76,20 @@ public class JsonSignalProtocolStore implements SignalProtocolStore {
|
||||||
return identityKeyStore.saveIdentity(address, identityKey);
|
return identityKeyStore.saveIdentity(address, identityKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveIdentity(String name, IdentityKey identityKey, TrustLevel trustLevel) {
|
public void saveIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel) {
|
||||||
identityKeyStore.saveIdentity(name, identityKey, trustLevel, null);
|
identityKeyStore.saveIdentity(serviceAddress, identityKey, trustLevel, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, List<JsonIdentityKeyStore.Identity>> getIdentities() {
|
public void setIdentityTrustLevel(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel) {
|
||||||
|
identityKeyStore.setIdentityTrustLevel(serviceAddress, identityKey, trustLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JsonIdentityKeyStore.Identity> getIdentities() {
|
||||||
return identityKeyStore.getIdentities();
|
return identityKeyStore.getIdentities();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JsonIdentityKeyStore.Identity> getIdentities(String name) {
|
public List<JsonIdentityKeyStore.Identity> getIdentities(SignalServiceAddress serviceAddress) {
|
||||||
return identityKeyStore.getIdentities(name);
|
return identityKeyStore.getIdentities(serviceAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -93,6 +102,10 @@ public class JsonSignalProtocolStore implements SignalProtocolStore {
|
||||||
return identityKeyStore.getIdentity(address);
|
return identityKeyStore.getIdentity(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JsonIdentityKeyStore.Identity getIdentity(SignalServiceAddress serviceAddress) {
|
||||||
|
return identityKeyStore.getIdentity(serviceAddress);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||||
return preKeyStore.loadPreKey(preKeyId);
|
return preKeyStore.loadPreKey(preKeyId);
|
||||||
|
@ -143,6 +156,10 @@ public class JsonSignalProtocolStore implements SignalProtocolStore {
|
||||||
sessionStore.deleteAllSessions(name);
|
sessionStore.deleteAllSessions(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteAllSessions(SignalServiceAddress serviceAddress) {
|
||||||
|
sessionStore.deleteAllSessions(serviceAddress);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||||
return signedPreKeyStore.loadSignedPreKey(signedPreKeyId);
|
return signedPreKeyStore.loadSignedPreKey(signedPreKeyId);
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
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);
|
||||||
|
}
|
|
@ -18,23 +18,15 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class JsonThreadStore {
|
public class LegacyJsonThreadStore {
|
||||||
|
|
||||||
private static final ObjectMapper jsonProcessor = new ObjectMapper();
|
private static final ObjectMapper jsonProcessor = new ObjectMapper();
|
||||||
|
|
||||||
@JsonProperty("threads")
|
@JsonProperty("threads")
|
||||||
@JsonSerialize(using = JsonThreadStore.MapToListSerializer.class)
|
@JsonSerialize(using = MapToListSerializer.class)
|
||||||
@JsonDeserialize(using = ThreadsDeserializer.class)
|
@JsonDeserialize(using = ThreadsDeserializer.class)
|
||||||
private Map<String, ThreadInfo> threads = new HashMap<>();
|
private Map<String, ThreadInfo> threads = new HashMap<>();
|
||||||
|
|
||||||
public void updateThread(ThreadInfo thread) {
|
|
||||||
threads.put(thread.id, thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ThreadInfo getThread(String id) {
|
|
||||||
return threads.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ThreadInfo> getThreads() {
|
public List<ThreadInfo> getThreads() {
|
||||||
return new ArrayList<>(threads.values());
|
return new ArrayList<>(threads.values());
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
|
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||||
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -59,4 +60,10 @@ public class ErrorUtils {
|
||||||
System.err.println(e.getMessage());
|
System.err.println(e.getMessage());
|
||||||
System.err.println("Aborting sending.");
|
System.err.println("Aborting sending.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void handleInvalidNumberException(InvalidNumberException e) {
|
||||||
|
System.err.println("Failed to parse recipient: " + e.getMessage());
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
System.err.println("Aborting sending.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,15 @@ public class Hex {
|
||||||
private Hex() {
|
private Hex() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String toString(byte[] bytes) {
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
for (final byte aByte : bytes) {
|
||||||
|
appendHexChar(buf, aByte);
|
||||||
|
buf.append(" ");
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static String toStringCondensed(byte[] bytes) {
|
public static String toStringCondensed(byte[] bytes) {
|
||||||
StringBuffer buf = new StringBuffer();
|
StringBuffer buf = new StringBuffer();
|
||||||
for (final byte aByte : bytes) {
|
for (final byte aByte : bytes) {
|
||||||
|
@ -20,7 +29,6 @@ public class Hex {
|
||||||
private static void appendHexChar(StringBuffer buf, int b) {
|
private static void appendHexChar(StringBuffer buf, int b) {
|
||||||
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
|
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
|
||||||
buf.append(HEX_DIGITS[b & 0xf]);
|
buf.append(HEX_DIGITS[b & 0xf]);
|
||||||
buf.append(" ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] toByteArray(String s) {
|
public static byte[] toByteArray(String s) {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.asamk.signal.util;
|
package org.asamk.signal.util;
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.internal.util.Util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -35,6 +38,12 @@ public class IOUtils {
|
||||||
return output.toString();
|
return output.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] readFully(InputStream in) throws IOException {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
Util.copy(in, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
public static void createPrivateDirectories(String directoryPath) throws IOException {
|
public static void createPrivateDirectories(String directoryPath) throws IOException {
|
||||||
final File file = new File(directoryPath);
|
final File file = new File(directoryPath);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
|
|
|
@ -5,17 +5,14 @@ import java.security.SecureRandom;
|
||||||
|
|
||||||
public class RandomUtils {
|
public class RandomUtils {
|
||||||
|
|
||||||
private static final ThreadLocal<SecureRandom> LOCAL_RANDOM = new ThreadLocal<SecureRandom>() {
|
private static final ThreadLocal<SecureRandom> LOCAL_RANDOM = ThreadLocal.withInitial(() -> {
|
||||||
@Override
|
|
||||||
protected SecureRandom initialValue() {
|
|
||||||
SecureRandom rand = getSecureRandomUnseeded();
|
SecureRandom rand = getSecureRandomUnseeded();
|
||||||
|
|
||||||
// Let the SecureRandom seed it self initially
|
// Let the SecureRandom seed it self initially
|
||||||
rand.nextBoolean();
|
rand.nextBoolean();
|
||||||
|
|
||||||
return rand;
|
return rand;
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
private static SecureRandom getSecureRandomUnseeded() {
|
private static SecureRandom getSecureRandomUnseeded() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -3,6 +3,10 @@ package org.asamk.signal.util;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
import org.asamk.signal.GroupIdFormatException;
|
import org.asamk.signal.GroupIdFormatException;
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||||
|
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||||
import org.whispersystems.util.Base64;
|
import org.whispersystems.util.Base64;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -51,4 +55,16 @@ public class Util {
|
||||||
throw new GroupIdFormatException(groupId, e);
|
throw new GroupIdFormatException(groupId, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
|
||||||
|
return PhoneNumberFormatter.formatNumber(number, localNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignalServiceAddress getSignalServiceAddressFromIdentifier(final String identifier) {
|
||||||
|
if (UuidUtil.isUuid(identifier)) {
|
||||||
|
return new SignalServiceAddress(UuidUtil.parseOrNull(identifier), null);
|
||||||
|
} else {
|
||||||
|
return new SignalServiceAddress(null, identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue