From a17262d9ff7379c4f1590a3669af52e7237144bb Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 9 Sep 2021 18:54:48 +0200 Subject: [PATCH 1/4] Catch ProofRequiredException from getPreKeys request and wrap in SendMessageResult --- .../main/java/org/asamk/signal/manager/helper/SendHelper.java | 3 +++ src/main/java/org/asamk/signal/util/ErrorUtils.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index 6ebc0254..89e3eba2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -22,6 +22,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import java.io.IOException; @@ -282,6 +283,8 @@ public class SendHelper { message, SignalServiceMessageSender.IndividualSendEvents.EMPTY); } + } catch (ProofRequiredException e) { + return SendMessageResult.proofRequiredFailure(address, e); } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) { return SendMessageResult.identityFailure(address, e.getIdentityKey()); } diff --git a/src/main/java/org/asamk/signal/util/ErrorUtils.java b/src/main/java/org/asamk/signal/util/ErrorUtils.java index 39e32198..ef1956c3 100644 --- a/src/main/java/org/asamk/signal/util/ErrorUtils.java +++ b/src/main/java/org/asamk/signal/util/ErrorUtils.java @@ -67,7 +67,7 @@ public class ErrorUtils { } else if (result.getProofRequiredFailure() != null) { final var failure = result.getProofRequiredFailure(); return String.format( - "CAPTCHA proof required for sending to \"%s\", available options \"%s\" with token \"%s\", or wait \"%d\" seconds", + "CAPTCHA proof required for sending to \"%s\", available options \"%s\" with challenge token \"%s\", or wait \"%d\" seconds", identifier, failure.getOptions() .stream() From 1856e79a507e97c523f40426e0e30c5c72b50ad7 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 9 Sep 2021 18:58:45 +0200 Subject: [PATCH 2/4] Add missing check if client zk operations are null Fixes #710 --- .../org/asamk/signal/manager/SignalDependencies.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java index 970a6741..3478239f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java @@ -3,6 +3,7 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.signal.libsignal.metadata.certificate.CertificateValidator; +import org.signal.zkgroup.profiles.ClientZkProfileOperations; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.KeyBackupService; import org.whispersystems.signalservice.api.SignalServiceAccountManager; @@ -93,6 +94,11 @@ public class SignalDependencies { : null); } + private ClientZkProfileOperations getClientZkProfileOperations() { + final var clientZkOperations = getClientZkOperations(); + return clientZkOperations == null ? null : clientZkOperations.getProfileOperations(); + } + public SignalWebSocket getSignalWebSocket() { return getOrCreate(() -> signalWebSocket, () -> { final var timer = new UptimeSleepTimer(); @@ -126,7 +132,7 @@ public class SignalDependencies { () -> messageReceiver = new SignalServiceMessageReceiver(serviceEnvironmentConfig.getSignalServiceConfiguration(), credentialsProvider, userAgent, - getClientZkOperations().getProfileOperations(), + getClientZkProfileOperations(), ServiceConfig.AUTOMATIC_NETWORK_RETRY)); } @@ -139,7 +145,7 @@ public class SignalDependencies { userAgent, getSignalWebSocket(), Optional.absent(), - getClientZkOperations().getProfileOperations(), + getClientZkProfileOperations(), executor, ServiceConfig.MAX_ENVELOPE_SIZE, ServiceConfig.AUTOMATIC_NETWORK_RETRY)); @@ -156,7 +162,7 @@ public class SignalDependencies { public ProfileService getProfileService() { return getOrCreate(() -> profileService, - () -> profileService = new ProfileService(getClientZkOperations().getProfileOperations(), + () -> profileService = new ProfileService(getClientZkProfileOperations(), getMessageReceiver(), getSignalWebSocket())); } From eee140f74fe9a01972b8d61193c1125c1d89e0df Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 9 Sep 2021 19:20:48 +0200 Subject: [PATCH 3/4] Add submitRateLimitChallenge command Related #708 --- .../org/asamk/signal/manager/Manager.java | 4 ++ .../org/asamk/signal/commands/Commands.java | 1 + .../SubmitRateLimitChallengeCommand.java | 44 +++++++++++++++++++ .../org/asamk/signal/util/ErrorUtils.java | 12 ++++- 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index a7a691cc..a7e80f7c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -405,6 +405,10 @@ public class Manager implements Closeable { account.setRegistered(false); } + public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException { + dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha); + } + public List getLinkedDevices() throws IOException { var devices = dependencies.getAccountManager().getDevices(); account.setMultiDevice(devices.size() > 1); diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java index 90e8e114..5d637eee 100644 --- a/src/main/java/org/asamk/signal/commands/Commands.java +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -34,6 +34,7 @@ public class Commands { addCommand(new SendSyncRequestCommand()); addCommand(new SendTypingCommand()); addCommand(new SetPinCommand()); + addCommand(new SubmitRateLimitChallengeCommand()); addCommand(new TrustCommand()); addCommand(new UnblockCommand()); addCommand(new UnregisterCommand()); diff --git a/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java b/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java new file mode 100644 index 00000000..46f69896 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java @@ -0,0 +1,44 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; + +import org.asamk.signal.OutputWriter; +import org.asamk.signal.commands.exceptions.CommandException; +import org.asamk.signal.commands.exceptions.IOErrorException; +import org.asamk.signal.manager.Manager; + +import java.io.IOException; + +public class SubmitRateLimitChallengeCommand implements JsonRpcLocalCommand { + + @Override + public String getName() { + return "submitRateLimitChallenge"; + } + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.help( + "Submit a captcha challenge to lift the rate limit. This command should only be necessary when sending fails with a proof required error."); + subparser.addArgument("--challenge") + .required(true) + .help("The challenge token taken from the proof required error."); + subparser.addArgument("--captcha") + .required(true) + .help("The captcha token from the solved captcha on the signal website."); + } + + @Override + public void handleCommand(final Namespace ns, final Manager m, OutputWriter outputWriter) throws CommandException { + final var challenge = ns.getString("challenge"); + final var captchaString = ns.getString("captcha"); + final var captcha = captchaString == null ? null : captchaString.replace("signalcaptcha://", ""); + + try { + m.submitRateLimitRecaptchaChallenge(challenge, captcha); + } catch (IOException e) { + throw new IOErrorException("Submit challenge error: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/org/asamk/signal/util/ErrorUtils.java b/src/main/java/org/asamk/signal/util/ErrorUtils.java index ef1956c3..e2454925 100644 --- a/src/main/java/org/asamk/signal/util/ErrorUtils.java +++ b/src/main/java/org/asamk/signal/util/ErrorUtils.java @@ -67,7 +67,17 @@ public class ErrorUtils { } else if (result.getProofRequiredFailure() != null) { final var failure = result.getProofRequiredFailure(); return String.format( - "CAPTCHA proof required for sending to \"%s\", available options \"%s\" with challenge token \"%s\", or wait \"%d\" seconds", + "CAPTCHA proof required for sending to \"%s\", available options \"%s\" with challenge token \"%s\", or wait \"%d\" seconds.\n" + + ( + failure.getOptions().contains(ProofRequiredException.Option.RECAPTCHA) + ? + "To get the captcha token, go to https://signalcaptchas.org/registration/generate.html\n" + + "Check the developer tools (F12) console for a failed redirect to signalcaptcha://\n" + + "Everything after signalcaptcha:// is the captcha token.\n" + + "Use the following command to submit the captcha token:\n" + + "signal-cli submitRateLimitChallenge --challenge CHALLENGE_TOKEN --captcha CAPTCHA_TOKEN" + : "" + ), identifier, failure.getOptions() .stream() From 50e5acdf52139cc607a7bed615e4ab33912c8471 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 10 Sep 2021 10:13:51 +0200 Subject: [PATCH 4/4] Fix printing proof required error libsignal-service classifies it as network failure as well. --- .../java/org/asamk/signal/util/ErrorUtils.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/asamk/signal/util/ErrorUtils.java b/src/main/java/org/asamk/signal/util/ErrorUtils.java index e2454925..8e824d34 100644 --- a/src/main/java/org/asamk/signal/util/ErrorUtils.java +++ b/src/main/java/org/asamk/signal/util/ErrorUtils.java @@ -58,13 +58,7 @@ public class ErrorUtils { public static String getErrorMessageFromSendMessageResult(SendMessageResult result) { var identifier = getLegacyIdentifier(result.getAddress()); - if (result.isNetworkFailure()) { - return String.format("Network failure for \"%s\"", identifier); - } else if (result.isUnregisteredFailure()) { - return String.format("Unregistered user \"%s\"", identifier); - } else if (result.getIdentityFailure() != null) { - return String.format("Untrusted Identity for \"%s\"", identifier); - } else if (result.getProofRequiredFailure() != null) { + if (result.getProofRequiredFailure() != null) { final var failure = result.getProofRequiredFailure(); return String.format( "CAPTCHA proof required for sending to \"%s\", available options \"%s\" with challenge token \"%s\", or wait \"%d\" seconds.\n" @@ -85,6 +79,12 @@ public class ErrorUtils { .collect(Collectors.joining(", ")), failure.getToken(), failure.getRetryAfterSeconds()); + } else if (result.isNetworkFailure()) { + return String.format("Network failure for \"%s\"", identifier); + } else if (result.isUnregisteredFailure()) { + return String.format("Unregistered user \"%s\"", identifier); + } else if (result.getIdentityFailure() != null) { + return String.format("Untrusted Identity for \"%s\"", identifier); } return null; }