Add Group Send Endorsements support.

This commit is contained in:
Cody Henthorne
2024-07-08 12:47:20 -04:00
parent 414368e251
commit f5abd7acdf
86 changed files with 1691 additions and 887 deletions

View File

@@ -77,6 +77,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver;
import org.thoughtcrime.securesms.messages.GroupSendEndorsementInternalNotifier;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
import org.thoughtcrime.securesms.mms.SignalGlideModule;
@@ -222,6 +223,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(GroupRingCleanupJob::enqueue)
.addPostRender(LinkedDeviceInactiveCheckJob::enqueueIfNecessary)
.addPostRender(() -> ActiveCallManager.clearNotifications(this))
.addPostRender(() -> GroupSendEndorsementInternalNotifier.init())
.execute();
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");

View File

@@ -241,7 +241,7 @@ fun RecipientTable.restoreGroupFromBackup(group: Group): RecipientId {
}
val recipientId = writableDatabase.insert(RecipientTable.TABLE_NAME, null, values)
val restoredId = SignalDatabase.groups.create(masterKey, decryptedState)
val restoredId = SignalDatabase.groups.create(masterKey, decryptedState, groupSendEndorsements = null)
if (restoredId != null) {
SignalDatabase.groups.setShowAsStoryState(restoredId, group.storySendMode.toGroupShowAsStoryState())
}

View File

@@ -129,7 +129,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment(
textPref(
title = DSLSettingsText.from("Sealed Sender Mode"),
summary = DSLSettingsText.from(recipient.unidentifiedAccessMode.toString())
summary = DSLSettingsText.from(recipient.sealedSenderAccessMode.toString())
)
textPref(

View File

@@ -1,17 +1,17 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
import org.signal.core.util.Base64;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
import org.signal.libsignal.metadata.certificate.SenderCertificate;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
@@ -21,10 +21,8 @@ import org.thoughtcrime.securesms.keyvalue.CertificateType;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.signal.core.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import java.io.IOException;
import java.util.Collections;
@@ -35,9 +33,9 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class UnidentifiedAccessUtil {
public class SealedSenderAccessUtil {
private static final String TAG = Log.tag(UnidentifiedAccessUtil.class);
private static final String TAG = Log.tag(SealedSenderAccessUtil.class);
private static final byte[] UNRESTRICTED_KEY = new byte[16];
@@ -46,28 +44,34 @@ public class UnidentifiedAccessUtil {
}
@WorkerThread
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context, @NonNull Recipient recipient) {
return getAccessFor(context, recipient, true);
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient) {
return getSealedSenderAccessFor(recipient, true);
}
@WorkerThread
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context, @NonNull Recipient recipient, boolean log) {
return getAccessFor(context, Collections.singletonList(recipient), log).get(0);
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient, boolean log) {
return SealedSenderAccess.forIndividual(getAccessFor(recipient, log));
}
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient, @Nullable SealedSenderAccess.CreateGroupSendToken createGroupSendToken) {
return SealedSenderAccess.forIndividualWithGroupFallback(getAccessFor(recipient, true), getSealedSenderCertificate(), createGroupSendToken);
}
@WorkerThread
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients) {
return getAccessFor(context, recipients, true);
private static @Nullable UnidentifiedAccess getAccessFor(@NonNull Recipient recipient, boolean log) {
return getAccessFor(Collections.singletonList(recipient), false, log)
.get(0)
.orElse(null);
}
@WorkerThread
public static Map<RecipientId, Optional<UnidentifiedAccessPair>> getAccessMapFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory) {
List<Optional<UnidentifiedAccessPair>> accessList = getAccessFor(context, recipients, isForStory, true);
public static Map<RecipientId, Optional<UnidentifiedAccess>> getAccessMapFor(@NonNull List<Recipient> recipients, boolean isForStory) {
List<Optional<UnidentifiedAccess>> accessList = getAccessFor(recipients, isForStory, true);
Iterator<Recipient> recipientIterator = recipients.iterator();
Iterator<Optional<UnidentifiedAccessPair>> accessIterator = accessList.iterator();
Iterator<Recipient> recipientIterator = recipients.iterator();
Iterator<Optional<UnidentifiedAccess>> accessIterator = accessList.iterator();
Map<RecipientId, Optional<UnidentifiedAccessPair>> accessMap = new HashMap<>(recipients.size());
Map<RecipientId, Optional<UnidentifiedAccess>> accessMap = new HashMap<>(recipients.size());
while (recipientIterator.hasNext()) {
accessMap.put(recipientIterator.next().getId(), accessIterator.next());
@@ -77,40 +81,22 @@ public class UnidentifiedAccessUtil {
}
@WorkerThread
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean log) {
return getAccessFor(context, recipients, false, log);
}
@WorkerThread
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory, boolean log) {
final byte[] ourUnidentifiedAccessKey;
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
} else {
ourUnidentifiedAccessKey = ProfileKeyUtil.getSelfProfileKey().deriveAccessKey();
}
private static List<Optional<UnidentifiedAccess>> getAccessFor(@NonNull List<Recipient> recipients, boolean isForStory, boolean log) {
CertificateType certificateType = getUnidentifiedAccessCertificateType();
byte[] ourUnidentifiedAccessCertificate = SignalStore.certificate().getUnidentifiedAccessCertificate(certificateType);
List<Optional<UnidentifiedAccessPair>> access = recipients.parallelStream().map(recipient -> {
UnidentifiedAccessPair unidentifiedAccessPair = null;
List<Optional<UnidentifiedAccess>> access = recipients.parallelStream().map(recipient -> {
UnidentifiedAccess unidentifiedAccess = null;
if (ourUnidentifiedAccessCertificate != null) {
try {
UnidentifiedAccess theirAccess = getTargetUnidentifiedAccess(recipient, ourUnidentifiedAccessCertificate, isForStory);
UnidentifiedAccess ourAccess = new UnidentifiedAccess(ourUnidentifiedAccessKey, ourUnidentifiedAccessCertificate, false);
if (theirAccess != null) {
unidentifiedAccessPair = new UnidentifiedAccessPair(theirAccess, ourAccess);
}
unidentifiedAccess = getTargetUnidentifiedAccess(recipient, ourUnidentifiedAccessCertificate, isForStory);
} catch (InvalidCertificateException e) {
Log.w(TAG, "Invalid unidentified access certificate!", e);
}
} else {
Log.w(TAG, "Missing unidentified access certificate!");
Log.w(TAG, "Missing our unidentified access certificate!");
}
return Optional.ofNullable(unidentifiedAccessPair);
return Optional.ofNullable(unidentifiedAccess);
}).collect(Collectors.toList());
int unidentifiedCount = Stream.of(access).filter(Optional::isPresent).toList().size();
@@ -123,28 +109,17 @@ public class UnidentifiedAccessUtil {
return access;
}
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
public static @Nullable SenderCertificate getSealedSenderCertificate() {
byte[] unidentifiedAccessCertificate = getUnidentifiedAccessCertificate();
if (unidentifiedAccessCertificate == null) {
return null;
}
try {
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate();
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
}
if (ourUnidentifiedAccessCertificate != null) {
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate,
false),
new UnidentifiedAccess(ourUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate,
false)));
}
return Optional.empty();
return new SenderCertificate(unidentifiedAccessCertificate);
} catch (InvalidCertificateException e) {
Log.w(TAG, e);
return Optional.empty();
return null;
}
}
@@ -166,7 +141,7 @@ public class UnidentifiedAccessUtil {
byte[] accessKey;
switch (recipient.resolve().getUnidentifiedAccessMode()) {
switch (recipient.resolve().getSealedSenderAccessMode()) {
case UNKNOWN:
if (theirProfileKey == null) {
if (isForStory) {
@@ -192,7 +167,7 @@ public class UnidentifiedAccessUtil {
accessKey = UNRESTRICTED_KEY;
break;
default:
throw new AssertionError("Unknown mode: " + recipient.getUnidentifiedAccessMode().getMode());
throw new AssertionError("Unknown mode: " + recipient.getSealedSenderAccessMode().getMode());
}
if (accessKey == null && isForStory) {

View File

@@ -19,7 +19,9 @@ import org.signal.core.util.isAbsent
import org.signal.core.util.logging.Log
import org.signal.core.util.optionalString
import org.signal.core.util.readToList
import org.signal.core.util.readToMap
import org.signal.core.util.readToSingleInt
import org.signal.core.util.readToSingleLong
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireBlob
import org.signal.core.util.requireBoolean
@@ -30,7 +32,11 @@ import org.signal.core.util.requireString
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
import org.signal.libsignal.zkgroup.InvalidInputException
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken
import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember
@@ -39,6 +45,7 @@ import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterat
import org.thoughtcrime.securesms.crypto.SenderKeyUtil
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.recipients
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.database.model.GroupSendEndorsementRecords
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.BadGroupIdException
import org.thoughtcrime.securesms.groups.GroupId
@@ -50,6 +57,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
import org.whispersystems.signalservice.api.groupsv2.findMemberByAci
import org.whispersystems.signalservice.api.groupsv2.findPendingByServiceId
import org.whispersystems.signalservice.api.groupsv2.findRequestingByAci
@@ -63,6 +71,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.io.Closeable
import java.security.SecureRandom
import java.time.Instant
import java.util.Optional
import java.util.stream.Collectors
import javax.annotation.CheckReturnValue
@@ -94,6 +103,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
const val DISTRIBUTION_ID = "distribution_id"
const val SHOW_AS_STORY_STATE = "show_as_story_state"
const val LAST_FORCE_UPDATE_TIMESTAMP = "last_force_update_timestamp"
const val GROUP_SEND_ENDORSEMENTS_EXPIRATION = "group_send_endorsements_expiration"
/** 32 bytes serialized [GroupMasterKey] */
const val V2_MASTER_KEY = "master_key"
@@ -125,12 +135,13 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
$UNMIGRATED_V1_MEMBERS TEXT DEFAULT NULL,
$DISTRIBUTION_ID TEXT UNIQUE DEFAULT NULL,
$SHOW_AS_STORY_STATE INTEGER DEFAULT ${ShowAsStoryState.IF_ACTIVE.code},
$LAST_FORCE_UPDATE_TIMESTAMP INTEGER DEFAULT 0
$LAST_FORCE_UPDATE_TIMESTAMP INTEGER DEFAULT 0,
$GROUP_SEND_ENDORSEMENTS_EXPIRATION INTEGER DEFAULT 0
)
"""
@JvmField
val CREATE_INDEXS = MembershipTable.CREATE_INDEXES
val CREATE_INDEXS: Array<String> = MembershipTable.CREATE_INDEXES
private val GROUP_PROJECTION = arrayOf(
GROUP_ID,
@@ -147,7 +158,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
V2_MASTER_KEY,
V2_REVISION,
V2_DECRYPTED_GROUP,
LAST_FORCE_UPDATE_TIMESTAMP
LAST_FORCE_UPDATE_TIMESTAMP,
GROUP_SEND_ENDORSEMENTS_EXPIRATION
)
val TYPED_GROUP_PROJECTION = GROUP_PROJECTION
@@ -165,6 +177,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
const val ID = "_id"
const val GROUP_ID = "group_id"
const val RECIPIENT_ID = "recipient_id"
const val ENDORSEMENT = "endorsement"
//language=sql
const val CREATE_TABLE = """
@@ -172,6 +185,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
$ID INTEGER PRIMARY KEY,
$GROUP_ID TEXT NOT NULL REFERENCES ${GroupTable.TABLE_NAME} (${GroupTable.GROUP_ID}) ON DELETE CASCADE,
$RECIPIENT_ID INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
$ENDORSEMENT BLOB DEFAULT NULL,
UNIQUE($GROUP_ID, $RECIPIENT_ID)
)
"""
@@ -568,19 +582,19 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
throw LegacyGroupInsertException(groupId)
}
return create(groupId, title, members, avatar, null, null)
return create(groupId, title, members, avatar, null, null, null)
}
@CheckReturnValue
fun create(groupId: GroupId.Mms, title: String?, members: Collection<RecipientId>): Boolean {
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null)
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null, null)
}
@CheckReturnValue
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup): GroupId.V2? {
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?): GroupId.V2? {
val groupId = GroupId.v2(groupMasterKey)
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState)) {
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState, receivedGroupSendEndorsements = groupSendEndorsements)) {
groupId
} else {
null
@@ -604,10 +618,9 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
if (updated < 1) {
Log.w(TAG, "No group entry. Creating restore placeholder for $groupId")
create(
groupMasterKey,
DecryptedGroup.Builder()
.revision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
.build()
groupMasterKey = groupMasterKey,
groupState = DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION),
groupSendEndorsements = null
)
} else {
Log.w(TAG, "Had a group entry, but it was missing a master key. Updated.")
@@ -628,7 +641,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
memberCollection: Collection<RecipientId>,
avatar: SignalServiceAttachmentPointer?,
groupMasterKey: GroupMasterKey?,
groupState: DecryptedGroup?
groupState: DecryptedGroup?,
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?
): Boolean {
val membershipValues = mutableListOf<ContentValues>()
val groupRecipientId = recipients.getOrInsertFromGroupId(groupId)
@@ -640,7 +654,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
values.put(RECIPIENT_ID, groupRecipientId.serialize())
values.put(GROUP_ID, groupId.toString())
values.put(TITLE, title)
membershipValues.addAll(members.toContentValues(groupId))
membershipValues.addAll(members.toContentValues(groupId, receivedGroupSendEndorsements?.toGroupSendEndorsementRecords()))
values.put(MMS, groupId.isMms)
if (avatar != null) {
@@ -657,6 +671,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
if (groupId.isV2) {
values.put(ACTIVE, if (groupState != null && gv2GroupActive(groupState)) 1 else 0)
values.put(DISTRIBUTION_ID, DistributionId.create().toString())
values.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, receivedGroupSendEndorsements?.expirationMs ?: 0)
} else if (groupId.isV1) {
values.put(ACTIVE, 1)
values.put(EXPECTED_V2_ID, groupId.requireV1().deriveV2MigrationGroupId().toString())
@@ -676,7 +691,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
values.put(V2_REVISION, groupState.revision)
values.put(V2_DECRYPTED_GROUP, groupState.encode())
membershipValues.clear()
membershipValues.addAll(groupMembers.toContentValues(groupId))
membershipValues.addAll(groupMembers.toContentValues(groupId, receivedGroupSendEndorsements?.toGroupSendEndorsementRecords()))
} else {
if (groupId.isV2) {
throw AssertionError("V2 group id but no master key")
@@ -691,9 +706,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
return false
}
for (query in SqlUtil.buildBulkInsert(MembershipTable.TABLE_NAME, arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID), membershipValues)) {
for (query in SqlUtil.buildBulkInsert(MembershipTable.TABLE_NAME, arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT), membershipValues)) {
writableDatabase.execSQL(query.where, query.whereArgs)
}
writableDatabase.setTransactionSuccessful()
} finally {
writableDatabase.endTransaction()
@@ -745,11 +761,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
notifyConversationListListeners()
}
fun update(groupMasterKey: GroupMasterKey, decryptedGroup: DecryptedGroup) {
update(GroupId.v2(groupMasterKey), decryptedGroup)
fun update(groupMasterKey: GroupMasterKey, decryptedGroup: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?) {
update(GroupId.v2(groupMasterKey), decryptedGroup, groupSendEndorsements)
}
fun update(groupId: GroupId.V2, decryptedGroup: DecryptedGroup) {
fun update(groupId: GroupId.V2, decryptedGroup: DecryptedGroup, receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?) {
val groupRecipientId: RecipientId = recipients.getOrInsertFromGroupId(groupId)
val existingGroup: Optional<GroupRecord> = getGroup(groupId)
val title: String = decryptedGroup.title
@@ -760,6 +776,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
contentValues.put(V2_DECRYPTED_GROUP, decryptedGroup.encode())
contentValues.put(ACTIVE, if (gv2GroupActive(decryptedGroup)) 1 else 0)
if (receivedGroupSendEndorsements != null) {
contentValues.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, receivedGroupSendEndorsements.expirationMs)
}
if (existingGroup.isPresent && existingGroup.get().unmigratedV1Members.isNotEmpty() && existingGroup.get().isV2Group) {
val unmigratedV1Members: MutableSet<RecipientId> = existingGroup.get().unmigratedV1Members.toMutableSet()
@@ -781,6 +801,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
val groupMembers = getV2GroupMembers(decryptedGroup, true)
var groupSendEndorsementRecords: GroupSendEndorsementRecords? = receivedGroupSendEndorsements?.toGroupSendEndorsementRecords() ?: getGroupSendEndorsements(groupId)
val addedMembers: List<RecipientId> = if (existingGroup.isPresent && existingGroup.get().isV2Group) {
val change = GroupChangeReconstruct.reconstructGroupChange(existingGroup.get().requireV2GroupProperties().decryptedGroup, decryptedGroup)
@@ -800,6 +821,12 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
)
}
if (receivedGroupSendEndorsements == null && (removed.isNotEmpty() || change.newMembers.isNotEmpty())) {
Log.v(TAG, "Members were removed or added, and no new endorsements, clearing endorsements and GSE expiration")
contentValues.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, 0)
groupSendEndorsementRecords = null
}
change.newMembers.toAciList().toRecipientIds()
} else {
groupMembers
@@ -812,7 +839,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
.where("$GROUP_ID = ?", groupId.toString())
.run()
performMembershipUpdate(database, groupId, groupMembers)
performMembershipUpdate(database, groupId, groupMembers, groupSendEndorsementRecords)
}
if (decryptedGroup.disappearingMessagesTimer != null) {
@@ -867,7 +894,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
.toMutableList()
}
private fun performMembershipUpdate(database: SQLiteDatabase, groupId: GroupId, members: Collection<RecipientId>) {
private fun performMembershipUpdate(database: SQLiteDatabase, groupId: GroupId, members: Collection<RecipientId>, groupSendEndorsementRecords: GroupSendEndorsementRecords?) {
check(database.inTransaction())
database
.delete(MembershipTable.TABLE_NAME)
@@ -876,8 +903,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
val inserts = SqlUtil.buildBulkInsert(
MembershipTable.TABLE_NAME,
arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID),
members.toSet().toContentValues(groupId)
arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT),
members.toSet().toContentValues(groupId, groupSendEndorsementRecords)
)
inserts.forEach {
@@ -906,6 +933,94 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
.run()
}
fun getGroupSendEndorsementsExpiration(groupId: GroupId): Long {
return writableDatabase
.select(GROUP_SEND_ENDORSEMENTS_EXPIRATION)
.from(TABLE_NAME)
.where("$GROUP_ID = ?", groupId)
.run()
.readToSingleLong(0L)
}
fun updateGroupSendEndorsements(groupId: GroupId.V2, receivedGroupSendEndorsements: ReceivedGroupSendEndorsements) {
val endorsements: Map<RecipientId, GroupSendEndorsement?> = receivedGroupSendEndorsements.toGroupSendEndorsementRecords().endorsements
writableDatabase.withinTransaction { db ->
db.update(MembershipTable.TABLE_NAME, contentValuesOf(MembershipTable.ENDORSEMENT to null), "${MembershipTable.GROUP_ID} = ?", arrayOf(groupId.serialize()))
for ((recipientId, endorsement) in endorsements) {
db.update(MembershipTable.TABLE_NAME)
.values(MembershipTable.ENDORSEMENT to endorsement?.serialize())
.where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, recipientId)
.run()
}
writableDatabase
.update(TABLE_NAME)
.values(GROUP_SEND_ENDORSEMENTS_EXPIRATION to receivedGroupSendEndorsements.expirationMs)
.where("$GROUP_ID = ?", groupId)
.run()
}
}
fun getGroupSendEndorsements(groupId: GroupId): GroupSendEndorsementRecords {
val allEndorsements: Map<RecipientId, GroupSendEndorsement?> = readableDatabase
.select(MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT)
.from(MembershipTable.TABLE_NAME)
.where("${MembershipTable.GROUP_ID} = ?", groupId)
.run()
.readToMap { cursor ->
val recipientId = RecipientId.from(cursor.requireLong(MembershipTable.RECIPIENT_ID))
val endorsement = cursor.requireBlob(MembershipTable.ENDORSEMENT)?.let { endorsementBlob ->
try {
GroupSendEndorsement(endorsementBlob)
} catch (e: InvalidInputException) {
Log.w(TAG, "Unable to parse group send endorsement for $recipientId", e)
null
}
}
recipientId to endorsement
}
return GroupSendEndorsementRecords(allEndorsements)
}
fun getGroupSendFullToken(threadId: Long, recipientId: RecipientId): GroupSendFullToken? {
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(threadId)
if (threadRecipient == null || !threadRecipient.isGroup) {
return null
}
return getGroupSendFullToken(threadRecipient.requireGroupId().requireV2(), recipientId)
}
fun getGroupSendFullToken(groupId: GroupId.V2, recipientId: RecipientId): GroupSendFullToken? {
val groupRecord = SignalDatabase.groups.getGroup(groupId).orElse(null) ?: return null
val endorsement = SignalDatabase.groups.getGroupSendEndorsement(groupId, recipientId) ?: return null
val groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupRecord.requireV2GroupProperties().groupMasterKey)
return endorsement.toFullToken(groupSecretParams, Instant.ofEpochMilli(groupRecord.groupSendEndorsementExpiration))
}
private fun getGroupSendEndorsement(groupId: GroupId, recipientId: RecipientId): GroupSendEndorsement? {
return readableDatabase
.select(MembershipTable.ENDORSEMENT)
.from(MembershipTable.TABLE_NAME)
.where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, recipientId)
.run()
.readToSingleObject { c ->
c.requireBlob(MembershipTable.ENDORSEMENT)?.let { endorsementBlob ->
try {
GroupSendEndorsement(endorsementBlob)
} catch (e: InvalidInputException) {
Log.w(TAG, "Unable to parse group send endorsement for $recipientId", e)
null
}
}
}
}
@WorkerThread
fun isCurrentMember(groupId: Push, recipientId: RecipientId): Boolean {
return readableDatabase
@@ -1008,7 +1123,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
groupRevision = cursor.requireInt(V2_REVISION),
decryptedGroupBytes = cursor.requireBlob(V2_DECRYPTED_GROUP),
distributionId = cursor.optionalString(DISTRIBUTION_ID).map { id -> DistributionId.from(id) }.orElse(null),
lastForceUpdateTimestamp = cursor.requireLong(LAST_FORCE_UPDATE_TIMESTAMP)
lastForceUpdateTimestamp = cursor.requireLong(LAST_FORCE_UPDATE_TIMESTAMP),
groupSendEndorsementExpiration = cursor.requireLong(GROUP_SEND_ENDORSEMENTS_EXPIRATION)
)
}
}
@@ -1220,15 +1336,20 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
return RecipientId.toSerializedList(this)
}
private fun Collection<RecipientId>.toContentValues(groupId: GroupId): List<ContentValues> {
private fun Collection<RecipientId>.toContentValues(groupId: GroupId, groupSendEndorsementRecords: GroupSendEndorsementRecords?): List<ContentValues> {
return map {
contentValuesOf(
MembershipTable.GROUP_ID to groupId.serialize(),
MembershipTable.RECIPIENT_ID to it.serialize()
MembershipTable.RECIPIENT_ID to it.serialize(),
MembershipTable.ENDORSEMENT to groupSendEndorsementRecords?.endorsements?.get(it)?.serialize()
)
}
}
private fun ReceivedGroupSendEndorsements.toGroupSendEndorsementRecords(): GroupSendEndorsementRecords {
return GroupSendEndorsementRecords(endorsements.map { (aci, endorsement) -> RecipientId.from(aci) to endorsement }.toMap())
}
private fun serviceIdsToRecipientIds(serviceIds: Sequence<ServiceId>): MutableList<RecipientId> {
return serviceIds
.map { serviceId ->

View File

@@ -958,10 +958,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
Log.i(TAG, "Creating restore placeholder for $groupId")
val createdId = groups.create(
masterKey,
DecryptedGroup.Builder()
.revision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
.build()
groupMasterKey = masterKey,
groupState = DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION),
groupSendEndorsements = null
)
if (createdId == null) {
@@ -1469,9 +1468,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
}
fun setUnidentifiedAccessMode(id: RecipientId, unidentifiedAccessMode: UnidentifiedAccessMode) {
fun setSealedSenderAccessMode(id: RecipientId, sealedSenderAccessMode: SealedSenderAccessMode) {
val values = ContentValues(1).apply {
put(SEALED_SENDER_MODE, unidentifiedAccessMode.mode)
put(SEALED_SENDER_MODE, sealedSenderAccessMode.mode)
}
if (update(id, values)) {
AppDependencies.databaseObserver.notifyRecipientChanged(id)
@@ -1554,7 +1553,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val valuesToSet = ContentValues(3).apply {
put(PROFILE_KEY, encodedProfileKey)
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
put(SEALED_SENDER_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
put(SEALED_SENDER_MODE, SealedSenderAccessMode.UNKNOWN.mode)
}
val updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare)
@@ -1586,7 +1585,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val valuesToSet = ContentValues(3).apply {
put(PROFILE_KEY, Base64.encodeWithPadding(profileKey.serialize()))
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
put(SEALED_SENDER_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
put(SEALED_SENDER_MODE, SealedSenderAccessMode.UNKNOWN.mode)
}
if (writableDatabase.update(TABLE_NAME, valuesToSet, selection, args) > 0) {
@@ -4610,14 +4609,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
}
enum class UnidentifiedAccessMode(val mode: Int) {
enum class SealedSenderAccessMode(val mode: Int) {
UNKNOWN(0),
DISABLED(1),
ENABLED(2),
UNRESTRICTED(3);
companion object {
fun fromMode(mode: Int): UnidentifiedAccessMode {
fun fromMode(mode: Int): SealedSenderAccessMode {
return values()[mode]
}
}

View File

@@ -149,7 +149,7 @@ object RecipientTableCursorUtil {
profileSharing = cursor.requireBoolean(RecipientTable.PROFILE_SHARING),
lastProfileFetch = cursor.requireLong(RecipientTable.LAST_PROFILE_FETCH),
notificationChannel = cursor.requireString(RecipientTable.NOTIFICATION_CHANNEL),
unidentifiedAccessMode = RecipientTable.UnidentifiedAccessMode.fromMode(cursor.requireInt(RecipientTable.SEALED_SENDER_MODE)),
sealedSenderAccessMode = RecipientTable.SealedSenderAccessMode.fromMode(cursor.requireInt(RecipientTable.SEALED_SENDER_MODE)),
capabilities = readCapabilities(cursor),
storageId = Base64.decodeNullableOrThrow(cursor.requireString(RecipientTable.STORAGE_SERVICE_ID)),
mentionSetting = RecipientTable.MentionSetting.fromId(cursor.requireInt(RecipientTable.MENTION_SETTING)),

View File

@@ -95,6 +95,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V234_ThumbnailResto
import org.thoughtcrime.securesms.database.helpers.migration.V235_AttachmentUuidColumn
import org.thoughtcrime.securesms.database.helpers.migration.V236_FixInAppSubscriberCurrencyIfAble
import org.thoughtcrime.securesms.database.helpers.migration.V237_ResetGroupForceUpdateTimestamps
import org.thoughtcrime.securesms.database.helpers.migration.V238_AddGroupSendEndorsementsColumns
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -192,10 +193,11 @@ object SignalDatabaseMigrations {
234 to V234_ThumbnailRestoreStateColumn,
235 to V235_AttachmentUuidColumn,
236 to V236_FixInAppSubscriberCurrencyIfAble,
237 to V237_ResetGroupForceUpdateTimestamps
237 to V237_ResetGroupForceUpdateTimestamps,
238 to V238_AddGroupSendEndorsementsColumns
)
const val DATABASE_VERSION = 237
const val DATABASE_VERSION = 238
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
/**
* Add columns to group and group membership tables needed for group send endorsements.
*/
@Suppress("ClassName")
object V238_AddGroupSendEndorsementsColumns : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE groups ADD COLUMN group_send_endorsements_expiration INTEGER DEFAULT 0")
db.execSQL("ALTER TABLE group_membership ADD COLUMN endorsement BLOB DEFAULT NULL")
}
}

View File

@@ -31,7 +31,8 @@ class GroupRecord(
groupRevision: Int,
decryptedGroupBytes: ByteArray?,
val distributionId: DistributionId?,
val lastForceUpdateTimestamp: Long
val lastForceUpdateTimestamp: Long,
val groupSendEndorsementExpiration: Long
) {
val members: List<RecipientId> by lazy {

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.model
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement
import org.thoughtcrime.securesms.recipients.RecipientId
/**
* Contains the individual group send endorsements for a specific group
* source from our local db.
*/
data class GroupSendEndorsementRecords(val endorsements: Map<RecipientId, GroupSendEndorsement?>) {
fun getEndorsement(recipientId: RecipientId): GroupSendEndorsement? {
return endorsements[recipientId]
}
fun isMissingAnyEndorsements(): Boolean {
return endorsements.values.any { it == null }
}
}

View File

@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.profiles.ProfileName
@@ -60,7 +60,7 @@ data class RecipientRecord(
val profileSharing: Boolean,
val lastProfileFetch: Long,
val notificationChannel: String?,
val unidentifiedAccessMode: UnidentifiedAccessMode,
val sealedSenderAccessMode: SealedSenderAccessMode,
val capabilities: Capabilities,
val storageId: ByteArray?,
val mentionSetting: MentionSetting,

View File

@@ -172,6 +172,16 @@ public final class GroupManager {
}
}
@WorkerThread
public static void updateGroupSendEndorsements(@NonNull Context context,
@NonNull GroupMasterKey groupMasterKey)
throws GroupChangeBusyException, IOException, GroupNotAMemberException
{
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
updater.updateGroupSendEndorsements();
}
}
@WorkerThread
public static void setMemberAdmin(@NonNull Context context,
@NonNull GroupId.V2 groupId,

View File

@@ -18,6 +18,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupChangeResponse;
import org.signal.storageservice.protos.groups.GroupExternalCredential;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
@@ -49,6 +50,8 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse;
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct;
@@ -219,16 +222,18 @@ final class GroupManagerV2 {
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception
{
GroupSecretParams groupSecretParams = GroupSecretParams.generate();
DecryptedGroup decryptedGroup;
DecryptedGroupResponse createGroupResponse;
try {
decryptedGroup = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer);
createGroupResponse = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer);
} catch (GroupAlreadyExistsException e) {
throw new GroupChangeFailedException(e);
}
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup);
DecryptedGroup decryptedGroup = createGroupResponse.getGroup();
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
ReceivedGroupSendEndorsements groupSendEndorsements = groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroup, createGroupResponse.getGroupSendEndorsementsResponse());
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup, groupSendEndorsements);
if (groupId == null) {
throw new GroupChangeFailedException("Unable to create group, group already exists");
@@ -670,7 +675,8 @@ final class GroupManagerV2 {
previousGroupState = v2GroupProperties.getDecryptedGroup();
GroupChange signedGroupChange = commitToServer(changeActions);
GroupChangeResponse changeResponse = commitToServer(changeActions);
GroupChange signedGroupChange = changeResponse.groupChange;
try {
//noinspection OptionalGetWithoutIsPresent
decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
@@ -680,7 +686,7 @@ final class GroupManagerV2 {
throw new IOException(e);
}
groupDatabase.update(groupId, decryptedGroupState);
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.groupSendEndorsementsResponse));
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);
@@ -690,7 +696,7 @@ final class GroupManagerV2 {
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers);
}
private @NonNull GroupChange commitToServer(@NonNull GroupChange.Actions change)
private @NonNull GroupChangeResponse commitToServer(@NonNull GroupChange.Actions change)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{
try {
@@ -747,6 +753,14 @@ final class GroupManagerV2 {
.forceSanityUpdateFromServer(timestamp);
}
@WorkerThread
void updateGroupSendEndorsements()
throws IOException, GroupNotAMemberException
{
GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey)
.updateGroupSendEndorsements();
}
private DecryptedGroupChange getDecryptedGroupChange(@Nullable byte[] signedGroupChange) {
if (signedGroupChange != null && signedGroupChange.length > 0) {
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
@@ -764,11 +778,11 @@ final class GroupManagerV2 {
}
@WorkerThread
private @NonNull DecryptedGroup createGroupOnServer(@NonNull GroupSecretParams groupSecretParams,
@Nullable String name,
@Nullable byte[] avatar,
@NonNull Collection<RecipientId> members,
int disappearingMessageTimerSeconds)
private @NonNull DecryptedGroupResponse createGroupOnServer(@NonNull GroupSecretParams groupSecretParams,
@Nullable String name,
@Nullable byte[] avatar,
@NonNull Collection<RecipientId> members,
int disappearingMessageTimerSeconds)
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException
{
if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(members)) {
@@ -797,15 +811,10 @@ final class GroupManagerV2 {
disappearingMessageTimerSeconds);
try {
groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
DecryptedGroupResponse groupResponse = groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
DecryptedGroup decryptedGroup = groupsV2Api.getGroup(groupSecretParams, AppDependencies.getGroupsV2Authorization().getAuthorizationForToday(serviceIds, groupSecretParams));
if (decryptedGroup == null) {
throw new GroupChangeFailedException();
}
return decryptedGroup;
} catch (VerificationFailedException | InvalidGroupStateException e) {
return groupResponse;
} catch (VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
throw new GroupChangeFailedException(e);
} catch (GroupExistsException e) {
throw new GroupAlreadyExistsException(e);
@@ -837,8 +846,9 @@ final class GroupManagerV2 {
@Nullable byte[] avatar)
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException
{
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
boolean alreadyAMember = false;
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
boolean alreadyAMember = false;
boolean groupAlreadyExists = false;
if (requestToJoin) {
Log.i(TAG, "Requesting to join " + groupId);
@@ -846,10 +856,13 @@ final class GroupManagerV2 {
Log.i(TAG, "Joining " + groupId);
}
GroupChange signedGroupChange = null;
DecryptedGroupChange decryptedChange = null;
GroupChangeResponse groupChangeResponse = null;
GroupChange signedGroupChange = null;
DecryptedGroupChange decryptedChange = null;
try {
signedGroupChange = joinGroupOnServer(requestToJoin, joinInfo.revision);
groupChangeResponse = joinGroupOnServer(requestToJoin, joinInfo.revision);
signedGroupChange = groupChangeResponse.groupChange;
if (requestToJoin) {
Log.i(TAG, String.format("Successfully requested to join %s on server", groupId));
@@ -857,7 +870,7 @@ final class GroupManagerV2 {
Log.i(TAG, String.format("Successfully added self to %s on server", groupId));
}
decryptedChange = decryptChange(signedGroupChange);
decryptedChange = decryptChange(Objects.requireNonNull(signedGroupChange));
} catch (GroupJoinAlreadyAMemberException e) {
Log.i(TAG, "Server reports that we are already a member of " + groupId);
alreadyAMember = true;
@@ -869,28 +882,37 @@ final class GroupManagerV2 {
if (group.isPresent()) {
Log.i(TAG, "Group already present locally");
if (decryptedChange != null) {
try {
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
} catch (GroupNotAMemberException e) {
Log.w(TAG, "Unable to apply join change to existing group", e);
}
}
groupAlreadyExists = true;
} else {
GroupId.V2 groupId = groupDatabase.create(groupMasterKey, decryptedGroup);
GroupId.V2 groupId = groupDatabase.create(groupMasterKey, decryptedGroup, null);
if (groupId != null) {
Log.i(TAG, "Created local group with placeholder");
} else {
Log.i(TAG, "Create placeholder failed, group suddenly present locally, attempting to apply change");
if (decryptedChange != null) {
try {
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
} catch (GroupNotAMemberException e) {
Log.w(TAG, "Unable to apply join change to existing group", e);
}
}
Log.i(TAG, "Create placeholder failed, group suddenly present locally");
groupAlreadyExists = true;
}
}
if (groupAlreadyExists) {
Log.i(TAG, "Attempting to update local group with change/server");
try {
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
.updateLocalGroupToRevision(decryptedChange != null ? decryptedChange.revision : GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), decryptedChange);
} catch (GroupNotAMemberException e) {
Log.w(TAG, "Despite adding self to group, change/server says we are not a member, scheduling refresh of group info " + groupId, e);
AppDependencies.getJobManager()
.add(new RequestGroupV2InfoJob(groupId));
throw new GroupChangeFailedException(e);
} catch (IOException e) {
Log.w(TAG, "Group data fetch failed, scheduling refresh of group info " + groupId, e);
AppDependencies.getJobManager()
.add(new RequestGroupV2InfoJob(groupId));
throw e;
}
}
@@ -1006,7 +1028,7 @@ final class GroupManagerV2 {
return group.build();
}
private @NonNull GroupChange joinGroupOnServer(boolean requestToJoin, int currentRevision)
private @NonNull GroupChangeResponse joinGroupOnServer(boolean requestToJoin, int currentRevision)
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException, GroupJoinAlreadyAMemberException
{
if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(Collections.singleton(Recipient.self().getId()))) {
@@ -1029,7 +1051,7 @@ final class GroupManagerV2 {
return commitJoinChangeWithConflictResolution(currentRevision, change);
}
private @NonNull GroupChange commitJoinChangeWithConflictResolution(int currentRevision, @NonNull GroupChange.Actions.Builder change)
private @NonNull GroupChangeResponse commitJoinChangeWithConflictResolution(int currentRevision, @NonNull GroupChange.Actions.Builder change)
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException, GroupJoinAlreadyAMemberException
{
for (int attempt = 0; attempt < 5; attempt++) {
@@ -1038,10 +1060,10 @@ final class GroupManagerV2 {
.build();
Log.i(TAG, "Trying to join group at V" + changeActions.revision);
GroupChange signedGroupChange = commitJoinToServer(changeActions);
GroupChangeResponse changeResponse = commitJoinToServer(changeActions);
Log.i(TAG, "Successfully joined group at V" + changeActions.revision);
return signedGroupChange;
return changeResponse;
} catch (GroupPatchNotAcceptedException e) {
Log.w(TAG, "Patch not accepted", e);
@@ -1051,7 +1073,7 @@ final class GroupManagerV2 {
} else {
throw new GroupChangeFailedException(e);
}
} catch (VerificationFailedException | InvalidGroupStateException ex) {
} catch (VerificationFailedException | InvalidGroupStateException | InvalidInputException ex) {
throw new GroupChangeFailedException(ex);
}
} catch (ConflictException e) {
@@ -1064,7 +1086,7 @@ final class GroupManagerV2 {
throw new GroupChangeFailedException("Unable to join group after conflicts");
}
private @NonNull GroupChange commitJoinToServer(@NonNull GroupChange.Actions change)
private @NonNull GroupChangeResponse commitJoinToServer(@NonNull GroupChange.Actions change)
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException
{
try {
@@ -1109,7 +1131,7 @@ final class GroupManagerV2 {
}
private boolean testGroupMembership()
throws IOException, VerificationFailedException, InvalidGroupStateException
throws IOException, VerificationFailedException, InvalidGroupStateException, InvalidInputException
{
try {
groupsV2Api.getGroup(groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
@@ -1140,7 +1162,7 @@ final class GroupManagerV2 {
DecryptedGroupChange decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
DecryptedGroup newGroup = DecryptedGroupUtil.applyWithoutRevisionCheck(decryptedGroup, decryptedChange);
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision));
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision), null);
sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange, false);
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
@@ -1165,7 +1187,7 @@ final class GroupManagerV2 {
.build();
Log.i(TAG, "Trying to cancel request group at V" + changeActions.revision);
GroupChange signedGroupChange = commitJoinToServer(changeActions);
GroupChange signedGroupChange = commitJoinToServer(changeActions).groupChange;
Log.i(TAG, "Successfully cancelled group join at V" + changeActions.revision);
return signedGroupChange;

View File

@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.groups.v2.processing
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse
import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog
@@ -9,10 +10,15 @@ import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog
*/
class GroupStateDiff(
val previousGroupState: DecryptedGroup?,
val serverHistory: List<DecryptedGroupChangeLog>
val serverHistory: List<DecryptedGroupChangeLog>,
val groupSendEndorsementsResponse: GroupSendEndorsementsResponse?
) {
constructor(previousGroupState: DecryptedGroup?, changedGroupState: DecryptedGroup?, change: DecryptedGroupChange?) : this(previousGroupState, listOf(DecryptedGroupChangeLog(changedGroupState, change)))
constructor(
previousGroupState: DecryptedGroup?,
changedGroupState: DecryptedGroup?,
change: DecryptedGroupChange?
) : this(previousGroupState, listOf(DecryptedGroupChangeLog(changedGroupState, change)), null)
val earliestRevisionNumber: Int
get() {

View File

@@ -9,6 +9,7 @@ import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import org.signal.core.util.logging.Log
import org.signal.core.util.orNull
import org.signal.libsignal.zkgroup.InvalidInputException
import org.signal.libsignal.zkgroup.VerificationFailedException
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
@@ -43,6 +44,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceIds
@@ -95,8 +97,9 @@ class GroupsV2StateProcessor private constructor(
}
}
private val groupsApi = AppDependencies.signalServiceAccountManager.groupsV2Api
private val groupsApi = AppDependencies.signalServiceAccountManager.getGroupsV2Api()
private val groupsV2Authorization = AppDependencies.groupsV2Authorization
private val groupOperations = AppDependencies.groupsV2Operations.forGroup(groupSecretParams)
private val groupId = GroupId.v2(groupSecretParams.getPublicParams().getGroupIdentifier())
private val profileAndMessageHelper = ProfileAndMessageHelper.create(serviceIds.aci, groupMasterKey, groupId)
@@ -124,6 +127,36 @@ class GroupsV2StateProcessor private constructor(
}
}
/**
* Fetch and save the latest group send endorsements from the server. This endorsements returned may
* not match our local view of the membership if the membership has changed on the server and we haven't updated the
* group state yet. This is only an issue when trying to send to a group member that has been removed and should be handled
* gracefully as a fallback in the sending flow.
*/
@WorkerThread
@Throws(IOException::class, GroupNotAMemberException::class)
fun updateGroupSendEndorsements() {
val result = groupsApi.getGroupAsResult(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
val groupResponse = when (result) {
is NetworkResult.Success -> result.result
else -> when (val cause = result.getCause()!!) {
is NotInGroupException, is GroupNotFoundException -> throw GroupNotAMemberException(cause)
is IOException -> throw cause
else -> throw IOException(cause)
}
}
val receivedGroupSendEndorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, groupResponse.group, groupResponse.groupSendEndorsementsResponse)
if (receivedGroupSendEndorsements != null) {
Log.i(TAG, "$logPrefix Updating group send endorsements")
SignalDatabase.groups.updateGroupSendEndorsements(groupId, receivedGroupSendEndorsements)
} else {
Log.w(TAG, "$logPrefix No group send endorsements on response")
}
}
/**
* Using network where required, will attempt to bring the local copy of the group up to the revision specified.
*
@@ -233,7 +266,8 @@ class GroupsV2StateProcessor private constructor(
return saveGroupUpdate(
timestamp = timestamp,
serverGuid = serverGuid,
groupStateDiff = groupStateDiff
groupStateDiff = groupStateDiff,
groupSendEndorsements = null
)
}
@@ -259,6 +293,8 @@ class GroupsV2StateProcessor private constructor(
else -> return InternalUpdateResult.from(result.getCause()!!)
}
val sendEndorsementExpiration = groupRecord.map { it.groupSendEndorsementExpiration }.orElse(0L)
var includeFirstState = currentLocalState == null ||
currentLocalState.revision < 0 ||
currentLocalState.revision == joinedAtRevision ||
@@ -273,20 +309,30 @@ class GroupsV2StateProcessor private constructor(
var hasRemainingRemoteChanges = false
while (hasMore) {
Log.i(TAG, "$logPrefix Requesting change logs from server, currentRevision=${currentLocalState?.revision ?: "null"} logsNeededFrom=$logsNeededFrom includeFirstState=$includeFirstState")
Log.i(TAG, "$logPrefix Requesting change logs from server, currentRevision=${currentLocalState?.revision ?: "null"} logsNeededFrom=$logsNeededFrom includeFirstState=$includeFirstState sendEndorsementExpiration=${sendEndorsementExpiration > 0}")
val (remoteGroupStateDiff, pagingData) = getGroupChangeLogs(currentLocalState, logsNeededFrom, includeFirstState)
val (remoteGroupStateDiff, pagingData) = getGroupChangeLogs(currentLocalState, logsNeededFrom, includeFirstState, sendEndorsementExpiration)
val applyGroupStateDiffResult: AdvanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(remoteGroupStateDiff, targetRevision)
val updatedGroupState: DecryptedGroup? = applyGroupStateDiffResult.updatedGroupState
if (updatedGroupState == null || updatedGroupState == remoteGroupStateDiff.previousGroupState) {
Log.i(TAG, "$logPrefix Local state is at or later than server revision: ${currentLocalState?.revision ?: "null"}")
if (currentLocalState != null) {
val endorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, currentLocalState, remoteGroupStateDiff.groupSendEndorsementsResponse)
if (endorsements != null) {
Log.i(TAG, "$logPrefix Received updated send endorsements, saving")
SignalDatabase.groups.updateGroupSendEndorsements(groupId, endorsements)
}
}
return InternalUpdateResult.NoUpdateNeeded
}
Log.i(TAG, "$logPrefix Saving updated group state at revision: ${updatedGroupState.revision}")
saveGroupState(remoteGroupStateDiff, updatedGroupState)
saveGroupState(remoteGroupStateDiff, updatedGroupState, groupOperations.receiveGroupSendEndorsements(serviceIds.aci, updatedGroupState, remoteGroupStateDiff.groupSendEndorsementsResponse))
if (addMessagesForAllUpdates) {
Log.d(TAG, "$logPrefix Inserting group changes into chat history")
@@ -320,7 +366,7 @@ class GroupsV2StateProcessor private constructor(
}
if (!addMessagesForAllUpdates) {
Log.i(TAG, "Inserting single update message for restore placeholder")
Log.i(TAG, "$logPrefix Inserting single update message for restore placeholder")
profileAndMessageHelper.insertUpdateMessages(runningTimestamp, null, setOf(AppliedGroupChangeLog(currentLocalState!!, null)), serverGuid)
}
@@ -341,19 +387,20 @@ class GroupsV2StateProcessor private constructor(
private fun updateToLatestViaServer(timestamp: Long, currentLocalState: DecryptedGroup?, reconstructChange: Boolean): InternalUpdateResult {
val result = groupsApi.getGroupAsResult(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
val serverState = if (result is NetworkResult.Success) {
val groupResponse = if (result is NetworkResult.Success) {
result.result
} else {
return InternalUpdateResult.from(result.getCause()!!)
}
val completeGroupChange = if (reconstructChange) GroupChangeReconstruct.reconstructGroupChange(currentLocalState, serverState) else null
val remoteGroupStateDiff = GroupStateDiff(currentLocalState, serverState, completeGroupChange)
val completeGroupChange = if (reconstructChange) GroupChangeReconstruct.reconstructGroupChange(currentLocalState, groupResponse.group) else null
val remoteGroupStateDiff = GroupStateDiff(currentLocalState, groupResponse.group, completeGroupChange)
return saveGroupUpdate(
timestamp = timestamp,
serverGuid = null,
groupStateDiff = remoteGroupStateDiff
groupStateDiff = remoteGroupStateDiff,
groupSendEndorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, groupResponse.group, groupResponse.groupSendEndorsementsResponse)
)
}
@@ -403,22 +450,30 @@ class GroupsV2StateProcessor private constructor(
}
@Throws(IOException::class)
private fun getGroupChangeLogs(localState: DecryptedGroup?, logsNeededFromRevision: Int, includeFirstState: Boolean): Pair<GroupStateDiff, GroupHistoryPage.PagingData> {
private fun getGroupChangeLogs(
localState: DecryptedGroup?,
logsNeededFromRevision: Int,
includeFirstState: Boolean,
sendEndorsementsExpirationMs: Long
): Pair<GroupStateDiff, GroupHistoryPage.PagingData> {
try {
val groupHistoryPage = groupsApi.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState)
val groupHistoryPage = groupsApi.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState, sendEndorsementsExpirationMs)
return GroupStateDiff(localState, groupHistoryPage.changeLogs) to groupHistoryPage.pagingData
return GroupStateDiff(localState, groupHistoryPage.changeLogs, groupHistoryPage.groupSendEndorsementsResponse) to groupHistoryPage.pagingData
} catch (e: InvalidGroupStateException) {
throw IOException(e)
} catch (e: VerificationFailedException) {
throw IOException(e)
} catch (e: InvalidInputException) {
throw IOException(e)
}
}
private fun saveGroupUpdate(
timestamp: Long,
serverGuid: String?,
groupStateDiff: GroupStateDiff
groupStateDiff: GroupStateDiff,
groupSendEndorsements: ReceivedGroupSendEndorsements?
): InternalUpdateResult {
val currentLocalState: DecryptedGroup? = groupStateDiff.previousGroupState
val applyGroupStateDiffResult = GroupStatePatcher.applyGroupStateDiff(groupStateDiff, GroupStatePatcher.LATEST)
@@ -426,12 +481,18 @@ class GroupsV2StateProcessor private constructor(
if (updatedGroupState == null || updatedGroupState == groupStateDiff.previousGroupState) {
Log.i(TAG, "$logPrefix Local state and server state are equal")
if (groupSendEndorsements != null) {
Log.i(TAG, "$logPrefix Saving new send endorsements")
SignalDatabase.groups.updateGroupSendEndorsements(groupId, groupSendEndorsements)
}
return InternalUpdateResult.NoUpdateNeeded
} else {
Log.i(TAG, "$logPrefix Local state (revision: ${currentLocalState?.revision}) does not match, updating to ${updatedGroupState.revision}")
}
saveGroupState(groupStateDiff, updatedGroupState)
saveGroupState(groupStateDiff, updatedGroupState, groupSendEndorsements)
if (currentLocalState == null || currentLocalState.revision == RESTORE_PLACEHOLDER_REVISION) {
Log.i(TAG, "$logPrefix Inserting single update message for no local state or restore placeholder")
@@ -453,20 +514,24 @@ class GroupsV2StateProcessor private constructor(
return InternalUpdateResult.Updated(updatedGroupState)
}
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup) {
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?) {
val previousGroupState = groupStateDiff.previousGroupState
if (groupSendEndorsements != null) {
Log.i(TAG, "$logPrefix Updating send endorsements")
}
val needsAvatarFetch = if (previousGroupState == null) {
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState)
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState, groupSendEndorsements)
if (groupId == null) {
Log.w(TAG, "$logPrefix Group create failed, trying to update")
SignalDatabase.groups.update(groupMasterKey, updatedGroupState)
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
}
updatedGroupState.avatar.isNotEmpty()
} else {
SignalDatabase.groups.update(groupMasterKey, updatedGroupState)
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
updatedGroupState.avatar != previousGroupState.avatar
}

View File

@@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime;
@@ -18,14 +18,12 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
@@ -136,12 +134,11 @@ public class AutomaticSessionResetJob extends BaseJob {
return;
}
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
try {
messageSender.sendNullMessage(address, unidentifiedAccess);
messageSender.sendNullMessage(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient));
} catch (UntrustedIdentityException e) {
Log.w(TAG, "Unable to send null message.");
}

View File

@@ -18,7 +18,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate
import java.util.Optional
import java.util.concurrent.TimeUnit
/**
@@ -83,7 +82,7 @@ class CallLinkUpdateSendJob private constructor(
)
AppDependencies.signalServiceMessageSender
.sendSyncMessage(SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate), Optional.empty())
.sendSyncMessage(SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate))
if (callLinkUpdateType == CallLinkUpdate.Type.DELETE) {
SignalDatabase.callLinks.deleteCallLink(callLinkRoomId)

View File

@@ -17,7 +17,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
import org.whispersystems.signalservice.internal.push.SyncMessage
import java.util.Optional
import java.util.concurrent.TimeUnit
/**
@@ -98,10 +97,7 @@ class CallLogEventSendJob private constructor(
override fun onRun() {
AppDependencies.signalServiceMessageSender
.sendSyncMessage(
SignalServiceSyncMessage.forCallLogEvent(callLogEvent),
Optional.empty()
)
.sendSyncMessage(SignalServiceSyncMessage.forCallLogEvent(callLogEvent))
}
override fun onShouldRetry(e: Exception): Boolean {

View File

@@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer
import org.thoughtcrime.securesms.service.webrtc.CallEventSyncMessageUtil
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
import org.whispersystems.signalservice.internal.push.SyncMessage
import java.util.Optional
import java.util.concurrent.TimeUnit
/**
@@ -142,7 +141,7 @@ class CallSyncEventJob private constructor(
val syncMessage = createSyncMessage(syncTimestamp, callSyncEvent, call.type)
return try {
AppDependencies.signalServiceMessageSender.sendSyncMessage(SignalServiceSyncMessage.forCallEvent(syncMessage), Optional.empty())
AppDependencies.signalServiceMessageSender.sendSyncMessage(SignalServiceSyncMessage.forCallEvent(syncMessage))
null
} catch (e: Exception) {
Log.w(TAG, "Unable to send call event sync message for ${callSyncEvent.callId}", e)

View File

@@ -10,11 +10,11 @@ import com.annimon.stream.Stream;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.PaymentTable;
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@@ -37,7 +37,6 @@ import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
@@ -160,7 +159,7 @@ public class IndividualSendJob extends PushSendJob {
Recipient recipient = message.getThreadRecipient().fresh();
byte[] profileKey = recipient.getProfileKey();
UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
SealedSenderAccessMode accessMode = recipient.getSealedSenderAccessMode();
boolean unidentified = deliver(message, originalEditedMessage);
@@ -177,15 +176,15 @@ public class IndividualSendJob extends PushSendJob {
SignalDatabase.messages().incrementViewedReceiptCount(message.getSentTimeMillis(), recipient.getId(), System.currentTimeMillis());
}
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
if (unidentified && accessMode == SealedSenderAccessMode.UNKNOWN && profileKey == null) {
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-unrestricted following a UD send.");
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.UNRESTRICTED);
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.UNRESTRICTED);
} else if (unidentified && accessMode == SealedSenderAccessMode.UNKNOWN) {
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-enabled following a UD send.");
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.ENABLED);
} else if (!unidentified && accessMode != SealedSenderAccessMode.DISABLED) {
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-disabled following a non-UD send.");
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.DISABLED);
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.DISABLED);
}
if (originalEditedMessage != null && originalEditedMessage.getExpireStarted() > 0) {
@@ -306,25 +305,35 @@ public class IndividualSendJob extends PushSendJob {
if (originalEditedMessage != null) {
if (Util.equals(SignalStore.account().getAci(), address.getServiceId())) {
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
SendMessageResult result = messageSender.sendSelfSyncEditMessage(new SignalServiceEditMessage(originalEditedMessage.getDateSent(), mediaMessage));
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
return syncAccess.isPresent();
return SealedSenderAccessUtil.getSealedSenderCertificate() != null;
} else {
SendMessageResult result = messageSender.sendEditMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, IndividualSendEvents.EMPTY, message.isUrgent(), originalEditedMessage.getDateSent());
SendMessageResult result = messageSender.sendEditMessage(address,
SealedSenderAccessUtil.getSealedSenderAccessFor(messageRecipient),
ContentHint.RESENDABLE,
mediaMessage,
IndividualSendEvents.EMPTY,
message.isUrgent(),
originalEditedMessage.getDateSent());
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
return result.getSuccess().isUnidentified();
}
} else if (Util.equals(SignalStore.account().getAci(), address.getServiceId())) {
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
SendMessageResult result = messageSender.sendSyncMessage(mediaMessage);
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
return syncAccess.isPresent();
return SealedSenderAccessUtil.getSealedSenderCertificate() != null;
} else {
SignalLocalMetrics.IndividualMessageSend.onDeliveryStarted(messageId, message.getSentTimeMillis());
SendMessageResult result = messageSender.sendDataMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, new MetricEventListener(messageId), message.isUrgent(), messageRecipient.getNeedsPniSignature());
SendMessageResult result = messageSender.sendDataMessage(address,
SealedSenderAccessUtil.getSealedSenderAccessFor(messageRecipient),
ContentHint.RESENDABLE,
mediaMessage,
new MetricEventListener(messageId),
message.isUrgent(),
messageRecipient.getNeedsPniSignature());
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), message.isUrgent());

View File

@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.RecipientTable.RecipientReader;
import org.thoughtcrime.securesms.database.SignalDatabase;
@@ -88,8 +87,8 @@ public class MultiDeviceBlockedUpdateJob extends BaseJob {
}
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
messageSender.sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups)),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups))
);
}
}

View File

@@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate
import java.util.Optional
import kotlin.time.Duration.Companion.days
/**
@@ -57,7 +56,7 @@ class MultiDeviceCallLinkSyncJob private constructor(
val syncMessage = SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate)
try {
AppDependencies.signalServiceMessageSender.sendSyncMessage(syncMessage, Optional.empty())
AppDependencies.signalServiceMessageSender.sendSyncMessage(syncMessage)
} catch (e: Exception) {
Log.w(TAG, "Unable to send call link update message.", e)
throw e

View File

@@ -5,10 +5,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
@@ -106,8 +105,8 @@ public class MultiDeviceConfigurationUpdateJob extends BaseJob {
messageSender.sendSyncMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(readReceiptsEnabled),
Optional.of(unidentifiedDeliveryIndicatorsEnabled),
Optional.of(typingIndicatorsEnabled),
Optional.of(linkPreviewsEnabled))),
UnidentifiedAccessUtil.getAccessForSync(context));
Optional.of(linkPreviewsEnabled)))
);
}
@Override

View File

@@ -15,13 +15,12 @@ import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
@@ -298,8 +297,8 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
.withLength(length)
.withResumableUploadSpec(messageSender.getResumableUploadSpec());
messageSender.sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream.build(), complete)),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream.build(), complete))
);
} catch (IOException ioe) {
throw new NetworkException(ioe);
}

View File

@@ -5,7 +5,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
@@ -73,8 +72,8 @@ public class MultiDeviceKeysUpdateJob extends BaseJob {
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
messageSender.sendSyncMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.ofNullable(storageServiceKey), Optional.of(SignalStore.svr().getOrCreateMasterKey()))),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.ofNullable(storageServiceKey), Optional.of(SignalStore.svr().getOrCreateMasterKey())))
);
}
@Override

View File

@@ -5,10 +5,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -122,8 +121,8 @@ public class MultiDeviceMessageRequestResponseJob extends BaseJob {
}
if (response != null) {
messageSender.sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response)
);
} else {
Log.w(TAG, recipient.getId() + " not registered!");
}

View File

@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.PaymentTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
@@ -117,8 +116,8 @@ public final class MultiDeviceOutgoingPaymentSyncJob extends BaseJob {
AppDependencies.getSignalServiceMessageSender()
.sendSyncMessage(SignalServiceSyncMessage.forOutgoingPayment(outgoingPaymentMessage),
UnidentifiedAccessUtil.getAccessForSync(context));
.sendSyncMessage(SignalServiceSyncMessage.forOutgoingPayment(outgoingPaymentMessage)
);
}
@Override

View File

@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
@@ -58,8 +57,8 @@ public class MultiDeviceProfileContentUpdateJob extends BaseJob {
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)
);
}
@Override

View File

@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
@@ -98,7 +97,7 @@ public class MultiDeviceProfileKeyUpdateJob extends BaseJob {
SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false));
messageSender.sendSyncMessage(syncMessage, UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(syncMessage);
}
@Override

View File

@@ -8,12 +8,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.core.util.ListUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -121,7 +120,7 @@ public class MultiDeviceReadUpdateJob extends BaseJob {
}
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
messageSender.sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages), UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages));
}
@Override

View File

@@ -3,13 +3,12 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.signal.core.util.Hex;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
@@ -93,8 +92,8 @@ public class MultiDeviceStickerPackOperationJob extends BaseJob {
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
StickerPackOperationMessage stickerPackOperation = new StickerPackOperationMessage(packIdBytes, packKeyBytes, remoteType);
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(Collections.singletonList(stickerPackOperation)),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(Collections.singletonList(stickerPackOperation))
);
}
@Override

View File

@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.Hex;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.StickerTable.StickerPackRecordReader;
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
@@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.signal.core.util.Hex;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
@@ -80,8 +79,8 @@ public class MultiDeviceStickerPackSyncJob extends BaseJob {
}
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(operations),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(operations)
);
}
@Override

View File

@@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
@@ -58,8 +57,7 @@ public class MultiDeviceStorageSyncRequestJob extends BaseJob {
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST));
}
@Override

View File

@@ -11,7 +11,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessageRe
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import java.lang.Exception
import java.util.Optional
import java.util.concurrent.TimeUnit
@@ -57,7 +56,7 @@ class MultiDeviceStorySendSyncJob private constructor(parameters: Parameters, pr
val updateManifest = SignalDatabase.storySends.getLocalManifest(sentTimestamp)
val recipientsSet: Set<SignalServiceStoryMessageRecipient> = updateManifest.toRecipientsSet()
val transcriptMessage: SignalServiceSyncMessage = SignalServiceSyncMessage.forSentTranscript(buildSentTranscript(recipientsSet))
val sendMessageResult = AppDependencies.signalServiceMessageSender.sendSyncMessage(transcriptMessage, Optional.empty())
val sendMessageResult = AppDependencies.signalServiceMessageSender.sendSyncMessage(transcriptMessage)
Log.i(TAG, "Sent transcript message with ${recipientsSet.size} recipients")

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.jobs
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
@@ -57,10 +56,7 @@ class MultiDeviceSubscriptionSyncRequestJob private constructor(parameters: Para
val messageSender = AppDependencies.signalServiceMessageSender
messageSender.sendSyncMessage(
SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.SUBSCRIPTION_STATUS),
UnidentifiedAccessUtil.getAccessForSync(context)
)
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.SUBSCRIPTION_STATUS))
}
override fun onShouldRetry(e: Exception): Boolean {

View File

@@ -4,20 +4,19 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.Base64;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.signal.core.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -116,8 +115,8 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob {
SignalServiceAddress verifiedAddress = RecipientUtil.toSignalServiceAddress(context, recipient);
VerifiedMessage verifiedMessage = new VerifiedMessage(verifiedAddress, new IdentityKey(identityKey, 0), verifiedState, timestamp);
messageSender.sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage),
UnidentifiedAccessUtil.getAccessFor(context, recipient));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)
);
} catch (InvalidKeyException e) {
throw new IOException(e);
}

View File

@@ -6,11 +6,10 @@ import androidx.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -92,7 +91,7 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob {
ViewOnceOpenMessage openMessage = new ViewOnceOpenMessage(RecipientUtil.getOrFetchServiceId(context, recipient), messageId.timestamp);
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage), UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage));
}
@Override

View File

@@ -8,12 +8,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.core.util.ListUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -121,7 +120,7 @@ public class MultiDeviceViewedUpdateJob extends BaseJob {
}
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages), UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages));
}
@Override

View File

@@ -4,21 +4,19 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
@@ -72,12 +70,11 @@ public class NullMessageSendJob extends BaseJob {
Log.w(TAG, recipient.getId() + " not registered!");
}
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
try {
messageSender.sendNullMessage(address, unidentifiedAccess);
messageSender.sendNullMessage(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient));
} catch (UntrustedIdentityException e) {
Log.w(TAG, "Unable to send null message.");
}

View File

@@ -4,12 +4,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.PaymentTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -18,14 +18,12 @@ import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
public final class PaymentNotificationSendJob extends BaseJob {
@@ -81,9 +79,8 @@ public final class PaymentNotificationSendJob extends BaseJob {
return;
}
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
PaymentTable.PaymentTransaction payment = paymentDatabase.getPayment(uuid);
@@ -101,7 +98,13 @@ public final class PaymentNotificationSendJob extends BaseJob {
.withPayment(new SignalServiceDataMessage.Payment(new SignalServiceDataMessage.PaymentNotification(payment.getReceipt(), payment.getNote()), null))
.build();
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.DEFAULT, dataMessage, IndividualSendEvents.EMPTY, false, recipient.getNeedsPniSignature());
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address,
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
ContentHint.DEFAULT,
dataMessage,
IndividualSendEvents.EMPTY,
false,
recipient.getNeedsPniSignature());
if (recipient.getNeedsPniSignature()) {
SignalDatabase.pendingPniSignatureMessages().insertIfNecessary(recipientId, dataMessage.getTimestamp(), sendMessageResult);

View File

@@ -7,7 +7,7 @@ import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
import org.thoughtcrime.securesms.database.model.GroupRecord;
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
@@ -141,9 +141,9 @@ public class ResendMessageJob extends BaseJob {
return;
}
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<UnidentifiedAccessPair> access = UnidentifiedAccessUtil.getAccessFor(context, recipient);
Content contentToSend = content;
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Content contentToSend = content;
SealedSenderAccess.CreateGroupSendToken createGroupSendToken = null;
if (distributionId != null) {
if (groupId != null) {
@@ -157,6 +157,8 @@ public class ResendMessageJob extends BaseJob {
Log.w(TAG, "The target user is no longer in the group! Skipping message send.");
return;
}
createGroupSendToken = () -> SignalDatabase.groups().getGroupSendFullToken(groupId, recipientId);
} else {
Log.d(TAG, "GroupId is not present. Assuming this is a message for a distribution list.");
DistributionListRecord listRecord = SignalDatabase.distributionLists().getListByDistributionId(distributionId);
@@ -178,6 +180,8 @@ public class ResendMessageJob extends BaseJob {
SendMessageResult result;
SealedSenderAccess access = SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, createGroupSendToken);
try {
result = messageSender.resendContent(address, access, sentTimestamp, contentToSend, contentHint, Optional.ofNullable(groupId).map(GroupId::getDecodedId), urgent);
} catch (IllegalStateException e) {

View File

@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.RecipientTable.Companion.maskCapabilitiesToLong
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.RecipientRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies
@@ -222,7 +222,7 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
unrestrictedUnidentifiedAccess = remoteProfile.isUnrestrictedUnidentifiedAccess
)
if (localRecipientRecord.unidentifiedAccessMode != accessMode) {
if (localRecipientRecord.sealedSenderAccessMode != accessMode) {
return true
}
@@ -322,8 +322,8 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
val profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.profileKey)
val newMode = deriveUnidentifiedAccessMode(profileKey, unidentifiedAccessVerifier, unrestrictedUnidentifiedAccess)
if (recipient.unidentifiedAccessMode !== newMode) {
if (newMode === UnidentifiedAccessMode.UNRESTRICTED) {
if (recipient.sealedSenderAccessMode !== newMode) {
if (newMode === SealedSenderAccessMode.UNRESTRICTED) {
Log.i(TAG, "Marking recipient UD status as unrestricted.")
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
Log.i(TAG, "Marking recipient UD status as disabled.")
@@ -331,15 +331,15 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
Log.i(TAG, "Marking recipient UD status as " + newMode.name + " after verification.")
}
SignalDatabase.recipients.setUnidentifiedAccessMode(recipient.id, newMode)
SignalDatabase.recipients.setSealedSenderAccessMode(recipient.id, newMode)
}
}
private fun deriveUnidentifiedAccessMode(profileKey: ProfileKey?, unidentifiedAccessVerifier: String?, unrestrictedUnidentifiedAccess: Boolean): UnidentifiedAccessMode {
private fun deriveUnidentifiedAccessMode(profileKey: ProfileKey?, unidentifiedAccessVerifier: String?, unrestrictedUnidentifiedAccess: Boolean): SealedSenderAccessMode {
return if (unrestrictedUnidentifiedAccess && unidentifiedAccessVerifier != null) {
UnidentifiedAccessMode.UNRESTRICTED
SealedSenderAccessMode.UNRESTRICTED
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
UnidentifiedAccessMode.DISABLED
SealedSenderAccessMode.DISABLED
} else {
val profileCipher = ProfileCipher(profileKey)
val verifiedUnidentifiedAccess: Boolean = try {
@@ -350,9 +350,9 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
}
if (verifiedUnidentifiedAccess) {
UnidentifiedAccessMode.ENABLED
SealedSenderAccessMode.ENABLED
} else {
UnidentifiedAccessMode.DISABLED
SealedSenderAccessMode.DISABLED
}
}
}

View File

@@ -5,12 +5,13 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -123,7 +124,7 @@ public class SendDeliveryReceiptJob extends BaseJob {
timestamp);
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, recipient),
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, this::getGroupSendFullToken),
receiptMessage,
recipient.getNeedsPniSignature());
@@ -132,6 +133,19 @@ public class SendDeliveryReceiptJob extends BaseJob {
}
}
private @Nullable GroupSendFullToken getGroupSendFullToken() {
if (messageId == null) {
return null;
}
long threadId = SignalDatabase.messages().getThreadIdForMessage(messageId.getId());
if (threadId == -1) {
return null;
}
return SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
if (e instanceof ServerRejectedException) return false;

View File

@@ -9,14 +9,14 @@ import androidx.annotation.VisibleForTesting;
import org.signal.core.util.ListUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -188,7 +188,8 @@ public class SendReadReceiptJob extends BaseJob {
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageSentTimestamps, timestamp);
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient,
() -> SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId)),
receiptMessage,
recipient.getNeedsPniSignature());

View File

@@ -4,19 +4,18 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
@@ -87,12 +86,11 @@ public final class SendRetryReceiptJob extends BaseJob {
return;
}
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<UnidentifiedAccessPair> access = UnidentifiedAccessUtil.getAccessFor(context, recipient);
Optional<byte[]> group = groupId.map(GroupId::getDecodedId);
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
Optional<byte[]> group = groupId.map(GroupId::getDecodedId);
Log.i(TAG, "Sending retry receipt for " + errorMessage.getTimestamp() + " to " + recipientId + ", device: " + errorMessage.getDeviceId());
AppDependencies.getSignalServiceMessageSender().sendRetryReceipt(address, access, group, errorMessage);
AppDependencies.getSignalServiceMessageSender().sendRetryReceipt(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient), group, errorMessage);
}
@Override

View File

@@ -8,15 +8,15 @@ import androidx.annotation.Nullable;
import org.signal.core.util.ListUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
@@ -208,7 +208,8 @@ public class SendViewedReceiptJob extends BaseJob {
timestamp);
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient,
() -> SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId)),
receiptMessage,
recipient.getNeedsPniSignature());

View File

@@ -6,19 +6,19 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
@@ -85,12 +85,14 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
return;
}
GroupId.V2 groupId;
DistributionId distributionId;
GroupId.V2 groupId;
DistributionId distributionId;
SealedSenderAccess.CreateGroupSendToken createGroupSendFullToken = null;
if (threadRecipient.isPushV2Group()) {
groupId = threadRecipient.requireGroupId().requireV2();
distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
groupId = threadRecipient.requireGroupId().requireV2();
distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
createGroupSendFullToken = () -> SignalDatabase.groups().getGroupSendFullToken(groupId, targetRecipientId);
} else if (threadRecipient.isDistributionList()) {
groupId = null;
distributionId = SignalDatabase.distributionLists().getDistributionId(threadRecipientId);
@@ -116,10 +118,10 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
}
}
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
List<SignalServiceAddress> address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, targetRecipient));
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
List<Optional<UnidentifiedAccessPair>> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(targetRecipient));
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
List<SignalServiceAddress> address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, targetRecipient));
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
List<SealedSenderAccess> access = Collections.singletonList(SealedSenderAccessUtil.getSealedSenderAccessFor(targetRecipient, createGroupSendFullToken));
SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, Optional.ofNullable(groupId).map(GroupId::getDecodedId), false, false).get(0);

View File

@@ -12,7 +12,6 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.UnknownStorageIdTable;
@@ -209,7 +208,7 @@ public class StorageSyncJob extends BaseJob {
} else {
Log.w(TAG, "Failed to decrypt remote storage! Requesting new keys from primary.", e);
SignalStore.storageService().clearStorageKeyFromPrimary();
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forRequest(RequestMessage.forType(SyncMessage.Request.Type.KEYS)), UnidentifiedAccessUtil.getAccessForSync(context));
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forRequest(RequestMessage.forType(SyncMessage.Request.Type.KEYS)));
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.messages
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.util.RemoteConfig
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
/**
* Internal user only notifier when "bad" things happen with group send endorsement sends.
*/
object GroupSendEndorsementInternalNotifier : SealedSenderAccess.FallbackListener {
private const val TAG = "GSENotifier"
private var lastGroupSendNotify: Duration = 0.milliseconds
private var skippedGroupSendNotifies = 0
private var lastMissingNotify: Duration = 0.milliseconds
private var lastFallbackNotify: Duration = 0.milliseconds
@JvmStatic
fun init() {
if (RemoteConfig.internalUser) {
SealedSenderAccess.fallbackListener = this
}
}
override fun onAccessToTokenFallback() {
Log.w(TAG, "Fallback from access key to token", Throwable())
postFallbackError(AppDependencies.application)
}
override fun onTokenToAccessFallback(hasAccessKeyFallback: Boolean) {
Log.w(TAG, "Fallback from token hasAccessKey=$hasAccessKeyFallback", Throwable())
postFallbackError(AppDependencies.application)
}
@JvmStatic
fun postGroupSendFallbackError(context: Context) {
val now = System.currentTimeMillis().milliseconds
if (lastGroupSendNotify + 5.minutes > now && skippedGroupSendNotifies < 5) {
skippedGroupSendNotifies++
return
}
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("[Internal-only] GSE failed for group send")
.setContentText("Please tap to send a debug log")
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
.build()
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
lastGroupSendNotify = now
skippedGroupSendNotifies = 0
}
@JvmStatic
fun postMissingGroupSendEndorsement(context: Context) {
val now = System.currentTimeMillis().milliseconds
if (lastMissingNotify + 5.minutes > now) {
return
}
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("[Internal-only] GSE missing for recipient")
.setContentText("Please tap to send a debug log")
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
.build()
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
lastMissingNotify = now
}
@JvmStatic
fun postFallbackError(context: Context) {
val now = System.currentTimeMillis().milliseconds
if (lastFallbackNotify + 5.minutes > now) {
return
}
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("[Internal-only] GSE fallback occurred!")
.setContentText("Please tap to send a debug log")
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
.build()
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
lastFallbackNotify = now
}
}

View File

@@ -7,34 +7,44 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.metadata.certificate.SenderCertificate;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.InvalidRegistrationIdException;
import org.signal.libsignal.protocol.NoSessionException;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement;
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.MessageSendLogTables;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.DistributionListId;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.model.GroupSendEndorsementRecords;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.thoughtcrime.securesms.util.RecipientAccessList;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.CancelationException;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.SignalServiceMessageSender.LegacyGroupEvents;
import org.whispersystems.signalservice.api.SignalServiceMessageSender.SenderKeyGroupEvents;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.groupsv2.GroupSendEndorsements;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
@@ -43,6 +53,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessageRe
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
import org.whispersystems.signalservice.api.util.Preconditions;
@@ -52,6 +63,7 @@ import org.whispersystems.signalservice.internal.push.http.PartialSendBatchCompl
import org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -248,28 +260,81 @@ public final class GroupSendUtil {
Set<Recipient> unregisteredTargets = allTargets.stream().filter(Recipient::isUnregistered).collect(Collectors.toSet());
List<Recipient> registeredTargets = allTargets.stream().filter(r -> !unregisteredTargets.contains(r)).collect(Collectors.toList());
RecipientData recipients = new RecipientData(context, registeredTargets, isStorySend);
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.empty();
RecipientData recipients = new RecipientData(context, registeredTargets, isStorySend);
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.empty();
GroupSendEndorsementRecords groupSendEndorsementRecords = groupRecord.filter(GroupRecord::isV2Group).map(g -> SignalDatabase.groups().getGroupSendEndorsements(g.getId())).orElse(null);
long groupSendEndorsementExpiration = groupRecord.map(GroupRecord::getGroupSendEndorsementExpiration).orElse(0L);
SenderCertificate senderCertificate = SealedSenderAccessUtil.getSealedSenderCertificate();
boolean useGroupSendEndorsements = groupSendEndorsementRecords != null;
if (useGroupSendEndorsements && senderCertificate == null) {
Log.w(TAG, "Can't use group send endorsements without a sealed sender certificate, falling back to access key");
useGroupSendEndorsements = false;
} else if (useGroupSendEndorsements) {
boolean refreshGroupSendEndorsements = false;
if (groupSendEndorsementExpiration == 0) {
Log.i(TAG, "No group send endorsements expiration set, need to refresh");
refreshGroupSendEndorsements = true;
} else if (groupSendEndorsementExpiration - TimeUnit.HOURS.toMillis(2) < System.currentTimeMillis()) {
Log.i(TAG, "Group send endorsements are expired or expire imminently, refresh. Expires in " + (groupSendEndorsementExpiration - System.currentTimeMillis()) + "ms");
refreshGroupSendEndorsements = true;
} else if (groupSendEndorsementRecords.isMissingAnyEndorsements()) {
Log.i(TAG, "Missing group send endorsements for some members, refresh.");
refreshGroupSendEndorsements = true;
}
if (refreshGroupSendEndorsements) {
try {
GroupManager.updateGroupSendEndorsements(context, groupRecord.get().requireV2GroupProperties().getGroupMasterKey());
groupSendEndorsementExpiration = SignalDatabase.groups().getGroupSendEndorsementsExpiration(groupId);
groupSendEndorsementRecords = SignalDatabase.groups().getGroupSendEndorsements(groupId);
} catch (GroupChangeException | IOException e) {
if (groupSendEndorsementExpiration == 0) {
Log.w(TAG, "Unable to update group send endorsements, falling back to access key", e);
useGroupSendEndorsements = false;
groupSendEndorsementRecords = new GroupSendEndorsementRecords(Collections.emptyMap());
} else {
Log.w(TAG, "Unable to update group send endorsements, using what we have", e);
}
}
Log.d(TAG, "Refresh all group state because we needed to refresh gse");
AppDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId));
}
}
List<Recipient> senderKeyTargets = new LinkedList<>();
List<Recipient> legacyTargets = new LinkedList<>();
for (Recipient recipient : registeredTargets) {
Optional<UnidentifiedAccessPair> access = recipients.getAccessPair(recipient.getId());
boolean validMembership = true;
Optional<UnidentifiedAccess> access = recipients.getAccessPair(recipient.getId());
boolean validMembership = groupId == null || (groupRecord.isPresent() && groupRecord.get().getMembers().contains(recipient.getId()));
if (groupId != null && (!groupRecord.isPresent() || !groupRecord.get().getMembers().contains(recipient.getId()))) {
validMembership = false;
}
if (recipient.getHasServiceId() &&
access.isPresent() &&
access.get().getTargetUnidentifiedAccess().isPresent() &&
validMembership)
{
senderKeyTargets.add(recipient);
if (useGroupSendEndorsements) {
GroupSendEndorsement groupSendEndorsement = groupSendEndorsementRecords.getEndorsement(recipient.getId());
if (groupSendEndorsement != null && recipient.getHasAci() && validMembership) {
senderKeyTargets.add(recipient);
} else {
legacyTargets.add(recipient);
if (validMembership) {
Log.w(TAG, "Should be using group send endorsement but not found for " + recipient.getId());
if (RemoteConfig.internalUser()) {
GroupSendEndorsementInternalNotifier.postMissingGroupSendEndorsement(context);
}
}
}
} else {
legacyTargets.add(recipient);
// Use sender key
if (recipient.getHasServiceId() &&
access.isPresent() &&
validMembership)
{
senderKeyTargets.add(recipient);
} else {
legacyTargets.add(recipient);
}
}
}
@@ -299,7 +364,7 @@ public final class GroupSendUtil {
List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
if (senderKeyTargets.size() > 0 && distributionId != null) {
if (Util.hasItems(senderKeyTargets) && distributionId != null) {
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(distributionId);
long keyAge = System.currentTimeMillis() - keyCreateTime;
@@ -309,14 +374,36 @@ public final class GroupSendUtil {
}
try {
List<SignalServiceAddress> targets = senderKeyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<UnidentifiedAccess> access = senderKeyTargets.stream().map(r -> recipients.requireAccess(r.getId())).collect(Collectors.toList());
List<SignalServiceAddress> targets = new ArrayList<>(senderKeyTargets.size());
List<UnidentifiedAccess> access = new ArrayList<>(senderKeyTargets.size());
Map<ServiceId.ACI, GroupSendEndorsement> senderKeyEndorsements = new HashMap<>(senderKeyTargets.size());
GroupSendEndorsements groupSendEndorsements = null;
for (Recipient recipient : senderKeyTargets) {
targets.add(recipients.getAddress(recipient.getId()));
if (useGroupSendEndorsements) {
senderKeyEndorsements.put(recipient.requireAci(), groupSendEndorsementRecords.getEndorsement(recipient.getId()));
access.add(recipients.getAccess(recipient.getId()));
} else {
access.add(recipients.requireAccess(recipient.getId()));
}
}
if (useGroupSendEndorsements) {
groupSendEndorsements = new GroupSendEndorsements(
groupSendEndorsementExpiration,
senderKeyEndorsements,
senderCertificate,
GroupSecretParams.deriveFromMasterKey(groupRecord.get().requireV2GroupProperties().getGroupMasterKey())
);
}
final MessageSendLogTables messageLogDatabase = SignalDatabase.messageLog();
final AtomicLong entryId = new AtomicLong(-1);
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, isRecipientUpdate, partialResults -> {
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, groupSendEndorsements, isRecipientUpdate, partialResults -> {
if (!includeInMessageLog) {
return;
}
@@ -343,6 +430,10 @@ public final class GroupSendUtil {
} catch (InvalidUnidentifiedAccessHeaderException e) {
Log.w(TAG, "Someone had a bad UD header. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
if (useGroupSendEndorsements && RemoteConfig.internalUser()) {
GroupSendEndorsementInternalNotifier.postGroupSendFallbackError(context);
}
} catch (NoSessionException e) {
Log.w(TAG, "No session. Falling back to legacy sends.", e);
legacyTargets.addAll(senderKeyTargets);
@@ -377,15 +468,32 @@ public final class GroupSendUtil {
Log.i(TAG, "Need to do a legacy send to send a sync message for a group of only ourselves.");
}
List<SignalServiceAddress> targets = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<Optional<UnidentifiedAccessPair>> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList());
boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0;
List<SignalServiceAddress> legacyTargetAddresses = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
List<UnidentifiedAccess> legacyTargetAccesses = legacyTargets.stream().map(r -> recipients.getAccess(r.getId())).collect(Collectors.toList());
List<GroupSendFullToken> groupSendTokens = null;
boolean recipientUpdate = isRecipientUpdate || allResults.isEmpty();
if (useGroupSendEndorsements) {
Instant expiration = Instant.ofEpochMilli(groupSendEndorsementExpiration);
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupRecord.get().requireV2GroupProperties().getGroupMasterKey());
groupSendTokens = new ArrayList<>(legacyTargetAddresses.size());
for (Recipient r : legacyTargets) {
GroupSendEndorsement endorsement = groupSendEndorsementRecords.getEndorsement(r.getId());
if (r.getHasAci() && endorsement != null) {
groupSendTokens.add(endorsement.toFullToken(groupSecretParams, expiration));
} else {
groupSendTokens.add(null);
}
}
}
final MessageSendLogTables messageLogDatabase = SignalDatabase.messageLog();
final AtomicLong entryId = new AtomicLong(-1);
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, legacyTargets, access, recipientUpdate, result -> {
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, legacyTargetAddresses, legacyTargets, SealedSenderAccess.forFanOutGroupSend(groupSendTokens, SealedSenderAccessUtil.getSealedSenderCertificate(), legacyTargetAccesses), recipientUpdate, result -> {
if (!includeInMessageLog) {
return;
}
@@ -402,7 +510,7 @@ public final class GroupSendUtil {
allResults.addAll(results);
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
Log.d(TAG, "Successfully sent using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
Log.d(TAG, "Successfully sent using 1:1 to " + successCount + "/" + legacyTargetAddresses.size() + " legacy targets.");
} else if (relatedMessageId != null) {
SignalLocalMetrics.GroupMessageSend.onLegacyMessageSent(relatedMessageId.getId());
SignalLocalMetrics.GroupMessageSend.onLegacySyncFinished(relatedMessageId.getId());
@@ -448,6 +556,7 @@ public final class GroupSendUtil {
@NonNull DistributionId distributionId,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<UnidentifiedAccess> access,
@Nullable GroupSendEndorsements groupSendEndorsements,
boolean isRecipientUpdate,
@Nullable PartialSendBatchCompleteListener partialListener)
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException;
@@ -455,7 +564,7 @@ public final class GroupSendUtil {
@NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<Recipient> targetRecipients,
@NonNull List<Optional<UnidentifiedAccessPair>> access,
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
boolean isRecipientUpdate,
@Nullable PartialSendCompleteListener partialListener,
@Nullable CancelationSignal cancelationSignal)
@@ -504,19 +613,20 @@ public final class GroupSendUtil {
@NonNull DistributionId distributionId,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<UnidentifiedAccess> access,
@Nullable GroupSendEndorsements groupSendEndorsements,
boolean isRecipientUpdate,
@Nullable PartialSendBatchCompleteListener partialListener)
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
{
SenderKeyGroupEvents listener = relatedMessageId != null ? new SenderKeyMetricEventListener(relatedMessageId.getId()) : SenderKeyGroupEvents.EMPTY;
return messageSender.sendGroupDataMessage(distributionId, targets, access, isRecipientUpdate, contentHint, message, listener, urgent, isForStory, editMessage, partialListener);
return messageSender.sendGroupDataMessage(distributionId, targets, access, groupSendEndorsements, isRecipientUpdate, contentHint, message, listener, urgent, isForStory, editMessage, partialListener);
}
@Override
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<Recipient> targetRecipients,
@NonNull List<Optional<UnidentifiedAccessPair>> access,
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
boolean isRecipientUpdate,
@Nullable PartialSendCompleteListener partialListener,
@Nullable CancelationSignal cancelationSignal)
@@ -524,13 +634,14 @@ public final class GroupSendUtil {
{
// PniSignatures are only needed for 1:1 messages, but some message jobs use the GroupSendUtil methods to send 1:1
if (targets.size() == 1 && relatedMessageId == null) {
Recipient targetRecipient = targetRecipients.get(0);
SendMessageResult result;
Recipient targetRecipient = targetRecipients.get(0);
SealedSenderAccess sealedSenderAccess = sealedSenderAccesses.get(0);
SendMessageResult result;
if (editMessage != null) {
result = messageSender.sendEditMessage(targets.get(0), access.get(0), contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, editMessage.getTargetSentTimestamp());
result = messageSender.sendEditMessage(targets.get(0), sealedSenderAccess, contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, editMessage.getTargetSentTimestamp());
} else {
result = messageSender.sendDataMessage(targets.get(0), access.get(0), contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, targetRecipient.getNeedsPniSignature());
result = messageSender.sendDataMessage(targets.get(0), sealedSenderAccess, contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, targetRecipient.getNeedsPniSignature());
}
if (targetRecipient.getNeedsPniSignature()) {
@@ -540,10 +651,11 @@ public final class GroupSendUtil {
return Collections.singletonList(result);
} else {
LegacyGroupEvents listener = relatedMessageId != null ? new LegacyMetricEventListener(relatedMessageId.getId()) : LegacyGroupEvents.EMPTY;
if (editMessage != null) {
return messageSender.sendEditMessage(targets, access, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent, editMessage.getTargetSentTimestamp());
return messageSender.sendEditMessage(targets, sealedSenderAccesses, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent, editMessage.getTargetSentTimestamp());
} else {
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent);
return messageSender.sendDataMessage(targets, sealedSenderAccesses, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent);
}
}
}
@@ -591,11 +703,12 @@ public final class GroupSendUtil {
@NonNull DistributionId distributionId,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<UnidentifiedAccess> access,
@Nullable GroupSendEndorsements groupSendEndorsements,
boolean isRecipientUpdate,
@Nullable PartialSendBatchCompleteListener partialListener)
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
{
messageSender.sendGroupTyping(distributionId, targets, access, message);
messageSender.sendGroupTyping(distributionId, targets, access, groupSendEndorsements, message);
List<SendMessageResult> results = targets.stream().map(a -> SendMessageResult.success(a, Collections.emptyList(), true, false, -1, Optional.empty())).collect(Collectors.toList());
if (partialListener != null) {
@@ -609,13 +722,13 @@ public final class GroupSendUtil {
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<Recipient> targetRecipients,
@NonNull List<Optional<UnidentifiedAccessPair>> access,
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
boolean isRecipientUpdate,
@Nullable PartialSendCompleteListener partialListener,
@Nullable CancelationSignal cancelationSignal)
throws IOException
{
messageSender.sendTyping(targets, access, message, cancelationSignal);
messageSender.sendTyping(targets, sealedSenderAccesses, message, cancelationSignal);
return targets.stream().map(a -> SendMessageResult.success(a, Collections.emptyList(), true, false, -1, Optional.empty())).collect(Collectors.toList());
}
@@ -658,24 +771,25 @@ public final class GroupSendUtil {
@NonNull DistributionId distributionId,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<UnidentifiedAccess> access,
@Nullable GroupSendEndorsements groupSendEndorsements,
boolean isRecipientUpdate,
@Nullable PartialSendBatchCompleteListener partialSendListener)
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
{
return messageSender.sendCallMessage(distributionId, targets, access, message, partialSendListener);
return messageSender.sendCallMessage(distributionId, targets, access, groupSendEndorsements, message, partialSendListener);
}
@Override
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<Recipient> targetRecipients,
@NonNull List<Optional<UnidentifiedAccessPair>> access,
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
boolean isRecipientUpdate,
@Nullable PartialSendCompleteListener partialListener,
@Nullable CancelationSignal cancelationSignal)
throws IOException
{
return messageSender.sendCallMessage(targets, access, message);
return messageSender.sendCallMessage(targets, sealedSenderAccesses, message);
}
@Override
@@ -730,18 +844,19 @@ public final class GroupSendUtil {
@NonNull DistributionId distributionId,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<UnidentifiedAccess> access,
@Nullable GroupSendEndorsements groupSendEndorsements,
boolean isRecipientUpdate,
@Nullable PartialSendBatchCompleteListener partialListener)
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
{
return messageSender.sendGroupStory(distributionId, Optional.ofNullable(groupId).map(GroupId::getDecodedId), targets, access, isRecipientUpdate, message, getSentTimestamp(), manifest, partialListener);
return messageSender.sendGroupStory(distributionId, Optional.ofNullable(groupId).map(GroupId::getDecodedId), targets, access, groupSendEndorsements, isRecipientUpdate, message, getSentTimestamp(), manifest, partialListener);
}
@Override
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
@NonNull List<SignalServiceAddress> targets,
@NonNull List<Recipient> targetRecipients,
@NonNull List<Optional<UnidentifiedAccessPair>> access,
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
boolean isRecipientUpdate,
@Nullable PartialSendCompleteListener partialListener,
@Nullable CancelationSignal cancelationSignal)
@@ -839,12 +954,12 @@ public final class GroupSendUtil {
*/
private static final class RecipientData {
private final Map<RecipientId, Optional<UnidentifiedAccessPair>> accessById;
private final Map<RecipientId, Optional<UnidentifiedAccess>> accessById;
private final Map<RecipientId, SignalServiceAddress> addressById;
private final RecipientAccessList accessList;
RecipientData(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory) throws IOException {
this.accessById = UnidentifiedAccessUtil.getAccessMapFor(context, recipients, isForStory);
this.accessById = SealedSenderAccessUtil.getAccessMapFor(recipients, isForStory);
this.addressById = mapAddresses(context, recipients);
this.accessList = new RecipientAccessList(recipients);
}
@@ -853,22 +968,22 @@ public final class GroupSendUtil {
return Objects.requireNonNull(addressById.get(id));
}
@NonNull Optional<UnidentifiedAccessPair> getAccessPair(@NonNull RecipientId id) {
@NonNull Optional<UnidentifiedAccess> getAccessPair(@NonNull RecipientId id) {
return Objects.requireNonNull(accessById.get(id));
}
@Nullable UnidentifiedAccess getAccess(@NonNull RecipientId id) {
return Objects.requireNonNull(accessById.get(id)).orElse(null);
}
@NonNull UnidentifiedAccess requireAccess(@NonNull RecipientId id) {
return Objects.requireNonNull(accessById.get(id)).get().getTargetUnidentifiedAccess().get();
return Objects.requireNonNull(accessById.get(id)).get();
}
@NonNull RecipientId requireRecipientId(@NonNull SignalServiceAddress address) {
return accessList.requireIdByAddress(address);
}
@NonNull List<RecipientId> requireRecipientIds(@NonNull List<SignalServiceAddress> addresses) {
return addresses.stream().map(accessList::requireIdByAddress).collect(Collectors.toList());
}
private static @NonNull Map<RecipientId, SignalServiceAddress> mapAddresses(@NonNull Context context, @NonNull List<Recipient> recipients) throws IOException {
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, recipients);

View File

@@ -32,7 +32,7 @@ import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.BadGroupIdException
@@ -137,7 +137,7 @@ object MessageDecryptor {
val bufferedStore = bufferedProtocolStore.get(destination)
val localAddress = SignalServiceAddress(selfAci, SignalStore.account.e164)
val cipher = SignalServiceCipher(localAddress, SignalStore.account.deviceId, bufferedStore, ReentrantSessionLock.INSTANCE, UnidentifiedAccessUtil.getCertificateValidator())
val cipher = SignalServiceCipher(localAddress, SignalStore.account.deviceId, bufferedStore, ReentrantSessionLock.INSTANCE, SealedSenderAccessUtil.getCertificateValidator())
return try {
val startTimeNanos = System.nanoTime()

View File

@@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
import org.thoughtcrime.securesms.database.RecipientTable.MissingRecipientException
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
@@ -98,7 +98,7 @@ class Recipient(
val hiddenState: HiddenState = HiddenState.NOT_HIDDEN,
val lastProfileFetchTime: Long = 0,
private val notificationChannelValue: String? = null,
private val unidentifiedAccessModeValue: UnidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN,
private val sealedSenderAccessModeValue: SealedSenderAccessMode = SealedSenderAccessMode.UNKNOWN,
private val capabilities: RecipientRecord.Capabilities = RecipientRecord.Capabilities.UNKNOWN,
val storageId: ByteArray? = null,
val mentionSetting: MentionSetting = MentionSetting.ALWAYS_NOTIFY,
@@ -318,10 +318,10 @@ class Recipient(
val deleteSyncCapability: Capability = capabilities.deleteSync
/** The state around whether we can send sealed sender to this user. */
val unidentifiedAccessMode: UnidentifiedAccessMode = if (pni.isPresent && pni == serviceId) {
UnidentifiedAccessMode.DISABLED
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
SealedSenderAccessMode.DISABLED
} else {
unidentifiedAccessModeValue
sealedSenderAccessModeValue
}
/** The wallpaper to render as the chat background, if present. */
@@ -760,7 +760,7 @@ class Recipient(
systemProfileName == other.systemProfileName &&
profileAvatar == other.profileAvatar &&
notificationChannelValue == other.notificationChannelValue &&
unidentifiedAccessModeValue == other.unidentifiedAccessModeValue &&
sealedSenderAccessModeValue == other.sealedSenderAccessModeValue &&
storageId.contentEquals(other.storageId) &&
mentionSetting == other.mentionSetting &&
wallpaperValue == other.wallpaperValue &&

View File

@@ -162,7 +162,7 @@ object RecipientCreator {
lastProfileFetchTime = record.lastProfileFetch,
isSelf = isSelf,
notificationChannelValue = record.notificationChannel,
unidentifiedAccessModeValue = record.unidentifiedAccessMode,
sealedSenderAccessModeValue = record.sealedSenderAccessMode,
capabilities = record.capabilities,
storageId = record.storageId,
mentionSetting = record.mentionSetting,

View File

@@ -36,7 +36,7 @@ import org.signal.ringrtc.PeekInfo;
import org.signal.ringrtc.Remote;
import org.signal.storageservice.protos.groups.GroupExternalCredential;
import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.CallLinkTable;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.GroupTable;
@@ -66,8 +66,8 @@ import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.thoughtcrime.securesms.util.RecipientAccessList;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.rx.RxStore;
@@ -75,6 +75,7 @@ import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
import org.webrtc.PeerConnection;
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
@@ -780,8 +781,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
try {
AppDependencies.getSignalServiceMessageSender()
.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
recipient.isSelf() ? Optional.empty() : UnidentifiedAccessUtil.getAccessFor(context, recipient),
callMessage);
recipient.isSelf() ? SealedSenderAccess.NONE : SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
callMessage);
} catch (UntrustedIdentityException e) {
Log.i(TAG, "onSendCallMessage onFailure: ", e);
RetrieveProfileJob.enqueue(recipient.getId());
@@ -1129,8 +1130,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
try {
AppDependencies.getSignalServiceMessageSender()
.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
UnidentifiedAccessUtil.getAccessFor(context, recipient),
callMessage);
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
callMessage);
process((s, p) -> p.handleMessageSentSuccess(s, remotePeer.getCallId()));
} catch (UntrustedIdentityException e) {
RetrieveProfileJob.enqueue(remotePeer.getId());
@@ -1158,7 +1159,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
networkExecutor.execute(() -> {
try {
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall);
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
} catch (IOException | UntrustedIdentityException e) {
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
}
@@ -1175,7 +1176,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
networkExecutor.execute(() -> {
try {
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall);
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
} catch (IOException | UntrustedIdentityException e) {
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
}
@@ -1188,7 +1189,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
networkExecutor.execute(() -> {
try {
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, true);
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
} catch (IOException | UntrustedIdentityException e) {
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
}

View File

@@ -17,7 +17,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
@@ -38,8 +38,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.profiles.AvatarUploadParams;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
@@ -118,7 +117,7 @@ public final class ProfileUtil {
ServiceResponse<ProfileAndCredential> response = Single
.fromCallable(() -> new SignalServiceAddress(pni))
.flatMap(address -> profileService.getProfile(address, Optional.empty(), Optional.empty(), requestType, Locale.getDefault()))
.flatMap(address -> profileService.getProfile(address, Optional.empty(), SealedSenderAccess.NONE, requestType, Locale.getDefault()))
.onErrorReturn(t -> ServiceResponse.forUnknownError(t))
.blockingGet();
@@ -137,12 +136,12 @@ public final class ProfileUtil {
@NonNull SignalServiceProfile.RequestType requestType,
boolean allowUnidentifiedAccess)
{
ProfileService profileService = AppDependencies.getProfileService();
Optional<UnidentifiedAccess> unidentifiedAccess = allowUnidentifiedAccess ? getUnidentifiedAccess(context, recipient) : Optional.empty();
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
ProfileService profileService = AppDependencies.getProfileService();
SealedSenderAccess sealedSenderAccess = allowUnidentifiedAccess ? SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, false) : SealedSenderAccess.NONE;
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
return Single.fromCallable(() -> toSignalServiceAddress(context, recipient))
.flatMap(address -> profileService.getProfile(address, profileKey, unidentifiedAccess, requestType, Locale.getDefault()).map(p -> new Pair<>(recipient, p)))
.flatMap(address -> profileService.getProfile(address, profileKey, sealedSenderAccess, requestType, Locale.getDefault()).map(p -> new Pair<>(recipient, p)))
.onErrorReturn(t -> new Pair<>(recipient, ServiceResponse.forUnknownError(t)));
}
@@ -397,16 +396,6 @@ public final class ProfileUtil {
}
}
private static Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Context context, @NonNull Recipient recipient) {
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient, false);
if (unidentifiedAccess.isPresent()) {
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
}
return Optional.empty();
}
private static @NonNull SignalServiceAddress toSignalServiceAddress(@NonNull Context context, @NonNull Recipient recipient) throws IOException {
if (recipient.getRegistered() == RecipientTable.RegisteredState.NOT_REGISTERED) {
if (recipient.getHasServiceId()) {