Add basic support for receiving messages at your PNI.

We haven't implemented merging yet, so this is still very basic, but it
"works".
This commit is contained in:
Greyson Parrelli
2022-04-13 16:22:22 -04:00
parent 41e417ff0b
commit 35a9fddbb2
14 changed files with 136 additions and 23 deletions

View File

@@ -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()
}
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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()
}

View File

@@ -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<Job> jobs = new LinkedList<>();

View File

@@ -2713,7 +2713,9 @@
<string name="preferences__internal_clear_history" translatable="false">Clear history</string>
<string name="preferences__internal_clear_history_description" translatable="false">Clears all CDS history, meaning the next sync will consider all numbers to be new.</string>
<string name="preferences__internal_clear_all_service_ids" translatable="false">Clear all service IDs</string>
<string name="preferences__internal_clear_all_service_ids_description" translatable="false">Clears all known service IDs. Do not use on your personal device!</string>
<string name="preferences__internal_clear_all_service_ids_description" translatable="false">Clears all known service IDs (except your own). Do not use on your personal device!</string>
<string name="preferences__internal_clear_all_profile_keys" translatable="false">Clear all profile keys</string>
<string name="preferences__internal_clear_all_profile_keys_description" translatable="false">Clears all known profile keys (except your own). Do not use on your personal device!</string>
<!-- Payments -->

View File

@@ -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(),

View File

@@ -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());
}

View File

@@ -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());
}
}

View File

@@ -14,6 +14,7 @@ public final class SignalServiceMetadata {
private final boolean needsReceipt;
private final String serverGuid;
private final Optional<byte[]> 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<byte[]> groupId)
Optional<byte[]> 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<byte[]> getGroupId() {
return groupId;
}
public String getDestinationUuid() {
return destinationUuid;
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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 {