Update libsignal-service

This commit is contained in:
AsamK 2024-07-25 16:24:41 +02:00
parent 5ff66728e3
commit e51b1ee23a
10 changed files with 201 additions and 137 deletions

View file

@ -1,7 +1,13 @@
[ [
{
"name":"[B"
},
{ {
"name":"[Z" "name":"[Z"
}, },
{
"name":"[[B"
},
{ {
"name":"com.sun.security.auth.module.UnixSystem", "name":"com.sun.security.auth.module.UnixSystem",
"fields":[{"name":"gid"}, {"name":"groups"}, {"name":"uid"}, {"name":"username"}] "fields":[{"name":"gid"}, {"name":"groups"}, {"name":"uid"}, {"name":"username"}]

View file

@ -36,7 +36,6 @@ public class ProofRequiredException extends Exception {
static Option from(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException.Option option) { static Option from(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException.Option option) {
return switch (option) { return switch (option) {
case RECAPTCHA -> CAPTCHA;
case CAPTCHA -> CAPTCHA; case CAPTCHA -> CAPTCHA;
case PUSH_CHALLENGE -> PUSH_CHALLENGE; case PUSH_CHALLENGE -> PUSH_CHALLENGE;
}; };

View file

@ -27,19 +27,8 @@ public class ServiceConfig {
public static final long UNREGISTERED_LIFESPAN = TimeUnit.DAYS.toMillis(30); public static final long UNREGISTERED_LIFESPAN = TimeUnit.DAYS.toMillis(30);
public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) { public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) {
final var giftBadges = !isPrimaryDevice;
final var pni = !isPrimaryDevice;
final var paymentActivation = !isPrimaryDevice;
final var deleteSync = !isPrimaryDevice; final var deleteSync = !isPrimaryDevice;
return new AccountAttributes.Capabilities(true, return new AccountAttributes.Capabilities(true, deleteSync);
true,
true,
true,
true,
giftBadges,
pni,
paymentActivation,
deleteSync);
} }
public static ServiceEnvironmentConfig getServiceEnvironmentConfig( public static ServiceEnvironmentConfig getServiceEnvironmentConfig(

View file

@ -33,13 +33,16 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.storageservice.protos.groups.GroupChange; import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupChangeResponse;
import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse;
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
@ -133,9 +136,10 @@ public class GroupHelper {
} }
if (group == null) { if (group == null) {
try { try {
group = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams); final var response = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams);
if (group != null) { if (response != null) {
group = handleDecryptedGroupResponse(groupInfoV2, response);
storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, group); storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, group);
} }
} catch (NotAGroupMemberException ignored) { } catch (NotAGroupMemberException ignored) {
@ -156,6 +160,35 @@ public class GroupHelper {
return groupInfoV2; return groupInfoV2;
} }
private DecryptedGroup handleDecryptedGroupResponse(
GroupInfoV2 groupInfoV2, final DecryptedGroupResponse decryptedGroupResponse
) {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
ReceivedGroupSendEndorsements groupSendEndorsements = dependencies.getGroupsV2Operations()
.forGroup(groupSecretParams)
.receiveGroupSendEndorsements(account.getAci(),
decryptedGroupResponse.getGroup(),
decryptedGroupResponse.getGroupSendEndorsementsResponse());
// TODO save group endorsements
return decryptedGroupResponse.getGroup();
}
private GroupChange handleGroupChangeResponse(
final GroupInfoV2 groupInfoV2, final GroupChangeResponse groupChangeResponse
) {
ReceivedGroupSendEndorsements groupSendEndorsements = dependencies.getGroupsV2Operations()
.forGroup(GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()))
.receiveGroupSendEndorsements(account.getAci(),
groupInfoV2.getGroup(),
groupChangeResponse.groupSendEndorsementsResponse);
// TODO save group endorsements
return groupChangeResponse.groupChange;
}
public Pair<GroupId, SendGroupMessageResults> createGroup( public Pair<GroupId, SendGroupMessageResults> createGroup(
String name, Set<RecipientId> members, String avatarFile String name, Set<RecipientId> members, String avatarFile
) throws IOException, AttachmentInvalidException { ) throws IOException, AttachmentInvalidException {
@ -181,7 +214,7 @@ public class GroupHelper {
final var gv2 = gv2Pair.first(); final var gv2 = gv2Pair.first();
final var decryptedGroup = gv2Pair.second(); final var decryptedGroup = gv2Pair.second();
gv2.setGroup(decryptedGroup); gv2.setGroup(handleDecryptedGroupResponse(gv2, decryptedGroup));
gv2.setProfileSharingEnabled(true); gv2.setProfileSharingEnabled(true);
if (avatarBytes != null) { if (avatarBytes != null) {
context.getAvatarStore() context.getAvatarStore()
@ -277,7 +310,7 @@ public class GroupHelper {
var group = getGroupForUpdating(groupId); var group = getGroupForUpdating(groupId);
if (group instanceof GroupInfoV2 groupInfoV2) { if (group instanceof GroupInfoV2 groupInfoV2) {
Pair<DecryptedGroup, GroupChange> groupChangePair; Pair<DecryptedGroup, GroupChangeResponse> groupChangePair;
try { try {
groupChangePair = context.getGroupV2Helper().updateSelfProfileKey(groupInfoV2); groupChangePair = context.getGroupV2Helper().updateSelfProfileKey(groupInfoV2);
} catch (ConflictException e) { } catch (ConflictException e) {
@ -286,7 +319,9 @@ public class GroupHelper {
groupChangePair = context.getGroupV2Helper().updateSelfProfileKey(groupInfoV2); groupChangePair = context.getGroupV2Helper().updateSelfProfileKey(groupInfoV2);
} }
if (groupChangePair != null) { if (groupChangePair != null) {
sendUpdateGroupV2Message(groupInfoV2, groupChangePair.first(), groupChangePair.second()); sendUpdateGroupV2Message(groupInfoV2,
groupChangePair.first(),
handleGroupChangeResponse(groupInfoV2, groupChangePair.second()));
} }
} }
} }
@ -304,11 +339,12 @@ public class GroupHelper {
if (groupJoinInfo.pendingAdminApproval) { if (groupJoinInfo.pendingAdminApproval) {
throw new PendingAdminApprovalException("You have already requested to join the group."); throw new PendingAdminApprovalException("You have already requested to join the group.");
} }
final var groupChange = context.getGroupV2Helper() final var changeResponse = context.getGroupV2Helper()
.joinGroup(inviteLinkUrl.getGroupMasterKey(), inviteLinkUrl.getPassword(), groupJoinInfo); .joinGroup(inviteLinkUrl.getGroupMasterKey(), inviteLinkUrl.getPassword(), groupJoinInfo);
final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(), final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
groupJoinInfo.revision + 1, groupJoinInfo.revision + 1,
groupChange.encode()); changeResponse.groupChange == null ? null : changeResponse.groupChange.encode());
final var groupChange = handleGroupChangeResponse(group, changeResponse);
if (group.getGroup() == null) { if (group.getGroup() == null) {
// Only requested member, can't send update to group members // Only requested member, can't send update to group members
@ -404,17 +440,17 @@ public class GroupHelper {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
DecryptedGroup decryptedGroup; DecryptedGroup decryptedGroup;
try { try {
decryptedGroup = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams); final var response = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams);
if (response == null) {
return;
}
decryptedGroup = handleDecryptedGroupResponse(groupInfoV2, response);
} catch (NotAGroupMemberException e) { } catch (NotAGroupMemberException e) {
groupInfoV2.setPermissionDenied(true); groupInfoV2.setPermissionDenied(true);
account.getGroupStore().updateGroup(group); account.getGroupStore().updateGroup(group);
return; return;
} }
if (decryptedGroup == null) {
return;
}
try { try {
storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, decryptedGroup); storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, decryptedGroup);
} catch (NotAGroupMemberException ignored) { } catch (NotAGroupMemberException ignored) {
@ -496,10 +532,12 @@ public class GroupHelper {
) throws NotAGroupMemberException { ) throws NotAGroupMemberException {
final var revisionWeWereAdded = context.getGroupV2Helper().findRevisionWeWereAdded(newDecryptedGroup); final var revisionWeWereAdded = context.getGroupV2Helper().findRevisionWeWereAdded(newDecryptedGroup);
final var localRevision = localGroup.getGroup() == null ? 0 : localGroup.getGroup().revision; final var localRevision = localGroup.getGroup() == null ? 0 : localGroup.getGroup().revision;
final var sendEndorsementsExpirationMs = 0L;// TODO store expiration localGroup.getGroup() == null ? 0 : localGroup.getGroup().revision;
var fromRevision = Math.max(revisionWeWereAdded, localRevision); var fromRevision = Math.max(revisionWeWereAdded, localRevision);
final var newProfileKeys = new HashMap<RecipientId, ProfileKey>(); final var newProfileKeys = new HashMap<RecipientId, ProfileKey>();
while (true) { while (true) {
final var page = context.getGroupV2Helper().getDecryptedGroupHistoryPage(groupSecretParams, fromRevision); final var page = context.getGroupV2Helper()
.getDecryptedGroupHistoryPage(groupSecretParams, fromRevision, sendEndorsementsExpirationMs);
page.getChangeLogs() page.getChangeLogs()
.stream() .stream()
.map(DecryptedGroupChangeLog::getChange) .map(DecryptedGroupChangeLog::getChange)
@ -606,7 +644,9 @@ public class GroupHelper {
final var groupV2Helper = context.getGroupV2Helper(); final var groupV2Helper = context.getGroupV2Helper();
if (group.isPendingMember(account.getSelfRecipientId())) { if (group.isPendingMember(account.getSelfRecipientId())) {
var groupGroupChangePair = groupV2Helper.acceptInvite(group); var groupGroupChangePair = groupV2Helper.acceptInvite(group);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
if (members != null) { if (members != null) {
@ -614,14 +654,18 @@ public class GroupHelper {
requestingMembers.retainAll(group.getRequestingMembers()); requestingMembers.retainAll(group.getRequestingMembers());
if (!requestingMembers.isEmpty()) { if (!requestingMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.approveJoinRequestMembers(group, requestingMembers); var groupGroupChangePair = groupV2Helper.approveJoinRequestMembers(group, requestingMembers);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
final var newMembers = new HashSet<>(members); final var newMembers = new HashSet<>(members);
newMembers.removeAll(group.getMembers()); newMembers.removeAll(group.getMembers());
newMembers.removeAll(group.getRequestingMembers()); newMembers.removeAll(group.getRequestingMembers());
if (!newMembers.isEmpty()) { if (!newMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.addMembers(group, newMembers); var groupGroupChangePair = groupV2Helper.addMembers(group, newMembers);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
} }
@ -637,20 +681,26 @@ public class GroupHelper {
existingRemoveMembers.remove(account.getSelfRecipientId());// self can be removed with sendQuitGroupMessage existingRemoveMembers.remove(account.getSelfRecipientId());// self can be removed with sendQuitGroupMessage
if (!existingRemoveMembers.isEmpty()) { if (!existingRemoveMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.removeMembers(group, existingRemoveMembers); var groupGroupChangePair = groupV2Helper.removeMembers(group, existingRemoveMembers);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
var pendingRemoveMembers = new HashSet<>(removeMembers); var pendingRemoveMembers = new HashSet<>(removeMembers);
pendingRemoveMembers.retainAll(group.getPendingMembers()); pendingRemoveMembers.retainAll(group.getPendingMembers());
if (!pendingRemoveMembers.isEmpty()) { if (!pendingRemoveMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.revokeInvitedMembers(group, pendingRemoveMembers); var groupGroupChangePair = groupV2Helper.revokeInvitedMembers(group, pendingRemoveMembers);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
var requestingRemoveMembers = new HashSet<>(removeMembers); var requestingRemoveMembers = new HashSet<>(removeMembers);
requestingRemoveMembers.retainAll(group.getRequestingMembers()); requestingRemoveMembers.retainAll(group.getRequestingMembers());
if (!requestingRemoveMembers.isEmpty()) { if (!requestingRemoveMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.refuseJoinRequestMembers(group, requestingRemoveMembers); var groupGroupChangePair = groupV2Helper.refuseJoinRequestMembers(group, requestingRemoveMembers);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
} }
@ -663,7 +713,7 @@ public class GroupHelper {
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true); var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
result = sendUpdateGroupV2Message(group, result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(), groupGroupChangePair.first(),
groupGroupChangePair.second()); handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
} }
} }
@ -676,7 +726,7 @@ public class GroupHelper {
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false); var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
result = sendUpdateGroupV2Message(group, result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(), groupGroupChangePair.first(),
groupGroupChangePair.second()); handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
} }
} }
@ -686,7 +736,9 @@ public class GroupHelper {
newlyBannedMembers.removeAll(group.getBannedMembers()); newlyBannedMembers.removeAll(group.getBannedMembers());
if (!newlyBannedMembers.isEmpty()) { if (!newlyBannedMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.banMembers(group, newlyBannedMembers); var groupGroupChangePair = groupV2Helper.banMembers(group, newlyBannedMembers);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
} }
@ -695,38 +747,52 @@ public class GroupHelper {
existingUnbanMembers.retainAll(group.getBannedMembers()); existingUnbanMembers.retainAll(group.getBannedMembers());
if (!existingUnbanMembers.isEmpty()) { if (!existingUnbanMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.unbanMembers(group, existingUnbanMembers); var groupGroupChangePair = groupV2Helper.unbanMembers(group, existingUnbanMembers);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
} }
if (resetGroupLink) { if (resetGroupLink) {
var groupGroupChangePair = groupV2Helper.resetGroupLinkPassword(group); var groupGroupChangePair = groupV2Helper.resetGroupLinkPassword(group);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
if (groupLinkState != null) { if (groupLinkState != null) {
var groupGroupChangePair = groupV2Helper.setGroupLinkState(group, groupLinkState); var groupGroupChangePair = groupV2Helper.setGroupLinkState(group, groupLinkState);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
if (addMemberPermission != null) { if (addMemberPermission != null) {
var groupGroupChangePair = groupV2Helper.setAddMemberPermission(group, addMemberPermission); var groupGroupChangePair = groupV2Helper.setAddMemberPermission(group, addMemberPermission);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
if (editDetailsPermission != null) { if (editDetailsPermission != null) {
var groupGroupChangePair = groupV2Helper.setEditDetailsPermission(group, editDetailsPermission); var groupGroupChangePair = groupV2Helper.setEditDetailsPermission(group, editDetailsPermission);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
if (expirationTimer != null) { if (expirationTimer != null) {
var groupGroupChangePair = groupV2Helper.setMessageExpirationTimer(group, expirationTimer); var groupGroupChangePair = groupV2Helper.setMessageExpirationTimer(group, expirationTimer);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
if (isAnnouncementGroup != null) { if (isAnnouncementGroup != null) {
var groupGroupChangePair = groupV2Helper.setIsAnnouncementGroup(group, isAnnouncementGroup); var groupGroupChangePair = groupV2Helper.setIsAnnouncementGroup(group, isAnnouncementGroup);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
if (name != null || description != null || avatarFile != null) { if (name != null || description != null || avatarFile != null) {
@ -735,7 +801,9 @@ public class GroupHelper {
context.getAvatarStore() context.getAvatarStore()
.storeGroupAvatar(group.getGroupId(), outputStream -> outputStream.write(avatarFile)); .storeGroupAvatar(group.getGroupId(), outputStream -> outputStream.write(avatarFile));
} }
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
handleGroupChangeResponse(group, groupGroupChangePair.second()));
} }
return result; return result;
@ -771,7 +839,8 @@ public class GroupHelper {
groupInfoV2.setGroup(groupGroupChangePair.first()); groupInfoV2.setGroup(groupGroupChangePair.first());
account.getGroupStore().updateGroup(groupInfoV2); account.getGroupStore().updateGroup(groupInfoV2);
var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().encode()); var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2,
handleGroupChangeResponse(groupInfoV2, groupGroupChangePair.second()).encode());
return sendGroupMessage(messageBuilder, return sendGroupMessage(messageBuilder,
groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId()), groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId()),
groupInfoV2.getDistributionId()); groupInfoV2.getDistributionId());

View file

@ -19,6 +19,7 @@ import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange; import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupChangeResponse;
import org.signal.storageservice.protos.groups.Member; import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
@ -27,6 +28,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate; import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage; import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage;
@ -75,7 +77,7 @@ class GroupV2Helper {
groupApiCredentials = null; groupApiCredentials = null;
} }
DecryptedGroup getDecryptedGroup(final GroupSecretParams groupSecretParams) throws NotAGroupMemberException { DecryptedGroupResponse getDecryptedGroup(final GroupSecretParams groupSecretParams) throws NotAGroupMemberException {
try { try {
final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams); final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
return dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupsV2AuthorizationString); return dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupsV2AuthorizationString);
@ -85,7 +87,7 @@ class GroupV2Helper {
} }
logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage()); logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage());
return null; return null;
} catch (IOException | VerificationFailedException | InvalidGroupStateException e) { } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage()); logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage());
return null; return null;
} }
@ -103,19 +105,23 @@ class GroupV2Helper {
} }
GroupHistoryPage getDecryptedGroupHistoryPage( GroupHistoryPage getDecryptedGroupHistoryPage(
final GroupSecretParams groupSecretParams, int fromRevision final GroupSecretParams groupSecretParams, int fromRevision, long sendEndorsementsExpirationMs
) throws NotAGroupMemberException { ) throws NotAGroupMemberException {
try { try {
final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams); final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
return dependencies.getGroupsV2Api() return dependencies.getGroupsV2Api()
.getGroupHistoryPage(groupSecretParams, fromRevision, groupsV2AuthorizationString, false); .getGroupHistoryPage(groupSecretParams,
fromRevision,
groupsV2AuthorizationString,
false,
sendEndorsementsExpirationMs);
} catch (NonSuccessfulResponseCodeException e) { } catch (NonSuccessfulResponseCodeException e) {
if (e.getCode() == 403) { if (e.getCode() == 403) {
throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null); throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null);
} }
logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage()); logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage());
return null; return null;
} catch (IOException | VerificationFailedException | InvalidGroupStateException e) { } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage()); logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage());
return null; return null;
} }
@ -132,7 +138,7 @@ class GroupV2Helper {
return partialDecryptedGroup.revision; return partialDecryptedGroup.revision;
} }
Pair<GroupInfoV2, DecryptedGroup> createGroup( Pair<GroupInfoV2, DecryptedGroupResponse> createGroup(
String name, Set<RecipientId> members, byte[] avatarFile String name, Set<RecipientId> members, byte[] avatarFile
) { ) {
final var newGroup = buildNewGroup(name, members, avatarFile); final var newGroup = buildNewGroup(name, members, avatarFile);
@ -143,16 +149,16 @@ class GroupV2Helper {
final var groupSecretParams = newGroup.getGroupSecretParams(); final var groupSecretParams = newGroup.getGroupSecretParams();
final GroupsV2AuthorizationString groupAuthForToday; final GroupsV2AuthorizationString groupAuthForToday;
final DecryptedGroup decryptedGroup; final DecryptedGroupResponse response;
try { try {
groupAuthForToday = getGroupAuthForToday(groupSecretParams); groupAuthForToday = getGroupAuthForToday(groupSecretParams);
dependencies.getGroupsV2Api().putNewGroup(newGroup, groupAuthForToday); dependencies.getGroupsV2Api().putNewGroup(newGroup, groupAuthForToday);
decryptedGroup = dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupAuthForToday); response = dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupAuthForToday);
} catch (IOException | VerificationFailedException | InvalidGroupStateException e) { } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
logger.warn("Failed to create V2 group: {}", e.getMessage()); logger.warn("Failed to create V2 group: {}", e.getMessage());
return null; return null;
} }
if (decryptedGroup == null) { if (response == null) {
logger.warn("Failed to create V2 group, unknown error!"); logger.warn("Failed to create V2 group, unknown error!");
return null; return null;
} }
@ -161,7 +167,7 @@ class GroupV2Helper {
final var masterKey = groupSecretParams.getMasterKey(); final var masterKey = groupSecretParams.getMasterKey();
var g = new GroupInfoV2(groupId, masterKey, context.getAccount().getRecipientResolver()); var g = new GroupInfoV2(groupId, masterKey, context.getAccount().getRecipientResolver());
return new Pair<>(g, decryptedGroup); return new Pair<>(g, response);
} }
private GroupsV2Operations.NewGroup buildNewGroup( private GroupsV2Operations.NewGroup buildNewGroup(
@ -195,7 +201,7 @@ class GroupV2Helper {
0); 0);
} }
Pair<DecryptedGroup, GroupChange> updateGroup( Pair<DecryptedGroup, GroupChangeResponse> updateGroup(
GroupInfoV2 groupInfoV2, String name, String description, byte[] avatarFile GroupInfoV2 groupInfoV2, String name, String description, byte[] avatarFile
) throws IOException { ) throws IOException {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
@ -218,7 +224,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> addMembers( Pair<DecryptedGroup, GroupChangeResponse> addMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> newMembers GroupInfoV2 groupInfoV2, Set<RecipientId> newMembers
) throws IOException { ) throws IOException {
GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -244,7 +250,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> leaveGroup( Pair<DecryptedGroup, GroupChangeResponse> leaveGroup(
GroupInfoV2 groupInfoV2, Set<RecipientId> membersToMakeAdmin GroupInfoV2 groupInfoV2, Set<RecipientId> membersToMakeAdmin
) throws IOException { ) throws IOException {
var pendingMembersList = groupInfoV2.getGroup().pendingMembers; var pendingMembersList = groupInfoV2.getGroup().pendingMembers;
@ -264,7 +270,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci, adminUuids)); return commitChange(groupInfoV2, groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci, adminUuids));
} }
Pair<DecryptedGroup, GroupChange> removeMembers( Pair<DecryptedGroup, GroupChangeResponse> removeMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> members GroupInfoV2 groupInfoV2, Set<RecipientId> members
) throws IOException { ) throws IOException {
final var memberUuids = members.stream() final var memberUuids = members.stream()
@ -276,7 +282,7 @@ class GroupV2Helper {
return ejectMembers(groupInfoV2, memberUuids); return ejectMembers(groupInfoV2, memberUuids);
} }
Pair<DecryptedGroup, GroupChange> approveJoinRequestMembers( Pair<DecryptedGroup, GroupChangeResponse> approveJoinRequestMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> members GroupInfoV2 groupInfoV2, Set<RecipientId> members
) throws IOException { ) throws IOException {
final var memberUuids = members.stream() final var memberUuids = members.stream()
@ -287,7 +293,7 @@ class GroupV2Helper {
return approveJoinRequest(groupInfoV2, memberUuids); return approveJoinRequest(groupInfoV2, memberUuids);
} }
Pair<DecryptedGroup, GroupChange> refuseJoinRequestMembers( Pair<DecryptedGroup, GroupChangeResponse> refuseJoinRequestMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> members GroupInfoV2 groupInfoV2, Set<RecipientId> members
) throws IOException { ) throws IOException {
final var memberUuids = members.stream() final var memberUuids = members.stream()
@ -297,7 +303,7 @@ class GroupV2Helper {
return refuseJoinRequest(groupInfoV2, memberUuids); return refuseJoinRequest(groupInfoV2, memberUuids);
} }
Pair<DecryptedGroup, GroupChange> revokeInvitedMembers( Pair<DecryptedGroup, GroupChangeResponse> revokeInvitedMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> members GroupInfoV2 groupInfoV2, Set<RecipientId> members
) throws IOException { ) throws IOException {
var pendingMembersList = groupInfoV2.getGroup().pendingMembers; var pendingMembersList = groupInfoV2.getGroup().pendingMembers;
@ -311,7 +317,7 @@ class GroupV2Helper {
return revokeInvites(groupInfoV2, memberUuids); return revokeInvites(groupInfoV2, memberUuids);
} }
Pair<DecryptedGroup, GroupChange> banMembers( Pair<DecryptedGroup, GroupChangeResponse> banMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> block GroupInfoV2 groupInfoV2, Set<RecipientId> block
) throws IOException { ) throws IOException {
GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -329,7 +335,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> unbanMembers( Pair<DecryptedGroup, GroupChangeResponse> unbanMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> block GroupInfoV2 groupInfoV2, Set<RecipientId> block
) throws IOException { ) throws IOException {
GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -345,14 +351,14 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> resetGroupLinkPassword(GroupInfoV2 groupInfoV2) throws IOException { Pair<DecryptedGroup, GroupChangeResponse> resetGroupLinkPassword(GroupInfoV2 groupInfoV2) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
final var newGroupLinkPassword = GroupLinkPassword.createNew().serialize(); final var newGroupLinkPassword = GroupLinkPassword.createNew().serialize();
final var change = groupOperations.createModifyGroupLinkPasswordChange(newGroupLinkPassword); final var change = groupOperations.createModifyGroupLinkPasswordChange(newGroupLinkPassword);
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> setGroupLinkState( Pair<DecryptedGroup, GroupChangeResponse> setGroupLinkState(
GroupInfoV2 groupInfoV2, GroupLinkState state GroupInfoV2 groupInfoV2, GroupLinkState state
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -367,7 +373,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> setEditDetailsPermission( Pair<DecryptedGroup, GroupChangeResponse> setEditDetailsPermission(
GroupInfoV2 groupInfoV2, GroupPermission permission GroupInfoV2 groupInfoV2, GroupPermission permission
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -377,7 +383,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> setAddMemberPermission( Pair<DecryptedGroup, GroupChangeResponse> setAddMemberPermission(
GroupInfoV2 groupInfoV2, GroupPermission permission GroupInfoV2 groupInfoV2, GroupPermission permission
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -387,7 +393,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> updateSelfProfileKey(GroupInfoV2 groupInfoV2) throws IOException { Pair<DecryptedGroup, GroupChangeResponse> updateSelfProfileKey(GroupInfoV2 groupInfoV2) throws IOException {
Optional<DecryptedMember> selfInGroup = groupInfoV2.getGroup() == null Optional<DecryptedMember> selfInGroup = groupInfoV2.getGroup() == null
? Optional.empty() ? Optional.empty()
: DecryptedGroupUtil.findMemberByAci(groupInfoV2.getGroup().members, getSelfAci()); : DecryptedGroupUtil.findMemberByAci(groupInfoV2.getGroup().members, getSelfAci());
@ -417,7 +423,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
GroupChange joinGroup( GroupChangeResponse joinGroup(
GroupMasterKey groupMasterKey, GroupMasterKey groupMasterKey,
GroupLinkPassword groupLinkPassword, GroupLinkPassword groupLinkPassword,
DecryptedGroupJoinInfo decryptedGroupJoinInfo DecryptedGroupJoinInfo decryptedGroupJoinInfo
@ -444,7 +450,7 @@ class GroupV2Helper {
return commitChange(groupSecretParams, decryptedGroupJoinInfo.revision, change, groupLinkPassword); return commitChange(groupSecretParams, decryptedGroupJoinInfo.revision, change, groupLinkPassword);
} }
Pair<DecryptedGroup, GroupChange> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException { Pair<DecryptedGroup, GroupChangeResponse> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
final var selfRecipientId = context.getAccount().getSelfRecipientId(); final var selfRecipientId = context.getAccount().getSelfRecipientId();
@ -461,7 +467,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> setMemberAdmin( Pair<DecryptedGroup, GroupChangeResponse> setMemberAdmin(
GroupInfoV2 groupInfoV2, RecipientId recipientId, boolean admin GroupInfoV2 groupInfoV2, RecipientId recipientId, boolean admin
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -475,7 +481,7 @@ class GroupV2Helper {
} }
} }
Pair<DecryptedGroup, GroupChange> setMessageExpirationTimer( Pair<DecryptedGroup, GroupChangeResponse> setMessageExpirationTimer(
GroupInfoV2 groupInfoV2, int messageExpirationTimer GroupInfoV2 groupInfoV2, int messageExpirationTimer
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -483,7 +489,7 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change); return commitChange(groupInfoV2, change);
} }
Pair<DecryptedGroup, GroupChange> setIsAnnouncementGroup( Pair<DecryptedGroup, GroupChangeResponse> setIsAnnouncementGroup(
GroupInfoV2 groupInfoV2, boolean isAnnouncementGroup GroupInfoV2 groupInfoV2, boolean isAnnouncementGroup
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -511,7 +517,7 @@ class GroupV2Helper {
return dependencies.getGroupsV2Operations().forGroup(groupSecretParams); return dependencies.getGroupsV2Operations().forGroup(groupSecretParams);
} }
private Pair<DecryptedGroup, GroupChange> revokeInvites( private Pair<DecryptedGroup, GroupChangeResponse> revokeInvites(
GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
@ -525,28 +531,28 @@ class GroupV2Helper {
return commitChange(groupInfoV2, groupOperations.createRemoveInvitationChange(uuidCipherTexts)); return commitChange(groupInfoV2, groupOperations.createRemoveInvitationChange(uuidCipherTexts));
} }
private Pair<DecryptedGroup, GroupChange> approveJoinRequest( private Pair<DecryptedGroup, GroupChangeResponse> approveJoinRequest(
GroupInfoV2 groupInfoV2, Set<UUID> uuids GroupInfoV2 groupInfoV2, Set<UUID> uuids
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, groupOperations.createApproveGroupJoinRequest(uuids)); return commitChange(groupInfoV2, groupOperations.createApproveGroupJoinRequest(uuids));
} }
private Pair<DecryptedGroup, GroupChange> refuseJoinRequest( private Pair<DecryptedGroup, GroupChangeResponse> refuseJoinRequest(
GroupInfoV2 groupInfoV2, Set<ServiceId> serviceIds GroupInfoV2 groupInfoV2, Set<ServiceId> serviceIds
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, groupOperations.createRefuseGroupJoinRequest(serviceIds, false, List.of())); return commitChange(groupInfoV2, groupOperations.createRefuseGroupJoinRequest(serviceIds, false, List.of()));
} }
private Pair<DecryptedGroup, GroupChange> ejectMembers( private Pair<DecryptedGroup, GroupChangeResponse> ejectMembers(
GroupInfoV2 groupInfoV2, Set<ACI> members GroupInfoV2 groupInfoV2, Set<ACI> members
) throws IOException { ) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(members, false, List.of())); return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(members, false, List.of()));
} }
private Pair<DecryptedGroup, GroupChange> commitChange( private Pair<DecryptedGroup, GroupChangeResponse> commitChange(
GroupInfoV2 groupInfoV2, GroupChange.Actions.Builder change GroupInfoV2 groupInfoV2, GroupChange.Actions.Builder change
) throws IOException { ) throws IOException {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
@ -567,10 +573,12 @@ class GroupV2Helper {
var signedGroupChange = dependencies.getGroupsV2Api() var signedGroupChange = dependencies.getGroupsV2Api()
.patchGroup(changeActions, getGroupAuthForToday(groupSecretParams), Optional.empty()); .patchGroup(changeActions, getGroupAuthForToday(groupSecretParams), Optional.empty());
groupInfoV2.setGroup(decryptedGroupState);
return new Pair<>(decryptedGroupState, signedGroupChange); return new Pair<>(decryptedGroupState, signedGroupChange);
} }
private GroupChange commitChange( private GroupChangeResponse commitChange(
GroupSecretParams groupSecretParams, GroupSecretParams groupSecretParams,
int currentRevision, int currentRevision,
GroupChange.Actions.Builder change, GroupChange.Actions.Builder change,

View file

@ -16,13 +16,14 @@ import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.PaymentUtils; import org.asamk.signal.manager.util.PaymentUtils;
import org.asamk.signal.manager.util.ProfileUtils; import org.asamk.signal.manager.util.ProfileUtils;
import org.asamk.signal.manager.util.Utils; import org.asamk.signal.manager.util.Utils;
import org.jetbrains.annotations.Nullable;
import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.profiles.AvatarUploadParams; import org.whispersystems.signalservice.api.profiles.AvatarUploadParams;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
@ -387,7 +388,7 @@ public final class ProfileHelper {
private Single<ProfileAndCredential> retrieveProfile( private Single<ProfileAndCredential> retrieveProfile(
SignalServiceAddress address, SignalServiceAddress address,
Optional<ProfileKey> profileKey, Optional<ProfileKey> profileKey,
Optional<UnidentifiedAccess> unidentifiedAccess, @Nullable SealedSenderAccess unidentifiedAccess,
SignalServiceProfile.RequestType requestType SignalServiceProfile.RequestType requestType
) { ) {
final var profileService = dependencies.getProfileService(); final var profileService = dependencies.getProfileService();
@ -450,13 +451,7 @@ public final class ProfileHelper {
} }
} }
private Optional<UnidentifiedAccess> getUnidentifiedAccess(RecipientId recipientId) { private @Nullable SealedSenderAccess getUnidentifiedAccess(RecipientId recipientId) {
var unidentifiedAccess = context.getUnidentifiedAccessHelper().getAccessFor(recipientId, true); return context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId, true);
if (unidentifiedAccess.isPresent()) {
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
}
return Optional.empty();
} }
} }

View file

@ -13,6 +13,7 @@ import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry; import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry;
import org.jetbrains.annotations.Nullable;
import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.InvalidRegistrationIdException; import org.signal.libsignal.protocol.InvalidRegistrationIdException;
import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.NoSessionException;
@ -22,9 +23,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.ContentHint; 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.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; 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.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
@ -199,7 +201,7 @@ public class SendHelper {
return SendMessageResult.success(account.getSelfAddress(), List.of(), false, false, 0, Optional.empty()); return SendMessageResult.success(account.getSelfAddress(), List.of(), false, false, 0, Optional.empty());
} }
try { try {
return messageSender.sendSyncMessage(message, context.getUnidentifiedAccessHelper().getAccessForSync()); return messageSender.sendSyncMessage(message);
} catch (UnregisteredUserException e) { } catch (UnregisteredUserException e) {
var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId()); var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId());
return SendMessageResult.unregisteredFailure(address); return SendMessageResult.unregisteredFailure(address);
@ -380,10 +382,11 @@ public class SendHelper {
() -> false, () -> false,
urgent, urgent,
editTargetTimestamp.get()); editTargetTimestamp.get());
final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupDataMessage( final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, groupSendEndorsements, isRecipientUpdate) -> messageSender.sendGroupDataMessage(
distId, distId,
recipients, recipients,
unidentifiedAccess, unidentifiedAccess,
groupSendEndorsements,
isRecipientUpdate, isRecipientUpdate,
contentHint, contentHint,
message, message,
@ -436,9 +439,11 @@ public class SendHelper {
unidentifiedAccess, unidentifiedAccess,
message, message,
() -> false), () -> false),
(distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupTyping(distId, (distId, recipients, unidentifiedAccess, groupSendEndorsements, isRecipientUpdate) -> messageSender.sendGroupTyping(
distId,
recipients, recipients,
unidentifiedAccess, unidentifiedAccess,
groupSendEndorsements,
message), message),
recipientIds, recipientIds,
distributionId); distributionId);
@ -526,8 +531,8 @@ public class SendHelper {
final var senderKeyTargets = new HashSet<RecipientId>(); final var senderKeyTargets = new HashSet<RecipientId>();
final var recipientList = new ArrayList<>(recipientIds); final var recipientList = new ArrayList<>(recipientIds);
for (final var recipientId : recipientList) { for (final var recipientId : recipientList) {
final var access = context.getUnidentifiedAccessHelper().getAccessFor(recipientId); final var access = context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId);
if (access.isEmpty() || access.get().getTargetUnidentifiedAccess().isEmpty()) { if (access != null) {
continue; continue;
} }
@ -562,7 +567,8 @@ public class SendHelper {
final var addresses = recipientIdList.stream() final var addresses = recipientIdList.stream()
.map(context.getRecipientHelper()::resolveSignalServiceAddress) .map(context.getRecipientHelper()::resolveSignalServiceAddress)
.toList(); .toList();
final var unidentifiedAccesses = context.getUnidentifiedAccessHelper().getAccessFor(recipientIdList); final var unidentifiedAccesses = context.getUnidentifiedAccessHelper()
.getSealedSenderAccessFor(recipientIdList);
try { try {
final var results = sender.send(addresses, unidentifiedAccesses, isRecipientUpdate); final var results = sender.send(addresses, unidentifiedAccesses, isRecipientUpdate);
@ -601,15 +607,14 @@ public class SendHelper {
List<UnidentifiedAccess> unidentifiedAccesses = context.getUnidentifiedAccessHelper() List<UnidentifiedAccess> unidentifiedAccesses = context.getUnidentifiedAccessHelper()
.getAccessFor(recipientIdList) .getAccessFor(recipientIdList)
.stream() .stream()
.map(Optional::get)
.map(UnidentifiedAccessPair::getTargetUnidentifiedAccess)
.map(Optional::get)
.toList(); .toList();
final GroupSendEndorsements groupSendEndorsements = null;//TODO
try { try {
List<SendMessageResult> results = sender.send(distributionId, List<SendMessageResult> results = sender.send(distributionId,
addresses, addresses,
unidentifiedAccesses, unidentifiedAccesses,
groupSendEndorsements,
isRecipientUpdate); isRecipientUpdate);
final var successCount = results.stream().filter(SendMessageResult::isSuccess).count(); final var successCount = results.stream().filter(SendMessageResult::isSuccess).count();
@ -684,7 +689,7 @@ public class SendHelper {
try { try {
return s.send(messageSender, return s.send(messageSender,
address, address,
context.getUnidentifiedAccessHelper().getAccessFor(recipientId), context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId),
includePniSignature); includePniSignature);
} catch (UnregisteredUserException e) { } catch (UnregisteredUserException e) {
final RecipientId newRecipientId; final RecipientId newRecipientId;
@ -696,7 +701,7 @@ public class SendHelper {
address = context.getRecipientHelper().resolveSignalServiceAddress(newRecipientId); address = context.getRecipientHelper().resolveSignalServiceAddress(newRecipientId);
return s.send(messageSender, return s.send(messageSender,
address, address,
context.getUnidentifiedAccessHelper().getAccessFor(newRecipientId), context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(newRecipientId),
includePniSignature); includePniSignature);
} }
} catch (UnregisteredUserException e) { } catch (UnregisteredUserException e) {
@ -772,7 +777,7 @@ public class SendHelper {
SendMessageResult send( SendMessageResult send(
SignalServiceMessageSender messageSender, SignalServiceMessageSender messageSender,
SignalServiceAddress address, SignalServiceAddress address,
Optional<UnidentifiedAccessPair> unidentifiedAccess, @Nullable SealedSenderAccess unidentifiedAccess,
boolean includePniSignature boolean includePniSignature
) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; ) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
} }
@ -783,6 +788,7 @@ public class SendHelper {
DistributionId distributionId, DistributionId distributionId,
List<SignalServiceAddress> recipients, List<SignalServiceAddress> recipients,
List<UnidentifiedAccess> unidentifiedAccess, List<UnidentifiedAccess> unidentifiedAccess,
GroupSendEndorsements groupSendEndorsements,
boolean isRecipientUpdate boolean isRecipientUpdate
) throws IOException, UntrustedIdentityException, NoSessionException, InvalidKeyException, InvalidRegistrationIdException; ) throws IOException, UntrustedIdentityException, NoSessionException, InvalidKeyException, InvalidRegistrationIdException;
} }
@ -791,7 +797,7 @@ public class SendHelper {
List<SendMessageResult> send( List<SendMessageResult> send(
List<SignalServiceAddress> recipients, List<SignalServiceAddress> recipients,
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess, List<SealedSenderAccess> unidentifiedAccess,
boolean isRecipientUpdate boolean isRecipientUpdate
) throws IOException, UntrustedIdentityException; ) throws IOException, UntrustedIdentityException;
} }

View file

@ -5,17 +5,17 @@ import org.asamk.signal.manager.api.Profile;
import org.asamk.signal.manager.internal.SignalDependencies; import org.asamk.signal.manager.internal.SignalDependencies;
import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.jetbrains.annotations.Nullable;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
import org.signal.libsignal.metadata.certificate.SenderCertificate; import org.signal.libsignal.metadata.certificate.SenderCertificate;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class UnidentifiedAccessHelper { public class UnidentifiedAccessHelper {
@ -42,57 +42,49 @@ public class UnidentifiedAccessHelper {
senderCertificate = null; senderCertificate = null;
} }
public List<Optional<UnidentifiedAccessPair>> getAccessFor(List<RecipientId> recipients) { public List<SealedSenderAccess> getSealedSenderAccessFor(List<RecipientId> recipients) {
return recipients.stream().map(this::getAccessFor).map(SealedSenderAccess::forIndividual).toList();
}
public @Nullable SealedSenderAccess getSealedSenderAccessFor(RecipientId recipient) {
return getSealedSenderAccessFor(recipient, false);
}
public @Nullable SealedSenderAccess getSealedSenderAccessFor(RecipientId recipient, boolean noRefresh) {
return SealedSenderAccess.forIndividual(getAccessFor(recipient, noRefresh));
}
public List<UnidentifiedAccess> getAccessFor(List<RecipientId> recipients) {
return recipients.stream().map(this::getAccessFor).toList(); return recipients.stream().map(this::getAccessFor).toList();
} }
public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) { private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipient) {
return getAccessFor(recipient, false); return getAccessFor(recipient, false);
} }
public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipientId, boolean noRefresh) { private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipientId, boolean noRefresh) {
var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipientId, noRefresh); var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipientId, noRefresh);
if (recipientUnidentifiedAccessKey == null) { if (recipientUnidentifiedAccessKey == null) {
logger.trace("Unidentified access not available for {}", recipientId); logger.trace("Unidentified access not available for {}", recipientId);
return Optional.empty(); return null;
} }
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh); var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
if (selfUnidentifiedAccessKey == null) { if (selfUnidentifiedAccessKey == null) {
logger.trace("Unidentified access not available for self"); logger.trace("Unidentified access not available for self");
return Optional.empty(); return null;
} }
var senderCertificate = getSenderCertificateFor(recipientId); var senderCertificate = getSenderCertificateFor(recipientId);
if (senderCertificate == null) { if (senderCertificate == null) {
logger.trace("Unidentified access not available due to missing sender certificate"); logger.trace("Unidentified access not available due to missing sender certificate");
return Optional.empty(); return null;
} }
try { try {
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey, return new UnidentifiedAccess(recipientUnidentifiedAccessKey, senderCertificate, false);
senderCertificate,
false), new UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate, false)));
} catch (InvalidCertificateException e) { } catch (InvalidCertificateException e) {
return Optional.empty(); return null;
}
}
public Optional<UnidentifiedAccessPair> getAccessForSync() {
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(false);
var selfUnidentifiedAccessCertificate = getSenderCertificate();
if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
return Optional.empty();
}
try {
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey,
selfUnidentifiedAccessCertificate,
false),
new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate, false)));
} catch (InvalidCertificateException e) {
return Optional.empty();
} }
} }
@ -121,7 +113,7 @@ public class UnidentifiedAccessHelper {
privacySenderCertificate = new SenderCertificate(certificate); privacySenderCertificate = new SenderCertificate(certificate);
return certificate; return certificate;
} catch (IOException | InvalidCertificateException e) { } catch (IOException | InvalidCertificateException e) {
logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage()); logger.warn("Failed to get sender certificate (pnp), ignoring: {}", e.getMessage());
return null; return null;
} }
} }

View file

@ -15,7 +15,7 @@ dependencyResolutionManagement {
library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j")
library("logback", "ch.qos.logback", "logback-classic").version("1.5.6") library("logback", "ch.qos.logback", "logback-classic").version("1.5.6")
library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_104") library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_105")
library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.0.0") library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.0.0")
library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0")
library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2")

View file

@ -8,7 +8,7 @@ public class BaseConfig {
public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion();
static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT"))
.orElse("Signal-Android/7.9.0"); .orElse("Signal-Android/7.12.1");
static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null
? "signal-cli" ? "signal-cli"
: PROJECT_NAME + "/" + PROJECT_VERSION; : PROJECT_NAME + "/" + PROJECT_VERSION;