Improve error handling for empty prekey bundles.

This commit is contained in:
Greyson Parrelli
2026-04-14 20:55:28 -04:00
committed by jeffrey-signal
parent d2243707b5
commit 45e6e06c01
3 changed files with 44 additions and 8 deletions
@@ -58,9 +58,9 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.whispersystems.signalservice.api.InvalidPreKeyException;
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil;
import org.whispersystems.signalservice.api.messages.AttachmentTransferProgress;
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
@@ -69,6 +69,7 @@ import org.whispersystems.signalservice.api.messages.SignalServicePreview;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
import org.whispersystems.signalservice.internal.push.BodyRange;
import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
@@ -150,6 +151,10 @@ public abstract class PushSendJob extends SendJob {
return false;
}
if (exception instanceof InvalidPreKeyException) {
return false;
}
return exception instanceof IOException ||
exception instanceof RetryLaterException ||
exception instanceof ProofRequiredException;
@@ -2103,7 +2103,11 @@ public class SignalServiceMessageSender {
}
} catch (MismatchedDevicesException mde) {
Log.w(TAG, "[sendMessage][" + timestamp + "] Handling mismatched devices. (" + mde.getMessage() + ")");
handleMismatchedDevices(recipient, mde.getMismatchedDevices());
try {
handleMismatchedDevices(recipient, mde.getMismatchedDevices());
} catch (InvalidPreKeyException e) {
return SendMessageResult.invalidPreKeyFailure(recipient);
}
} catch (StaleDevicesException ste) {
Log.w(TAG, "[sendMessage][" + timestamp + "] Handling stale devices. (" + ste.getMessage() + ")");
handleStaleDevices(recipient, ste.getStaleDevices());
@@ -2580,15 +2584,39 @@ public class SignalServiceMessageSender {
if (error instanceof MismatchedDeviceException) {
MismatchedDeviceException mismatchedDeviceException = (MismatchedDeviceException) error;
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Handling mismatched devices. (" + mismatchedDeviceException.getMessage() + ")");
List<SendMessageResult> invalidPreKeyResults = new LinkedList<>();
for (MismatchedDeviceException.Entry entry : mismatchedDeviceException.getEntries()) {
SignalServiceAddress address = new SignalServiceAddress(ServiceId.fromLibSignal(entry.getAccount()));
MismatchedDevices devices = MismatchedDevices.fromLibSignal(entry);
handleMismatchedDevices(address, devices);
try {
handleMismatchedDevices(address, devices);
} catch (InvalidPreKeyException e) {
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Invalid prekey for " + address.getIdentifier() + " during mismatch handling.");
invalidPreKeyResults.add(SendMessageResult.invalidPreKeyFailure(address));
continue;
}
if (entry.getStaleDevices().length > 0) {
StaleDevices staleDevices = StaleDevices.fromLibSignal(entry);
handleStaleDevices(address, staleDevices);
}
}
if (!invalidPreKeyResults.isEmpty()) {
Set<ServiceId> failedAddresses = invalidPreKeyResults.stream()
.map(r -> r.getAddress().getServiceId())
.collect(Collectors.toSet());
List<SendMessageResult> networkFailures = recipients.stream()
.filter(r -> !failedAddresses.contains(r.getServiceId()))
.map(SendMessageResult::networkFailure)
.collect(Collectors.toList());
List<SendMessageResult> combinedResults = new LinkedList<>();
combinedResults.addAll(invalidPreKeyResults);
combinedResults.addAll(networkFailures);
return combinedResults;
}
} else if (error instanceof RequestUnauthorizedException) {
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Invalid access header.");
throw new InvalidUnidentifiedAccessHeaderException();
@@ -8,10 +8,13 @@ package org.whispersystems.signalservice.api.keys
import org.signal.core.util.logging.Log
import org.signal.core.util.toByteArray
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.InvalidKeyException
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.signal.libsignal.protocol.kem.KEMPublicKey
import org.signal.libsignal.protocol.state.PreKeyBundle
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.whispersystems.signalservice.api.InvalidPreKeyException
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
@@ -29,7 +32,6 @@ import org.whispersystems.signalservice.internal.push.PreKeyResponse
import org.whispersystems.signalservice.internal.push.PreKeyState
import org.whispersystems.signalservice.internal.put
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import java.io.IOException
import java.security.MessageDigest
import java.util.LinkedList
@@ -169,7 +171,8 @@ class KeysApi(
if (bundles.isNotEmpty()) {
NetworkResult.Success(bundles[0])
} else {
NetworkResult.NetworkError(IOException("No prekeys available!"))
val address = SignalProtocolAddress(destination.identifier, deviceId)
NetworkResult.NetworkError(InvalidPreKeyException(address, InvalidKeyException("No valid prekeys available for $address")))
}
}
}
@@ -216,7 +219,7 @@ class KeysApi(
if (device.getSignedPreKey() != null) {
val rawSignedPreKeyId = device.getSignedPreKey().keyId
if (rawSignedPreKeyId !in 0..Int.MAX_VALUE) {
if (rawSignedPreKeyId !in 0..Int.MAX_VALUE.toLong()) {
Log.w(TAG, "Signed pre-key ID for device ${device.deviceId} is out of valid range! Skipping.")
continue
}
@@ -230,7 +233,7 @@ class KeysApi(
if (device.getPreKey() != null) {
val rawPreKeyId = device.getPreKey().keyId
if (rawPreKeyId !in 0..Int.MAX_VALUE) {
if (rawPreKeyId !in 0..Int.MAX_VALUE.toLong()) {
Log.w(TAG, "Pre-key ID for device ${device.deviceId} is out of valid range! Skipping.")
continue
}
@@ -240,7 +243,7 @@ class KeysApi(
if (device.getKyberPreKey() != null) {
val rawKyberPreKeyId = device.getKyberPreKey().keyId
if (rawKyberPreKeyId !in 0..Int.MAX_VALUE) {
if (rawKyberPreKeyId !in 0..Int.MAX_VALUE.toLong()) {
Log.w(TAG, "Kyber pre-key ID for device ${device.deviceId} is out of valid range! Skipping.")
continue
}