mirror of
https://github.com/AsamK/signal-cli
synced 2025-09-04 05:00:39 +00:00
Merge branch 'AsamK:master' into master
This commit is contained in:
commit
26372c4ab3
21 changed files with 365 additions and 102 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,16 +1,31 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.9.0] - 2021-09-12
|
||||||
|
**Attention**: Now requires native libsignal-client version 0.9
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
- Removed deprecated `--json` parameter, use `--output=json` instead
|
- Removed deprecated `--json` parameter, use global parameter `--output=json` instead
|
||||||
- Json output format of `listGroups` command changed:
|
- Json output format of `listGroups` command changed:
|
||||||
Members are now arrays of `{"number":"...","uuid":"..."}` instead of arrays of strings.
|
Members are now arrays of `{"number":"...","uuid":"..."}` objects instead of arrays of strings.
|
||||||
- Removed deprecated fallback data paths, only `$XDG_DATA_HOME/signal-cli` is used now
|
- Removed deprecated fallback data paths, only `$XDG_DATA_HOME/signal-cli` is used now
|
||||||
For those still using the old paths (`$HOME/.config/signal`, `$HOME/.config/textsecure`) you need to move those to the new location.
|
For those still using the old paths (`$HOME/.config/signal`, `$HOME/.config/textsecure`) you need to move those to the new location.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- New global parameter `--trust-new-identities=always` to allow trusting any new identity key without verification
|
- New global parameter `--trust-new-identities=always` to allow trusting any new identity key without verification
|
||||||
- New parameter `--device-name` for `updateAccount` command to update the device name
|
- New parameter `--device-name` for `updateAccount` command to change the device name (also works for the main device)
|
||||||
|
- New SignalControl DBus interface, to register/verify/link new accounts
|
||||||
|
- New `jsonRpc` command that provides a JSON-RPC based API on stdout/stdin
|
||||||
|
- Support for announcement groups
|
||||||
|
- New parameter `--set-permission-send-messages` for `updateGroup` to create an announcement group
|
||||||
|
- New `sendReceipt` command to send read and viewed receipts
|
||||||
|
- Support for receiving sender key messages, mobile apps can now send messages more efficiently with server-side fan-out to groups with signal-cli members.
|
||||||
|
- Support for reading data from remote Signal storage. Now v2 groups will be shown after linking a new device.
|
||||||
|
- New `submitRateLimitChallenge` command that can be used to lift some rate-limits by solving a captcha
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Store identity key correctly when sending a message after a recipient has changed keys
|
||||||
|
|
||||||
## [0.8.5] - 2021-08-07
|
## [0.8.5] - 2021-08-07
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -4,6 +4,7 @@ signal-cli is a commandline interface for [libsignal-service-java](https://githu
|
||||||
To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a linked device](https://github.com/WhisperSystems/libsignal-service-java/pull/21).
|
To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a linked device](https://github.com/WhisperSystems/libsignal-service-java/pull/21).
|
||||||
For registering you need a phone number where you can receive SMS or incoming calls.
|
For registering you need a phone number where you can receive SMS or incoming calls.
|
||||||
signal-cli is primarily intended to be used on servers to notify admins of important events. For this use-case, it has a dbus interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)), that can be used to send messages from any programming language that has dbus bindings.
|
signal-cli is primarily intended to be used on servers to notify admins of important events. For this use-case, it has a dbus interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)), that can be used to send messages from any programming language that has dbus bindings.
|
||||||
|
It also has a JSON-RPC based interface, see the [documentation](https://github.com/AsamK/signal-cli/wiki/JSON-RPC-service) for more information.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -88,6 +89,10 @@ dependencies. If you have a recent gradle version installed, you can replace `./
|
||||||
|
|
||||||
./gradlew distTar
|
./gradlew distTar
|
||||||
|
|
||||||
|
5. Compile and run signal-cli:
|
||||||
|
|
||||||
|
./gradlew run --args="--help"
|
||||||
|
|
||||||
### Building a native binary with GraalVM (EXPERIMENTAL)
|
### Building a native binary with GraalVM (EXPERIMENTAL)
|
||||||
|
|
||||||
It is possible to build a native binary with [GraalVM](https://www.graalvm.org).
|
It is possible to build a native binary with [GraalVM](https://www.graalvm.org).
|
||||||
|
@ -97,9 +102,9 @@ This is still experimental and will not work in all situations.
|
||||||
2. [Install prerequisites](https://www.graalvm.org/reference-manual/native-image/#prerequisites)
|
2. [Install prerequisites](https://www.graalvm.org/reference-manual/native-image/#prerequisites)
|
||||||
3. Execute Gradle:
|
3. Execute Gradle:
|
||||||
|
|
||||||
./gradlew assembleNativeImage
|
./gradlew nativeCompile
|
||||||
|
|
||||||
The binary is available at *build/native-image/signal-cli*
|
The binary is available at *build/native/nativeCompile/signal-cli*
|
||||||
|
|
||||||
## FAQ and Troubleshooting
|
## FAQ and Troubleshooting
|
||||||
For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ)
|
For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ)
|
||||||
|
|
|
@ -3,9 +3,10 @@ plugins {
|
||||||
application
|
application
|
||||||
eclipse
|
eclipse
|
||||||
`check-lib-versions`
|
`check-lib-versions`
|
||||||
|
id("org.graalvm.buildtools.native") version "0.9.5"
|
||||||
}
|
}
|
||||||
|
|
||||||
version = "0.8.5"
|
version = "0.9.0"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
@ -16,6 +17,15 @@ application {
|
||||||
mainClass.set("org.asamk.signal.Main")
|
mainClass.set("org.asamk.signal.Main")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
graalvmNative {
|
||||||
|
binaries {
|
||||||
|
this["main"].run {
|
||||||
|
configurationFileDirectories.from(file("graalvm-config-dir"))
|
||||||
|
buildArgs.add("--allow-incomplete-classpath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -54,51 +64,3 @@ tasks.withType<Jar> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<JavaExec> {
|
|
||||||
val appArgs: String? by project
|
|
||||||
if (appArgs != null) {
|
|
||||||
// allow passing command-line arguments to the main application e.g.:
|
|
||||||
// $ gradle run -PappArgs="['-u', '+...', 'daemon', '--json']"
|
|
||||||
args = groovy.util.Eval.me(appArgs) as MutableList<String>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val assembleNativeImage by tasks.registering {
|
|
||||||
dependsOn("assemble")
|
|
||||||
|
|
||||||
var graalVMHome = ""
|
|
||||||
doFirst {
|
|
||||||
graalVMHome = System.getenv("GRAALVM_HOME")
|
|
||||||
?: throw GradleException("Required GRAALVM_HOME environment variable not set.")
|
|
||||||
}
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
val nativeBinaryOutputPath = "$buildDir/native-image"
|
|
||||||
val nativeBinaryName = "signal-cli"
|
|
||||||
|
|
||||||
mkdir(nativeBinaryOutputPath)
|
|
||||||
|
|
||||||
exec {
|
|
||||||
workingDir = File(".")
|
|
||||||
commandLine(
|
|
||||||
"$graalVMHome/bin/native-image",
|
|
||||||
"-H:Path=$nativeBinaryOutputPath",
|
|
||||||
"-H:Name=$nativeBinaryName",
|
|
||||||
"-H:JNIConfigurationFiles=graalvm-config-dir/jni-config.json",
|
|
||||||
"-H:DynamicProxyConfigurationFiles=graalvm-config-dir/proxy-config.json",
|
|
||||||
"-H:ResourceConfigurationFiles=graalvm-config-dir/resource-config.json",
|
|
||||||
"-H:ReflectionConfigurationFiles=graalvm-config-dir/reflect-config.json",
|
|
||||||
"--no-fallback",
|
|
||||||
"--allow-incomplete-classpath",
|
|
||||||
"--report-unsupported-elements-at-runtime",
|
|
||||||
"--enable-url-protocols=http,https",
|
|
||||||
"--enable-https",
|
|
||||||
"--enable-all-security-services",
|
|
||||||
"-cp",
|
|
||||||
sourceSets.main.get().runtimeClasspath.asPath,
|
|
||||||
application.mainClass.get()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
"name":"java.lang.UnsatisfiedLinkError",
|
"name":"java.lang.UnsatisfiedLinkError",
|
||||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"java.util.UUID",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["long","long"] }]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"
|
"name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"
|
||||||
},
|
},
|
||||||
|
@ -28,10 +32,12 @@
|
||||||
{"name":"getLocalRegistrationId","parameterTypes":[] },
|
{"name":"getLocalRegistrationId","parameterTypes":[] },
|
||||||
{"name":"isTrustedIdentity","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.IdentityKey","org.whispersystems.libsignal.state.IdentityKeyStore$Direction"] },
|
{"name":"isTrustedIdentity","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.IdentityKey","org.whispersystems.libsignal.state.IdentityKeyStore$Direction"] },
|
||||||
{"name":"loadPreKey","parameterTypes":["int"] },
|
{"name":"loadPreKey","parameterTypes":["int"] },
|
||||||
|
{"name":"loadSenderKey","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","java.util.UUID"] },
|
||||||
{"name":"loadSession","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress"] },
|
{"name":"loadSession","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress"] },
|
||||||
{"name":"loadSignedPreKey","parameterTypes":["int"] },
|
{"name":"loadSignedPreKey","parameterTypes":["int"] },
|
||||||
{"name":"removePreKey","parameterTypes":["int"] },
|
{"name":"removePreKey","parameterTypes":["int"] },
|
||||||
{"name":"saveIdentity","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.IdentityKey"] },
|
{"name":"saveIdentity","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.IdentityKey"] },
|
||||||
|
{"name":"storeSenderKey","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","java.util.UUID","org.whispersystems.libsignal.groups.state.SenderKeyRecord"] },
|
||||||
{"name":"storeSession","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.state.SessionRecord"] }
|
{"name":"storeSession","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.state.SessionRecord"] }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -66,10 +72,24 @@
|
||||||
"name":"org.whispersystems.libsignal.UntrustedIdentityException",
|
"name":"org.whispersystems.libsignal.UntrustedIdentityException",
|
||||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"org.whispersystems.libsignal.groups.state.SenderKeyRecord",
|
||||||
|
"methods":[
|
||||||
|
{"name":"<init>","parameterTypes":["long"] },
|
||||||
|
{"name":"nativeHandle","parameterTypes":[] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.whispersystems.libsignal.groups.state.SenderKeyStore"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"org.whispersystems.libsignal.logging.Log",
|
"name":"org.whispersystems.libsignal.logging.Log",
|
||||||
"methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }]
|
"methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"org.whispersystems.libsignal.protocol.PlaintextContent",
|
||||||
|
"methods":[{"name":"nativeHandle","parameterTypes":[] }]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"org.whispersystems.libsignal.protocol.PreKeySignalMessage",
|
"name":"org.whispersystems.libsignal.protocol.PreKeySignalMessage",
|
||||||
"methods":[
|
"methods":[
|
||||||
|
@ -77,6 +97,9 @@
|
||||||
{"name":"nativeHandle","parameterTypes":[] }
|
{"name":"nativeHandle","parameterTypes":[] }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"org.whispersystems.libsignal.protocol.SenderKeyMessage"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"org.whispersystems.libsignal.protocol.SignalMessage",
|
"name":"org.whispersystems.libsignal.protocol.SignalMessage",
|
||||||
"methods":[
|
"methods":[
|
||||||
|
|
|
@ -697,6 +697,18 @@
|
||||||
"allDeclaredMethods":true,
|
"allDeclaredMethods":true,
|
||||||
"allDeclaredConstructors":true
|
"allDeclaredConstructors":true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore$Storage",
|
||||||
|
"allDeclaredFields":true,
|
||||||
|
"allDeclaredMethods":true,
|
||||||
|
"allDeclaredConstructors":true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore$Storage$SharedSenderKey",
|
||||||
|
"allDeclaredFields":true,
|
||||||
|
"allDeclaredMethods":true,
|
||||||
|
"allDeclaredConstructors":true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"org.asamk.signal.manager.storage.stickers.StickerStore",
|
"name":"org.asamk.signal.manager.storage.stickers.StickerStore",
|
||||||
"allDeclaredFields":true,
|
"allDeclaredFields":true,
|
||||||
|
@ -1505,6 +1517,12 @@
|
||||||
"name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArraySerializer",
|
"name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArraySerializer",
|
||||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"org.whispersystems.signalservice.api.storage.StorageAuthResponse",
|
||||||
|
"allDeclaredFields":true,
|
||||||
|
"allDeclaredMethods":true,
|
||||||
|
"allDeclaredConstructors":true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name":"org.whispersystems.signalservice.internal.contacts.crypto.SignatureBodyEntity",
|
"name":"org.whispersystems.signalservice.internal.contacts.crypto.SignatureBodyEntity",
|
||||||
"allDeclaredFields":true,
|
"allDeclaredFields":true,
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.asamk.signal.manager.helper.GroupHelper;
|
||||||
import org.asamk.signal.manager.helper.GroupV2Helper;
|
import org.asamk.signal.manager.helper.GroupV2Helper;
|
||||||
import org.asamk.signal.manager.helper.IncomingMessageHandler;
|
import org.asamk.signal.manager.helper.IncomingMessageHandler;
|
||||||
import org.asamk.signal.manager.helper.PinHelper;
|
import org.asamk.signal.manager.helper.PinHelper;
|
||||||
|
import org.asamk.signal.manager.helper.PreKeyHelper;
|
||||||
import org.asamk.signal.manager.helper.ProfileHelper;
|
import org.asamk.signal.manager.helper.ProfileHelper;
|
||||||
import org.asamk.signal.manager.helper.SendHelper;
|
import org.asamk.signal.manager.helper.SendHelper;
|
||||||
import org.asamk.signal.manager.helper.StorageHelper;
|
import org.asamk.signal.manager.helper.StorageHelper;
|
||||||
|
@ -62,14 +63,11 @@ import org.asamk.signal.manager.util.Utils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
||||||
import org.whispersystems.libsignal.fingerprint.Fingerprint;
|
import org.whispersystems.libsignal.fingerprint.Fingerprint;
|
||||||
import org.whispersystems.libsignal.fingerprint.FingerprintParsingException;
|
import org.whispersystems.libsignal.fingerprint.FingerprintParsingException;
|
||||||
import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
|
import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
|
||||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
|
||||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
|
||||||
import org.whispersystems.libsignal.util.Pair;
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||||
|
@ -141,6 +139,7 @@ public class Manager implements Closeable {
|
||||||
private final GroupHelper groupHelper;
|
private final GroupHelper groupHelper;
|
||||||
private final ContactHelper contactHelper;
|
private final ContactHelper contactHelper;
|
||||||
private final IncomingMessageHandler incomingMessageHandler;
|
private final IncomingMessageHandler incomingMessageHandler;
|
||||||
|
private final PreKeyHelper preKeyHelper;
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private boolean hasCaughtUpWithOldMessages = false;
|
private boolean hasCaughtUpWithOldMessages = false;
|
||||||
|
@ -219,6 +218,7 @@ public class Manager implements Closeable {
|
||||||
groupHelper,
|
groupHelper,
|
||||||
avatarStore,
|
avatarStore,
|
||||||
this::resolveSignalServiceAddress);
|
this::resolveSignalServiceAddress);
|
||||||
|
preKeyHelper = new PreKeyHelper(account, dependencies);
|
||||||
|
|
||||||
this.context = new Context(account,
|
this.context = new Context(account,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
@ -227,7 +227,8 @@ public class Manager implements Closeable {
|
||||||
groupHelper,
|
groupHelper,
|
||||||
syncHelper,
|
syncHelper,
|
||||||
profileHelper,
|
profileHelper,
|
||||||
storageHelper);
|
storageHelper,
|
||||||
|
preKeyHelper);
|
||||||
var jobExecutor = new JobExecutor(context);
|
var jobExecutor = new JobExecutor(context);
|
||||||
|
|
||||||
this.incomingMessageHandler = new IncomingMessageHandler(account,
|
this.incomingMessageHandler = new IncomingMessageHandler(account,
|
||||||
|
@ -238,6 +239,7 @@ public class Manager implements Closeable {
|
||||||
contactHelper,
|
contactHelper,
|
||||||
attachmentHelper,
|
attachmentHelper,
|
||||||
syncHelper,
|
syncHelper,
|
||||||
|
this::getRecipientProfile,
|
||||||
jobExecutor);
|
jobExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,10 +251,6 @@ public class Manager implements Closeable {
|
||||||
return account.getSelfRecipientId();
|
return account.getSelfRecipientId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityKeyPair getIdentityKeyPair() {
|
|
||||||
return account.getIdentityKeyPair();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDeviceId() {
|
public int getDeviceId() {
|
||||||
return account.getDeviceId();
|
return account.getDeviceId();
|
||||||
}
|
}
|
||||||
|
@ -309,9 +307,7 @@ public class Manager implements Closeable {
|
||||||
days);
|
days);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dependencies.getAccountManager().getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
|
preKeyHelper.refreshPreKeysIfNecessary();
|
||||||
refreshPreKeys();
|
|
||||||
}
|
|
||||||
if (account.getUuid() == null) {
|
if (account.getUuid() == null) {
|
||||||
account.setUuid(dependencies.getAccountManager().getOwnUuid());
|
account.setUuid(dependencies.getAccountManager().getOwnUuid());
|
||||||
}
|
}
|
||||||
|
@ -439,7 +435,7 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
|
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
|
||||||
var identityKeyPair = getIdentityKeyPair();
|
var identityKeyPair = account.getIdentityKeyPair();
|
||||||
var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
|
var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
|
||||||
|
|
||||||
dependencies.getAccountManager()
|
dependencies.getAccountManager()
|
||||||
|
@ -472,29 +468,7 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshPreKeys() throws IOException {
|
void refreshPreKeys() throws IOException {
|
||||||
var oneTimePreKeys = generatePreKeys();
|
preKeyHelper.refreshPreKeys();
|
||||||
final var identityKeyPair = getIdentityKeyPair();
|
|
||||||
var signedPreKeyRecord = generateSignedPreKey(identityKeyPair);
|
|
||||||
|
|
||||||
dependencies.getAccountManager().setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PreKeyRecord> generatePreKeys() {
|
|
||||||
final var offset = account.getPreKeyIdOffset();
|
|
||||||
|
|
||||||
var records = KeyUtils.generatePreKeyRecords(offset, ServiceConfig.PREKEY_BATCH_SIZE);
|
|
||||||
account.addPreKeys(records);
|
|
||||||
|
|
||||||
return records;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) {
|
|
||||||
final var signedPreKeyId = account.getNextSignedPreKeyId();
|
|
||||||
|
|
||||||
var record = KeyUtils.generateSignedPreKeyRecord(identityKeyPair, signedPreKeyId);
|
|
||||||
account.addSignedPreKey(record);
|
|
||||||
|
|
||||||
return record;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Profile getRecipientProfile(RecipientId recipientId) {
|
public Profile getRecipientProfile(RecipientId recipientId) {
|
||||||
|
@ -903,11 +877,11 @@ public class Manager implements Closeable {
|
||||||
// store message on disk, before acknowledging receipt to the server
|
// store message on disk, before acknowledging receipt to the server
|
||||||
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
||||||
});
|
});
|
||||||
logger.debug("New message received from server");
|
|
||||||
if (result.isPresent()) {
|
if (result.isPresent()) {
|
||||||
envelope = result.get();
|
envelope = result.get();
|
||||||
|
logger.debug("New message received from server");
|
||||||
} else {
|
} else {
|
||||||
// Received indicator that server queue is empty
|
logger.debug("Received indicator that server queue is empty");
|
||||||
handleQueuedActions(queuedActions);
|
handleQueuedActions(queuedActions);
|
||||||
queuedActions.clear();
|
queuedActions.clear();
|
||||||
|
|
||||||
|
@ -1175,7 +1149,7 @@ public class Manager implements Closeable {
|
||||||
) {
|
) {
|
||||||
return Utils.computeSafetyNumber(capabilities.isUuid(),
|
return Utils.computeSafetyNumber(capabilities.isUuid(),
|
||||||
account.getSelfAddress(),
|
account.getSelfAddress(),
|
||||||
getIdentityKeyPair().getPublicKey(),
|
account.getIdentityKeyPair().getPublicKey(),
|
||||||
theirAddress,
|
theirAddress,
|
||||||
theirIdentityKey);
|
theirIdentityKey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.jobs.Context;
|
||||||
|
|
||||||
|
public class RefreshPreKeysAction implements HandleAction {
|
||||||
|
|
||||||
|
private static final RefreshPreKeysAction INSTANCE = new RefreshPreKeysAction();
|
||||||
|
|
||||||
|
private RefreshPreKeysAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RefreshPreKeysAction create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getPreKeyHelper().refreshPreKeysIfNecessary();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.groups.GroupId;
|
||||||
|
import org.asamk.signal.manager.jobs.Context;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
import org.signal.libsignal.metadata.ProtocolException;
|
||||||
|
import org.whispersystems.libsignal.protocol.CiphertextMessage;
|
||||||
|
import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||||
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||||
|
|
||||||
|
public class SendRetryMessageRequestAction implements HandleAction {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
private final ProtocolException protocolException;
|
||||||
|
private final SignalServiceEnvelope envelope;
|
||||||
|
|
||||||
|
public SendRetryMessageRequestAction(
|
||||||
|
final RecipientId recipientId,
|
||||||
|
final ProtocolException protocolException,
|
||||||
|
final SignalServiceEnvelope envelope
|
||||||
|
) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.protocolException = protocolException;
|
||||||
|
this.envelope = envelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getAccount().getSessionStore().archiveSessions(recipientId);
|
||||||
|
|
||||||
|
int senderDevice = protocolException.getSenderDevice();
|
||||||
|
Optional<GroupId> groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion(
|
||||||
|
protocolException.getGroupId().get())) : Optional.absent();
|
||||||
|
|
||||||
|
byte[] originalContent;
|
||||||
|
int envelopeType;
|
||||||
|
if (protocolException.getUnidentifiedSenderMessageContent().isPresent()) {
|
||||||
|
final var messageContent = protocolException.getUnidentifiedSenderMessageContent().get();
|
||||||
|
originalContent = messageContent.getContent();
|
||||||
|
envelopeType = messageContent.getType();
|
||||||
|
} else {
|
||||||
|
originalContent = envelope.getContent();
|
||||||
|
envelopeType = envelopeTypeToCiphertextMessageType(envelope.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
DecryptionErrorMessage decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent,
|
||||||
|
envelopeType,
|
||||||
|
envelope.getTimestamp(),
|
||||||
|
senderDevice);
|
||||||
|
|
||||||
|
context.getSendHelper().sendRetryReceipt(decryptionErrorMessage, recipientId, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int envelopeTypeToCiphertextMessageType(int envelopeType) {
|
||||||
|
switch (envelopeType) {
|
||||||
|
case SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE:
|
||||||
|
return CiphertextMessage.PREKEY_TYPE;
|
||||||
|
case SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE:
|
||||||
|
return CiphertextMessage.SENDERKEY_TYPE;
|
||||||
|
case SignalServiceProtos.Envelope.Type.PLAINTEXT_CONTENT_VALUE:
|
||||||
|
return CiphertextMessage.PLAINTEXT_CONTENT_TYPE;
|
||||||
|
case SignalServiceProtos.Envelope.Type.CIPHERTEXT_VALUE:
|
||||||
|
default:
|
||||||
|
return CiphertextMessage.WHISPER_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final SendRetryMessageRequestAction that = (SendRetryMessageRequestAction) o;
|
||||||
|
|
||||||
|
if (!recipientId.equals(that.recipientId)) return false;
|
||||||
|
if (!protocolException.equals(that.protocolException)) return false;
|
||||||
|
return envelope.equals(that.envelope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = recipientId.hashCode();
|
||||||
|
result = 31 * result + protocolException.hashCode();
|
||||||
|
result = 31 * result + envelope.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,12 +6,14 @@ import org.asamk.signal.manager.SignalDependencies;
|
||||||
import org.asamk.signal.manager.TrustLevel;
|
import org.asamk.signal.manager.TrustLevel;
|
||||||
import org.asamk.signal.manager.UntrustedIdentityException;
|
import org.asamk.signal.manager.UntrustedIdentityException;
|
||||||
import org.asamk.signal.manager.actions.HandleAction;
|
import org.asamk.signal.manager.actions.HandleAction;
|
||||||
|
import org.asamk.signal.manager.actions.RefreshPreKeysAction;
|
||||||
import org.asamk.signal.manager.actions.RenewSessionAction;
|
import org.asamk.signal.manager.actions.RenewSessionAction;
|
||||||
import org.asamk.signal.manager.actions.RetrieveProfileAction;
|
import org.asamk.signal.manager.actions.RetrieveProfileAction;
|
||||||
import org.asamk.signal.manager.actions.RetrieveStorageDataAction;
|
import org.asamk.signal.manager.actions.RetrieveStorageDataAction;
|
||||||
import org.asamk.signal.manager.actions.SendGroupInfoAction;
|
import org.asamk.signal.manager.actions.SendGroupInfoAction;
|
||||||
import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
|
import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
|
||||||
import org.asamk.signal.manager.actions.SendReceiptAction;
|
import org.asamk.signal.manager.actions.SendReceiptAction;
|
||||||
|
import org.asamk.signal.manager.actions.SendRetryMessageRequestAction;
|
||||||
import org.asamk.signal.manager.actions.SendSyncBlockedListAction;
|
import org.asamk.signal.manager.actions.SendSyncBlockedListAction;
|
||||||
import org.asamk.signal.manager.actions.SendSyncContactsAction;
|
import org.asamk.signal.manager.actions.SendSyncContactsAction;
|
||||||
import org.asamk.signal.manager.actions.SendSyncGroupsAction;
|
import org.asamk.signal.manager.actions.SendSyncGroupsAction;
|
||||||
|
@ -22,12 +24,17 @@ import org.asamk.signal.manager.groups.GroupUtils;
|
||||||
import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
|
import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
|
||||||
import org.asamk.signal.manager.storage.SignalAccount;
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
|
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.Profile;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||||
import org.asamk.signal.manager.storage.stickers.Sticker;
|
import org.asamk.signal.manager.storage.stickers.Sticker;
|
||||||
import org.asamk.signal.manager.storage.stickers.StickerPackId;
|
import org.asamk.signal.manager.storage.stickers.StickerPackId;
|
||||||
|
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
|
||||||
|
import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
|
||||||
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
|
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
|
||||||
|
import org.signal.libsignal.metadata.ProtocolNoSessionException;
|
||||||
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
||||||
|
import org.signal.libsignal.metadata.SelfSendException;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -58,6 +65,7 @@ public final class IncomingMessageHandler {
|
||||||
private final ContactHelper contactHelper;
|
private final ContactHelper contactHelper;
|
||||||
private final AttachmentHelper attachmentHelper;
|
private final AttachmentHelper attachmentHelper;
|
||||||
private final SyncHelper syncHelper;
|
private final SyncHelper syncHelper;
|
||||||
|
private final ProfileProvider profileProvider;
|
||||||
private final JobExecutor jobExecutor;
|
private final JobExecutor jobExecutor;
|
||||||
|
|
||||||
public IncomingMessageHandler(
|
public IncomingMessageHandler(
|
||||||
|
@ -69,6 +77,7 @@ public final class IncomingMessageHandler {
|
||||||
final ContactHelper contactHelper,
|
final ContactHelper contactHelper,
|
||||||
final AttachmentHelper attachmentHelper,
|
final AttachmentHelper attachmentHelper,
|
||||||
final SyncHelper syncHelper,
|
final SyncHelper syncHelper,
|
||||||
|
final ProfileProvider profileProvider,
|
||||||
final JobExecutor jobExecutor
|
final JobExecutor jobExecutor
|
||||||
) {
|
) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
|
@ -79,6 +88,7 @@ public final class IncomingMessageHandler {
|
||||||
this.contactHelper = contactHelper;
|
this.contactHelper = contactHelper;
|
||||||
this.attachmentHelper = attachmentHelper;
|
this.attachmentHelper = attachmentHelper;
|
||||||
this.syncHelper = syncHelper;
|
this.syncHelper = syncHelper;
|
||||||
|
this.profileProvider = profileProvider;
|
||||||
this.jobExecutor = jobExecutor;
|
this.jobExecutor = jobExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +97,11 @@ public final class IncomingMessageHandler {
|
||||||
final boolean ignoreAttachments,
|
final boolean ignoreAttachments,
|
||||||
final Manager.ReceiveMessageHandler handler
|
final Manager.ReceiveMessageHandler handler
|
||||||
) {
|
) {
|
||||||
|
final List<HandleAction> actions = new ArrayList<>();
|
||||||
|
if (envelope.isPreKeySignalMessage()) {
|
||||||
|
actions.add(RefreshPreKeysAction.create());
|
||||||
|
}
|
||||||
|
|
||||||
SignalServiceContent content = null;
|
SignalServiceContent content = null;
|
||||||
if (!envelope.isReceipt()) {
|
if (!envelope.isReceipt()) {
|
||||||
try {
|
try {
|
||||||
|
@ -100,7 +115,7 @@ public final class IncomingMessageHandler {
|
||||||
return new Pair<>(List.of(), e);
|
return new Pair<>(List.of(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final var actions = checkAndHandleMessage(envelope, content, ignoreAttachments, handler, null);
|
actions.addAll(checkAndHandleMessage(envelope, content, ignoreAttachments, handler, null));
|
||||||
return new Pair<>(actions, null);
|
return new Pair<>(actions, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,11 +140,24 @@ public final class IncomingMessageHandler {
|
||||||
actions.add(new RetrieveProfileAction(recipientId));
|
actions.add(new RetrieveProfileAction(recipientId));
|
||||||
exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(recipientId),
|
exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(recipientId),
|
||||||
e.getSenderDevice());
|
e.getSenderDevice());
|
||||||
} catch (ProtocolInvalidMessageException e) {
|
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException | ProtocolInvalidMessageException e) {
|
||||||
final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
|
final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
|
||||||
logger.debug("Received invalid message, queuing renew session action.");
|
final var senderProfile = profileProvider.getProfile(sender);
|
||||||
actions.add(new RenewSessionAction(sender));
|
final var selfProfile = profileProvider.getProfile(account.getSelfRecipientId());
|
||||||
|
if (senderProfile != null
|
||||||
|
&& senderProfile.getCapabilities().contains(Profile.Capability.senderKey)
|
||||||
|
&& selfProfile != null
|
||||||
|
&& selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
|
||||||
|
logger.debug("Received invalid message, requesting message resend.");
|
||||||
|
actions.add(new SendRetryMessageRequestAction(sender, e, envelope));
|
||||||
|
} else {
|
||||||
|
logger.debug("Received invalid message, queuing renew session action.");
|
||||||
|
actions.add(new RenewSessionAction(sender));
|
||||||
|
}
|
||||||
exception = e;
|
exception = e;
|
||||||
|
} catch (SelfSendException e) {
|
||||||
|
logger.debug("Dropping unidentified message from self.");
|
||||||
|
return new Pair<>(List.of(), null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package org.asamk.signal.manager.helper;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.SignalDependencies;
|
||||||
|
import org.asamk.signal.manager.config.ServiceConfig;
|
||||||
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
|
import org.asamk.signal.manager.util.KeyUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||||
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
|
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PreKeyHelper {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(PreKeyHelper.class);
|
||||||
|
|
||||||
|
private final SignalAccount account;
|
||||||
|
private final SignalDependencies dependencies;
|
||||||
|
|
||||||
|
public PreKeyHelper(
|
||||||
|
final SignalAccount account, final SignalDependencies dependencies
|
||||||
|
) {
|
||||||
|
this.account = account;
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshPreKeysIfNecessary() throws IOException {
|
||||||
|
if (dependencies.getAccountManager().getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
|
||||||
|
refreshPreKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshPreKeys() throws IOException {
|
||||||
|
var oneTimePreKeys = generatePreKeys();
|
||||||
|
final var identityKeyPair = account.getIdentityKeyPair();
|
||||||
|
var signedPreKeyRecord = generateSignedPreKey(identityKeyPair);
|
||||||
|
|
||||||
|
dependencies.getAccountManager().setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PreKeyRecord> generatePreKeys() {
|
||||||
|
final var offset = account.getPreKeyIdOffset();
|
||||||
|
|
||||||
|
var records = KeyUtils.generatePreKeyRecords(offset, ServiceConfig.PREKEY_BATCH_SIZE);
|
||||||
|
account.addPreKeys(records);
|
||||||
|
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) {
|
||||||
|
final var signedPreKeyId = account.getNextSignedPreKeyId();
|
||||||
|
|
||||||
|
var record = KeyUtils.generateSignedPreKeyRecord(identityKeyPair, signedPreKeyId);
|
||||||
|
account.addSignedPreKey(record);
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,5 +5,5 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
public interface ProfileProvider {
|
public interface ProfileProvider {
|
||||||
|
|
||||||
Profile getProfile(RecipientId address);
|
Profile getProfile(RecipientId recipientId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||||
|
@ -23,6 +24,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
|
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -155,6 +157,25 @@ public class SendHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendRetryReceipt(
|
||||||
|
DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId
|
||||||
|
) throws IOException, UntrustedIdentityException {
|
||||||
|
var messageSender = dependencies.getMessageSender();
|
||||||
|
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
||||||
|
logger.debug("Sending retry receipt for {} to {}, device: {}",
|
||||||
|
errorMessage.getTimestamp(),
|
||||||
|
recipientId,
|
||||||
|
errorMessage.getDeviceId());
|
||||||
|
try {
|
||||||
|
messageSender.sendRetryReceipt(address,
|
||||||
|
unidentifiedAccessHelper.getAccessFor(recipientId),
|
||||||
|
groupId.transform(GroupId::serialize),
|
||||||
|
errorMessage);
|
||||||
|
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
||||||
|
throw new UntrustedIdentityException(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
|
public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
|
||||||
var messageSender = dependencies.getMessageSender();
|
var messageSender = dependencies.getMessageSender();
|
||||||
|
|
||||||
|
@ -285,6 +306,9 @@ public class SendHelper {
|
||||||
}
|
}
|
||||||
} catch (ProofRequiredException e) {
|
} catch (ProofRequiredException e) {
|
||||||
return SendMessageResult.proofRequiredFailure(address, e);
|
return SendMessageResult.proofRequiredFailure(address, e);
|
||||||
|
} catch (RateLimitException e) {
|
||||||
|
logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage());
|
||||||
|
return SendMessageResult.networkFailure(address);
|
||||||
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
||||||
return SendMessageResult.identityFailure(address, e.getIdentityKey());
|
return SendMessageResult.identityFailure(address, e.getIdentityKey());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.asamk.signal.manager.jobs;
|
||||||
import org.asamk.signal.manager.SignalDependencies;
|
import org.asamk.signal.manager.SignalDependencies;
|
||||||
import org.asamk.signal.manager.StickerPackStore;
|
import org.asamk.signal.manager.StickerPackStore;
|
||||||
import org.asamk.signal.manager.helper.GroupHelper;
|
import org.asamk.signal.manager.helper.GroupHelper;
|
||||||
|
import org.asamk.signal.manager.helper.PreKeyHelper;
|
||||||
import org.asamk.signal.manager.helper.ProfileHelper;
|
import org.asamk.signal.manager.helper.ProfileHelper;
|
||||||
import org.asamk.signal.manager.helper.SendHelper;
|
import org.asamk.signal.manager.helper.SendHelper;
|
||||||
import org.asamk.signal.manager.helper.StorageHelper;
|
import org.asamk.signal.manager.helper.StorageHelper;
|
||||||
|
@ -19,6 +20,7 @@ public class Context {
|
||||||
private final SyncHelper syncHelper;
|
private final SyncHelper syncHelper;
|
||||||
private final ProfileHelper profileHelper;
|
private final ProfileHelper profileHelper;
|
||||||
private final StorageHelper storageHelper;
|
private final StorageHelper storageHelper;
|
||||||
|
private final PreKeyHelper preKeyHelper;
|
||||||
|
|
||||||
public Context(
|
public Context(
|
||||||
final SignalAccount account,
|
final SignalAccount account,
|
||||||
|
@ -28,7 +30,8 @@ public class Context {
|
||||||
final GroupHelper groupHelper,
|
final GroupHelper groupHelper,
|
||||||
final SyncHelper syncHelper,
|
final SyncHelper syncHelper,
|
||||||
final ProfileHelper profileHelper,
|
final ProfileHelper profileHelper,
|
||||||
final StorageHelper storageHelper
|
final StorageHelper storageHelper,
|
||||||
|
final PreKeyHelper preKeyHelper
|
||||||
) {
|
) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.dependencies = dependencies;
|
this.dependencies = dependencies;
|
||||||
|
@ -38,6 +41,7 @@ public class Context {
|
||||||
this.syncHelper = syncHelper;
|
this.syncHelper = syncHelper;
|
||||||
this.profileHelper = profileHelper;
|
this.profileHelper = profileHelper;
|
||||||
this.storageHelper = storageHelper;
|
this.storageHelper = storageHelper;
|
||||||
|
this.preKeyHelper = preKeyHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignalAccount getAccount() {
|
public SignalAccount getAccount() {
|
||||||
|
@ -71,4 +75,8 @@ public class Context {
|
||||||
public StorageHelper getStorageHelper() {
|
public StorageHelper getStorageHelper() {
|
||||||
return storageHelper;
|
return storageHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PreKeyHelper getPreKeyHelper() {
|
||||||
|
return preKeyHelper;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,8 @@ public class Profile {
|
||||||
gv2,
|
gv2,
|
||||||
storage,
|
storage,
|
||||||
gv1Migration,
|
gv1Migration,
|
||||||
senderKey;
|
senderKey,
|
||||||
|
announcementGroup;
|
||||||
|
|
||||||
static Capability valueOfOrNull(String value) {
|
static Capability valueOfOrNull(String value) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -16,6 +16,11 @@ public class RecipientId {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RecipientId{" + "id=" + id + '}';
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -65,6 +65,10 @@ public class ProfileUtils {
|
||||||
if (encryptedProfile.getCapabilities().isSenderKey()) {
|
if (encryptedProfile.getCapabilities().isSenderKey()) {
|
||||||
capabilities.add(Profile.Capability.senderKey);
|
capabilities.add(Profile.Capability.senderKey);
|
||||||
}
|
}
|
||||||
|
if (encryptedProfile.getCapabilities().isAnnouncementGroup()) {
|
||||||
|
capabilities.add(Profile.Capability.announcementGroup);
|
||||||
|
}
|
||||||
|
|
||||||
return capabilities;
|
return capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -362,6 +362,8 @@ Trust all known keys of this user, only use this for testing.
|
||||||
|
|
||||||
*-v* VERIFIED_SAFETY_NUMBER, *--verified-safety-number* VERIFIED_SAFETY_NUMBER::
|
*-v* VERIFIED_SAFETY_NUMBER, *--verified-safety-number* VERIFIED_SAFETY_NUMBER::
|
||||||
Specify the safety number of the key, only use this option if you have verified the safety number.
|
Specify the safety number of the key, only use this option if you have verified the safety number.
|
||||||
|
Can be either the plain text numbers shown in the app or the bytes from the QR-code,
|
||||||
|
encoded as base64.
|
||||||
|
|
||||||
=== updateProfile
|
=== updateProfile
|
||||||
|
|
||||||
|
|
|
@ -11,17 +11,22 @@ if [ ! -z "$GRAALVM_HOME" ]; then
|
||||||
export JAVA_HOME=$GRAALVM_HOME
|
export JAVA_HOME=$GRAALVM_HOME
|
||||||
export SIGNAL_CLI_OPTS='-agentlib:native-image-agent=config-merge-dir=graalvm-config-dir/'
|
export SIGNAL_CLI_OPTS='-agentlib:native-image-agent=config-merge-dir=graalvm-config-dir/'
|
||||||
fi
|
fi
|
||||||
export SIGNAL_CLI="$PWD/build/install/signal-cli/bin/signal-cli"
|
|
||||||
|
|
||||||
NUMBER_1="$1"
|
NUMBER_1="$1"
|
||||||
NUMBER_2="$2"
|
NUMBER_2="$2"
|
||||||
TEST_PIN_1=456test_pin_foo123
|
TEST_PIN_1=456test_pin_foo123
|
||||||
|
NATIVE=1
|
||||||
|
|
||||||
PATH_TEST_CONFIG="$PWD/build/test-config"
|
PATH_TEST_CONFIG="$PWD/build/test-config"
|
||||||
PATH_MAIN="$PATH_TEST_CONFIG/main"
|
PATH_MAIN="$PATH_TEST_CONFIG/main"
|
||||||
PATH_LINK="$PATH_TEST_CONFIG/link"
|
PATH_LINK="$PATH_TEST_CONFIG/link"
|
||||||
|
|
||||||
./gradlew installDist
|
if [ "$NATIVE" -eq 1 ]; then
|
||||||
|
SIGNAL_CLI="$PWD/build/native/nativeCompile/signal-cli"
|
||||||
|
else
|
||||||
|
./gradlew installDist
|
||||||
|
SIGNAL_CLI="$PWD/build/install/signal-cli/bin/signal-cli"
|
||||||
|
fi
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
set -x
|
set -x
|
||||||
|
|
|
@ -56,6 +56,9 @@ public class Main {
|
||||||
e.getCause().printStackTrace();
|
e.getCause().printStackTrace();
|
||||||
}
|
}
|
||||||
status = getStatusForError(e);
|
status = getStatusForError(e);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
status = 2;
|
||||||
}
|
}
|
||||||
System.exit(status);
|
System.exit(status);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.asamk.signal.manager.groups.GroupUtils;
|
||||||
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.slf4j.helpers.MessageFormatter;
|
import org.slf4j.helpers.MessageFormatter;
|
||||||
|
import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
|
@ -113,6 +114,11 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
var typingMessage = content.getTypingMessage().get();
|
var typingMessage = content.getTypingMessage().get();
|
||||||
printTypingMessage(writer.indentedWriter(), typingMessage);
|
printTypingMessage(writer.indentedWriter(), typingMessage);
|
||||||
}
|
}
|
||||||
|
if (content.getDecryptionErrorMessage().isPresent()) {
|
||||||
|
writer.println("Received a decryption error message (resend request)");
|
||||||
|
var decryptionErrorMessage = content.getDecryptionErrorMessage().get();
|
||||||
|
printDecryptionErrorMessage(writer.indentedWriter(), decryptionErrorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
writer.println("Unknown message received.");
|
writer.println("Unknown message received.");
|
||||||
|
@ -215,6 +221,15 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void printDecryptionErrorMessage(
|
||||||
|
final PlainTextWriter writer, final DecryptionErrorMessage decryptionErrorMessage
|
||||||
|
) {
|
||||||
|
writer.println("Device id: {}", decryptionErrorMessage.getDeviceId());
|
||||||
|
writer.println("Timestamp: {}", DateUtils.formatTimestamp(decryptionErrorMessage.getTimestamp()));
|
||||||
|
writer.println("Ratchet key: {}",
|
||||||
|
decryptionErrorMessage.getRatchetKey().isPresent() ? "is present" : "not present");
|
||||||
|
}
|
||||||
|
|
||||||
private void printReceiptMessage(
|
private void printReceiptMessage(
|
||||||
final PlainTextWriter writer, final SignalServiceReceiptMessage receiptMessage
|
final PlainTextWriter writer, final SignalServiceReceiptMessage receiptMessage
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -61,7 +61,8 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand {
|
||||||
final var writer = (PlainTextWriter) outputWriter;
|
final var writer = (PlainTextWriter) outputWriter;
|
||||||
|
|
||||||
for (var entry : registered.entrySet()) {
|
for (var entry : registered.entrySet()) {
|
||||||
writer.println("{}: {}", entry.getKey(), entry.getValue());
|
final var uuid = entry.getValue().second();
|
||||||
|
writer.println("{}: {}", entry.getKey(), uuid != null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue