diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt index 5f1fe94075..2b4cd934d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt @@ -424,6 +424,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter } ) + clickPref( + title = DSLSettingsText.from(R.string.preferences__internal_clear_all_profile_keys), + summary = DSLSettingsText.from(R.string.preferences__internal_clear_all_profile_keys_description), + onClick = { + clearAllProfileKeys() + } + ) + dividerPref() sectionHeaderPref(R.string.ConversationListTabs__stories) @@ -548,6 +556,7 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter private fun clearAllServiceIds() { MaterialAlertDialogBuilder(requireContext()) + .setTitle("Clear all serviceIds?") .setMessage("Are you sure? Never do this on a non-test device.") .setPositiveButton(android.R.string.ok) { _, _ -> SignalDatabase.recipients.debugClearServiceIds() @@ -558,4 +567,18 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter } .show() } + + private fun clearAllProfileKeys() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("Clear all profile keys?") + .setMessage("Are you sure? Never do this on a non-test device.") + .setPositiveButton(android.R.string.ok) { _, _ -> + SignalDatabase.recipients.debugClearServiceIds() + Toast.makeText(context, "Cleared all profile keys.", Toast.LENGTH_SHORT).show() + } + .setNegativeButton(android.R.string.cancel) { d, _ -> + d.dismiss() + } + .show() + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt index 18a80bf878..a06fe057c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt @@ -60,12 +60,27 @@ class InternalConversationSettingsFragment : DSLSettingsFragment( ) if (!recipient.isGroup) { - val serviceId = recipient.serviceId.map(ServiceId::toString).orElse("null") - longClickPref( - title = DSLSettingsText.from("ServiceId"), - summary = DSLSettingsText.from(serviceId), - onLongClick = { copyToClipboard(serviceId) } - ) + if (recipient.isSelf) { + val aci: String = SignalStore.account().aci?.toString() ?: "null" + longClickPref( + title = DSLSettingsText.from("ACI"), + summary = DSLSettingsText.from(aci), + onLongClick = { copyToClipboard(aci) } + ) + val pni: String = SignalStore.account().pni?.toString() ?: "null" + longClickPref( + title = DSLSettingsText.from("PNI"), + summary = DSLSettingsText.from(pni), + onLongClick = { copyToClipboard(pni) } + ) + } else { + val serviceId: String = recipient.serviceId.map(ServiceId::toString).orElse("null") + longClickPref( + title = DSLSettingsText.from("ServiceId"), + summary = DSLSettingsText.from(serviceId), + onLongClick = { copyToClipboard(serviceId) } + ) + } } if (state.groupId != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalServiceDataStoreImpl.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalServiceDataStoreImpl.java index 0542837e62..6920b18bdd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalServiceDataStoreImpl.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalServiceDataStoreImpl.java @@ -29,7 +29,7 @@ public final class SignalServiceDataStoreImpl implements SignalServiceDataStore if (accountIdentifier.equals(SignalStore.account().getAci())) { return aciStore; } else if (accountIdentifier.equals(SignalStore.account().getPni())) { - throw new AssertionError("Not to be used yet!"); + return pniStore; } else { throw new IllegalArgumentException("No matching store found for " + accountIdentifier); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java index 23db36e932..e1ace1b0be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java @@ -93,7 +93,8 @@ public class PushDatabase extends Database { Util.isEmpty(content) ? null : Base64.decode(content), cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_RECEIVED_TIMESTAMP)), cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_DELIVERED_TIMESTAMP)), - cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID))); + cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID)), + ""); } } catch (IOException e) { Log.w(TAG, e); @@ -178,7 +179,8 @@ public class PushDatabase extends Database { content != null ? Base64.decode(content) : null, serverReceivedTimestamp, serverDeliveredTimestamp, - serverGuid); + serverGuid, + ""); } catch (IOException e) { throw new AssertionError(e); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt index b7a0e343a5..5abef9e8e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt @@ -2932,6 +2932,21 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) : SERVICE_ID to null, PNI_COLUMN to null ) + .where("$ID != ?", Recipient.self().id) + .run() + } + + /** + * Should only be used for debugging! A very destructive action that clears all known profile keys and credentials. + */ + fun debugClearProfileKeys() { + writableDatabase + .update(TABLE_NAME) + .values( + PROFILE_KEY to null, + PROFILE_KEY_CREDENTIAL to null + ) + .where("$ID != ?", Recipient.self().id) .run() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java index 484a3a4b53..56799ea35f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java @@ -50,6 +50,7 @@ import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; @@ -76,8 +77,26 @@ public final class MessageDecryptionUtil { * caller. */ public static @NonNull DecryptionResult decrypt(@NonNull Context context, @NonNull SignalServiceEnvelope envelope) { - SignalServiceAccountDataStore protocolStore = ApplicationDependencies.getProtocolStore().aci(); - SignalServiceAddress localAddress = new SignalServiceAddress(SignalStore.account().requireAci(), Recipient.self().requireE164()); + ServiceId aci = SignalStore.account().requireAci(); + ServiceId pni = SignalStore.account().requirePni(); + + ServiceId destination; + if (!FeatureFlags.usePnpCds()) { + destination = aci; + } else if (envelope.hasDestinationUuid()) { + destination = ServiceId.parseOrThrow(envelope.getDestinationUuid()); + } else { + Log.w(TAG, "No destinationUuid set! Defaulting to ACI."); + destination = aci; + } + + if (!destination.equals(aci) && !destination.equals(pni)) { + Log.w(TAG, "Destination of " + destination + " does not match our ACI (" + aci + ") or PNI (" + pni + ")! Defaulting to ACI."); + destination = aci; + } + + SignalServiceAccountDataStore protocolStore = ApplicationDependencies.getProtocolStore().get(destination); + SignalServiceAddress localAddress = new SignalServiceAddress(SignalStore.account().requireAci(), SignalStore.account().getE164()); SignalServiceCipher cipher = new SignalServiceCipher(localAddress, SignalStore.account().getDeviceId(), protocolStore, ReentrantSessionLock.INSTANCE, UnidentifiedAccessUtil.getCertificateValidator()); List jobs = new LinkedList<>(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9514a225ff..93ad79bd49 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2713,7 +2713,9 @@ Clear history Clears all CDS history, meaning the next sync will consider all numbers to be new. Clear all service IDs - Clears all known service IDs. Do not use on your personal device! + Clears all known service IDs (except your own). Do not use on your personal device! + Clear all profile keys + Clears all known profile keys (except your own). Do not use on your personal device! diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index ef92b81e54..aec5406f83 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -212,7 +212,8 @@ public class SignalServiceMessageReceiver { entity.getContent(), entity.getServerTimestamp(), messageResult.getServerDeliveredTimestamp(), - entity.getServerUuid()); + entity.getServerUuid(), + entity.getDestinationUuid()); } else { envelope = new SignalServiceEnvelope(entity.getType(), entity.getTimestamp(), diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index eb54ef8d9d..e6a0a8d997 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -195,7 +195,7 @@ public class SignalServiceCipher { SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress)); paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext)); - metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty()); + metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(sourceAddress)); } else if (envelope.isSignalMessage()) { @@ -203,10 +203,10 @@ public class SignalServiceCipher { SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress)); paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext)); - metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty()); + metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); } else if (envelope.isPlaintextContent()) { paddedMessage = new PlaintextContent(ciphertext).getBody(); - metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty()); + metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid()); } else if (envelope.isUnidentifiedSender()) { SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().uuid(), localAddress.getNumber().orElse(null), localDeviceId)); DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp()); @@ -224,7 +224,7 @@ public class SignalServiceCipher { } paddedMessage = result.getPaddedMessage(); - metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), needsReceipt, envelope.getServerGuid(), groupId); + metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), needsReceipt, envelope.getServerGuid(), groupId, envelope.getDestinationUuid()); } else { throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType()); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java index 63ef1c1d6b..270911b8d3 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceEnvelope.java @@ -12,12 +12,14 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.util.OptionalUtil; +import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope; import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceEnvelopeProto; import org.whispersystems.util.Base64; import java.io.IOException; import java.util.Optional; +import java.util.UUID; /** * This class represents an encrypted Signal Service envelope. @@ -62,13 +64,15 @@ public class SignalServiceEnvelope { byte[] content, long serverReceivedTimestamp, long serverDeliveredTimestamp, - String uuid) + String uuid, + String destinationUuid) { Envelope.Builder builder = Envelope.newBuilder() .setType(Envelope.Type.valueOf(type)) .setSourceDevice(senderDevice) .setTimestamp(timestamp) - .setServerTimestamp(serverReceivedTimestamp); + .setServerTimestamp(serverReceivedTimestamp) + .setDestinationUuid(destinationUuid); if (sender.isPresent()) { builder.setSourceUuid(sender.get().getServiceId().toString()); @@ -249,6 +253,14 @@ public class SignalServiceEnvelope { return envelope.getType().getNumber() == Envelope.Type.PLAINTEXT_CONTENT_VALUE; } + public boolean hasDestinationUuid() { + return envelope.hasDestinationUuid() && UuidUtil.isUuid(envelope.getDestinationUuid()); + } + + public String getDestinationUuid() { + return envelope.getDestinationUuid(); + } + public byte[] serialize() { SignalServiceEnvelopeProto.Builder builder = SignalServiceEnvelopeProto.newBuilder() .setType(getType()) @@ -277,6 +289,11 @@ public class SignalServiceEnvelope { builder.setServerGuid(getServerGuid()); } + if (hasDestinationUuid()) { + builder.setDestinationUuid(getDestinationUuid().toString()); + } + + return builder.build().toByteArray(); } @@ -296,6 +313,7 @@ public class SignalServiceEnvelope { proto.hasContent() ? proto.getContent().toByteArray() : null, proto.getServerReceivedTimestamp(), proto.getServerDeliveredTimestamp(), - proto.getServerGuid()); + proto.getServerGuid(), + proto.getDestinationUuid()); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceMetadata.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceMetadata.java index cef21fec59..a6d65d1ac9 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceMetadata.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceMetadata.java @@ -14,6 +14,7 @@ public final class SignalServiceMetadata { private final boolean needsReceipt; private final String serverGuid; private final Optional groupId; + private final String destinationUuid; public SignalServiceMetadata(SignalServiceAddress sender, int senderDevice, @@ -22,7 +23,8 @@ public final class SignalServiceMetadata { long serverDeliveredTimestamp, boolean needsReceipt, String serverGuid, - Optional groupId) + Optional groupId, + String destinationUuid) { this.sender = sender; this.senderDevice = senderDevice; @@ -32,6 +34,7 @@ public final class SignalServiceMetadata { this.needsReceipt = needsReceipt; this.serverGuid = serverGuid; this.groupId = groupId; + this.destinationUuid = destinationUuid != null ? destinationUuid : ""; } public SignalServiceAddress getSender() { @@ -65,4 +68,8 @@ public final class SignalServiceMetadata { public Optional getGroupId() { return groupId; } + + public String getDestinationUuid() { + return destinationUuid; + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceEnvelopeEntity.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceEnvelopeEntity.java index b4b48e1e14..0712905cbe 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceEnvelopeEntity.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SignalServiceEnvelopeEntity.java @@ -22,6 +22,9 @@ public class SignalServiceEnvelopeEntity { @JsonProperty private int sourceDevice; + @JsonProperty + private String destinationUuid; + @JsonProperty private byte[] message; @@ -79,4 +82,8 @@ public class SignalServiceEnvelopeEntity { public String getServerUuid() { return guid; } + + public String getDestinationUuid() { + return destinationUuid; + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/serialize/SignalServiceMetadataProtobufSerializer.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/serialize/SignalServiceMetadataProtobufSerializer.java index 7d9b1a2c0a..6de5bc1ffd 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/serialize/SignalServiceMetadataProtobufSerializer.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/serialize/SignalServiceMetadataProtobufSerializer.java @@ -20,7 +20,8 @@ public final class SignalServiceMetadataProtobufSerializer { .setTimestamp(metadata.getTimestamp()) .setServerReceivedTimestamp(metadata.getServerReceivedTimestamp()) .setServerDeliveredTimestamp(metadata.getServerDeliveredTimestamp()) - .setServerGuid(metadata.getServerGuid()); + .setServerGuid(metadata.getServerGuid()) + .setDestinationUuid(metadata.getDestinationUuid()); if (metadata.getGroupId().isPresent()) { builder.setGroupId(ByteString.copyFrom(metadata.getGroupId().get())); @@ -37,6 +38,7 @@ public final class SignalServiceMetadataProtobufSerializer { metadata.getServerDeliveredTimestamp(), metadata.getNeedsReceipt(), metadata.getServerGuid(), - Optional.ofNullable(metadata.getGroupId()).map(ByteString::toByteArray)); + Optional.ofNullable(metadata.getGroupId()).map(ByteString::toByteArray), + metadata.getDestinationUuid()); } } diff --git a/libsignal/service/src/main/proto/InternalSerialization.proto b/libsignal/service/src/main/proto/InternalSerialization.proto index 4505894735..37643222de 100644 --- a/libsignal/service/src/main/proto/InternalSerialization.proto +++ b/libsignal/service/src/main/proto/InternalSerialization.proto @@ -32,6 +32,7 @@ message SignalServiceEnvelopeProto { optional int64 serverReceivedTimestamp = 8; optional int64 serverDeliveredTimestamp = 9; optional string serverGuid = 10; + optional string destinationUuid = 11; } message MetadataProto { @@ -43,6 +44,7 @@ message MetadataProto { optional bool needsReceipt = 4; optional string serverGuid = 7; optional bytes groupId = 8; + optional string destinationUuid = 9; } message AddressProto {