[
+{
+ "name":"[B"
+},
{
"name":"[Z"
},
+{
+ "name":"[[B"
+},
{
"name":"com.sun.security.auth.module.UnixSystem",
"fields":[{"name":"gid"}, {"name":"groups"}, {"name":"uid"}, {"name":"username"}]
static Option from(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException.Option option) {
return switch (option) {
- case RECAPTCHA -> CAPTCHA;
case CAPTCHA -> CAPTCHA;
case PUSH_CHALLENGE -> PUSH_CHALLENGE;
};
public static final long UNREGISTERED_LIFESPAN = TimeUnit.DAYS.toMillis(30);
public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) {
- final var giftBadges = !isPrimaryDevice;
- final var pni = !isPrimaryDevice;
- final var paymentActivation = !isPrimaryDevice;
final var deleteSync = !isPrimaryDevice;
- return new AccountAttributes.Capabilities(true,
- true,
- true,
- true,
- true,
- giftBadges,
- pni,
- paymentActivation,
- deleteSync);
+ return new AccountAttributes.Capabilities(true, deleteSync);
}
public static ServiceEnvironmentConfig getServiceEnvironmentConfig(
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
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.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ReceivedGroupSendEndorsements;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
}
if (group == null) {
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);
}
} catch (NotAGroupMemberException ignored) {
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(
String name, Set<RecipientId> members, String avatarFile
) throws IOException, AttachmentInvalidException {
final var gv2 = gv2Pair.first();
final var decryptedGroup = gv2Pair.second();
- gv2.setGroup(decryptedGroup);
+ gv2.setGroup(handleDecryptedGroupResponse(gv2, decryptedGroup));
gv2.setProfileSharingEnabled(true);
if (avatarBytes != null) {
context.getAvatarStore()
var group = getGroupForUpdating(groupId);
if (group instanceof GroupInfoV2 groupInfoV2) {
- Pair<DecryptedGroup, GroupChange> groupChangePair;
+ Pair<DecryptedGroup, GroupChangeResponse> groupChangePair;
try {
groupChangePair = context.getGroupV2Helper().updateSelfProfileKey(groupInfoV2);
} catch (ConflictException e) {
groupChangePair = context.getGroupV2Helper().updateSelfProfileKey(groupInfoV2);
}
if (groupChangePair != null) {
- sendUpdateGroupV2Message(groupInfoV2, groupChangePair.first(), groupChangePair.second());
+ sendUpdateGroupV2Message(groupInfoV2,
+ groupChangePair.first(),
+ handleGroupChangeResponse(groupInfoV2, groupChangePair.second()));
}
}
}
if (groupJoinInfo.pendingAdminApproval) {
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);
final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
groupJoinInfo.revision + 1,
- groupChange.encode());
+ changeResponse.groupChange == null ? null : changeResponse.groupChange.encode());
+ final var groupChange = handleGroupChangeResponse(group, changeResponse);
if (group.getGroup() == null) {
// Only requested member, can't send update to group members
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
DecryptedGroup decryptedGroup;
try {
- decryptedGroup = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams);
+ final var response = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams);
+ if (response == null) {
+ return;
+ }
+ decryptedGroup = handleDecryptedGroupResponse(groupInfoV2, response);
} catch (NotAGroupMemberException e) {
groupInfoV2.setPermissionDenied(true);
account.getGroupStore().updateGroup(group);
return;
}
- if (decryptedGroup == null) {
- return;
- }
-
try {
storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, decryptedGroup);
} catch (NotAGroupMemberException ignored) {
) throws NotAGroupMemberException {
final var revisionWeWereAdded = context.getGroupV2Helper().findRevisionWeWereAdded(newDecryptedGroup);
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);
final var newProfileKeys = new HashMap<RecipientId, ProfileKey>();
while (true) {
- final var page = context.getGroupV2Helper().getDecryptedGroupHistoryPage(groupSecretParams, fromRevision);
+ final var page = context.getGroupV2Helper()
+ .getDecryptedGroupHistoryPage(groupSecretParams, fromRevision, sendEndorsementsExpirationMs);
page.getChangeLogs()
.stream()
.map(DecryptedGroupChangeLog::getChange)
final var groupV2Helper = context.getGroupV2Helper();
if (group.isPendingMember(account.getSelfRecipientId())) {
var groupGroupChangePair = groupV2Helper.acceptInvite(group);
- result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ handleGroupChangeResponse(group, groupGroupChangePair.second()));
}
if (members != null) {
requestingMembers.retainAll(group.getRequestingMembers());
if (!requestingMembers.isEmpty()) {
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);
newMembers.removeAll(group.getMembers());
newMembers.removeAll(group.getRequestingMembers());
if (!newMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.addMembers(group, newMembers);
- result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ handleGroupChangeResponse(group, groupGroupChangePair.second()));
}
}
existingRemoveMembers.remove(account.getSelfRecipientId());// self can be removed with sendQuitGroupMessage
if (!existingRemoveMembers.isEmpty()) {
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);
pendingRemoveMembers.retainAll(group.getPendingMembers());
if (!pendingRemoveMembers.isEmpty()) {
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);
requestingRemoveMembers.retainAll(group.getRequestingMembers());
if (!requestingRemoveMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.refuseJoinRequestMembers(group, requestingRemoveMembers);
- result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ handleGroupChangeResponse(group, groupGroupChangePair.second()));
}
}
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
- groupGroupChangePair.second());
+ handleGroupChangeResponse(group, groupGroupChangePair.second()));
}
}
}
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
result = sendUpdateGroupV2Message(group,
groupGroupChangePair.first(),
- groupGroupChangePair.second());
+ handleGroupChangeResponse(group, groupGroupChangePair.second()));
}
}
}
newlyBannedMembers.removeAll(group.getBannedMembers());
if (!newlyBannedMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.banMembers(group, newlyBannedMembers);
- result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ handleGroupChangeResponse(group, groupGroupChangePair.second()));
}
}
existingUnbanMembers.retainAll(group.getBannedMembers());
if (!existingUnbanMembers.isEmpty()) {
var groupGroupChangePair = groupV2Helper.unbanMembers(group, existingUnbanMembers);
- result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ handleGroupChangeResponse(group, groupGroupChangePair.second()));
}
}
if (resetGroupLink) {
var groupGroupChangePair = groupV2Helper.resetGroupLinkPassword(group);
- result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ handleGroupChangeResponse(group, groupGroupChangePair.second()));
}
if (groupLinkState != null) {
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) {
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) {
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) {
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) {
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) {
context.getAvatarStore()
.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;
groupInfoV2.setGroup(groupGroupChangePair.first());
account.getGroupStore().updateGroup(groupInfoV2);
- var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().encode());
+ var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2,
+ handleGroupChangeResponse(groupInfoV2, groupGroupChangePair.second()).encode());
return sendGroupMessage(messageBuilder,
groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId()),
groupInfoV2.getDistributionId());
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.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage;
groupApiCredentials = null;
}
- DecryptedGroup getDecryptedGroup(final GroupSecretParams groupSecretParams) throws NotAGroupMemberException {
+ DecryptedGroupResponse getDecryptedGroup(final GroupSecretParams groupSecretParams) throws NotAGroupMemberException {
try {
final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
return dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupsV2AuthorizationString);
}
logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage());
return null;
- } catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
+ } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage());
return null;
}
}
GroupHistoryPage getDecryptedGroupHistoryPage(
- final GroupSecretParams groupSecretParams, int fromRevision
+ final GroupSecretParams groupSecretParams, int fromRevision, long sendEndorsementsExpirationMs
) throws NotAGroupMemberException {
try {
final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
return dependencies.getGroupsV2Api()
- .getGroupHistoryPage(groupSecretParams, fromRevision, groupsV2AuthorizationString, false);
+ .getGroupHistoryPage(groupSecretParams,
+ fromRevision,
+ groupsV2AuthorizationString,
+ false,
+ sendEndorsementsExpirationMs);
} catch (NonSuccessfulResponseCodeException e) {
if (e.getCode() == 403) {
throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null);
}
logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage());
return null;
- } catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
+ } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage());
return null;
}
return partialDecryptedGroup.revision;
}
- Pair<GroupInfoV2, DecryptedGroup> createGroup(
+ Pair<GroupInfoV2, DecryptedGroupResponse> createGroup(
String name, Set<RecipientId> members, byte[] avatarFile
) {
final var newGroup = buildNewGroup(name, members, avatarFile);
final var groupSecretParams = newGroup.getGroupSecretParams();
final GroupsV2AuthorizationString groupAuthForToday;
- final DecryptedGroup decryptedGroup;
+ final DecryptedGroupResponse response;
try {
groupAuthForToday = getGroupAuthForToday(groupSecretParams);
dependencies.getGroupsV2Api().putNewGroup(newGroup, groupAuthForToday);
- decryptedGroup = dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupAuthForToday);
- } catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
+ response = dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupAuthForToday);
+ } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
logger.warn("Failed to create V2 group: {}", e.getMessage());
return null;
}
- if (decryptedGroup == null) {
+ if (response == null) {
logger.warn("Failed to create V2 group, unknown error!");
return null;
}
final var masterKey = groupSecretParams.getMasterKey();
var g = new GroupInfoV2(groupId, masterKey, context.getAccount().getRecipientResolver());
- return new Pair<>(g, decryptedGroup);
+ return new Pair<>(g, response);
}
private GroupsV2Operations.NewGroup buildNewGroup(
0);
}
- Pair<DecryptedGroup, GroupChange> updateGroup(
+ Pair<DecryptedGroup, GroupChangeResponse> updateGroup(
GroupInfoV2 groupInfoV2, String name, String description, byte[] avatarFile
) throws IOException {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
return commitChange(groupInfoV2, change);
}
- Pair<DecryptedGroup, GroupChange> addMembers(
+ Pair<DecryptedGroup, GroupChangeResponse> addMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> newMembers
) throws IOException {
GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, change);
}
- Pair<DecryptedGroup, GroupChange> leaveGroup(
+ Pair<DecryptedGroup, GroupChangeResponse> leaveGroup(
GroupInfoV2 groupInfoV2, Set<RecipientId> membersToMakeAdmin
) throws IOException {
var pendingMembersList = groupInfoV2.getGroup().pendingMembers;
return commitChange(groupInfoV2, groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci, adminUuids));
}
- Pair<DecryptedGroup, GroupChange> removeMembers(
+ Pair<DecryptedGroup, GroupChangeResponse> removeMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> members
) throws IOException {
final var memberUuids = members.stream()
return ejectMembers(groupInfoV2, memberUuids);
}
- Pair<DecryptedGroup, GroupChange> approveJoinRequestMembers(
+ Pair<DecryptedGroup, GroupChangeResponse> approveJoinRequestMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> members
) throws IOException {
final var memberUuids = members.stream()
return approveJoinRequest(groupInfoV2, memberUuids);
}
- Pair<DecryptedGroup, GroupChange> refuseJoinRequestMembers(
+ Pair<DecryptedGroup, GroupChangeResponse> refuseJoinRequestMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> members
) throws IOException {
final var memberUuids = members.stream()
return refuseJoinRequest(groupInfoV2, memberUuids);
}
- Pair<DecryptedGroup, GroupChange> revokeInvitedMembers(
+ Pair<DecryptedGroup, GroupChangeResponse> revokeInvitedMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> members
) throws IOException {
var pendingMembersList = groupInfoV2.getGroup().pendingMembers;
return revokeInvites(groupInfoV2, memberUuids);
}
- Pair<DecryptedGroup, GroupChange> banMembers(
+ Pair<DecryptedGroup, GroupChangeResponse> banMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> block
) throws IOException {
GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, change);
}
- Pair<DecryptedGroup, GroupChange> unbanMembers(
+ Pair<DecryptedGroup, GroupChangeResponse> unbanMembers(
GroupInfoV2 groupInfoV2, Set<RecipientId> block
) throws IOException {
GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
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 var newGroupLinkPassword = GroupLinkPassword.createNew().serialize();
final var change = groupOperations.createModifyGroupLinkPasswordChange(newGroupLinkPassword);
return commitChange(groupInfoV2, change);
}
- Pair<DecryptedGroup, GroupChange> setGroupLinkState(
+ Pair<DecryptedGroup, GroupChangeResponse> setGroupLinkState(
GroupInfoV2 groupInfoV2, GroupLinkState state
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, change);
}
- Pair<DecryptedGroup, GroupChange> setEditDetailsPermission(
+ Pair<DecryptedGroup, GroupChangeResponse> setEditDetailsPermission(
GroupInfoV2 groupInfoV2, GroupPermission permission
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, change);
}
- Pair<DecryptedGroup, GroupChange> setAddMemberPermission(
+ Pair<DecryptedGroup, GroupChangeResponse> setAddMemberPermission(
GroupInfoV2 groupInfoV2, GroupPermission permission
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
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.empty()
: DecryptedGroupUtil.findMemberByAci(groupInfoV2.getGroup().members, getSelfAci());
return commitChange(groupInfoV2, change);
}
- GroupChange joinGroup(
+ GroupChangeResponse joinGroup(
GroupMasterKey groupMasterKey,
GroupLinkPassword groupLinkPassword,
DecryptedGroupJoinInfo decryptedGroupJoinInfo
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 var selfRecipientId = context.getAccount().getSelfRecipientId();
return commitChange(groupInfoV2, change);
}
- Pair<DecryptedGroup, GroupChange> setMemberAdmin(
+ Pair<DecryptedGroup, GroupChangeResponse> setMemberAdmin(
GroupInfoV2 groupInfoV2, RecipientId recipientId, boolean admin
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
}
}
- Pair<DecryptedGroup, GroupChange> setMessageExpirationTimer(
+ Pair<DecryptedGroup, GroupChangeResponse> setMessageExpirationTimer(
GroupInfoV2 groupInfoV2, int messageExpirationTimer
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, change);
}
- Pair<DecryptedGroup, GroupChange> setIsAnnouncementGroup(
+ Pair<DecryptedGroup, GroupChangeResponse> setIsAnnouncementGroup(
GroupInfoV2 groupInfoV2, boolean isAnnouncementGroup
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return dependencies.getGroupsV2Operations().forGroup(groupSecretParams);
}
- private Pair<DecryptedGroup, GroupChange> revokeInvites(
+ private Pair<DecryptedGroup, GroupChangeResponse> revokeInvites(
GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, groupOperations.createRemoveInvitationChange(uuidCipherTexts));
}
- private Pair<DecryptedGroup, GroupChange> approveJoinRequest(
+ private Pair<DecryptedGroup, GroupChangeResponse> approveJoinRequest(
GroupInfoV2 groupInfoV2, Set<UUID> uuids
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, groupOperations.createApproveGroupJoinRequest(uuids));
}
- private Pair<DecryptedGroup, GroupChange> refuseJoinRequest(
+ private Pair<DecryptedGroup, GroupChangeResponse> refuseJoinRequest(
GroupInfoV2 groupInfoV2, Set<ServiceId> serviceIds
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
return commitChange(groupInfoV2, groupOperations.createRefuseGroupJoinRequest(serviceIds, false, List.of()));
}
- private Pair<DecryptedGroup, GroupChange> ejectMembers(
+ private Pair<DecryptedGroup, GroupChangeResponse> ejectMembers(
GroupInfoV2 groupInfoV2, Set<ACI> members
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
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
) throws IOException {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
var signedGroupChange = dependencies.getGroupsV2Api()
.patchGroup(changeActions, getGroupAuthForToday(groupSecretParams), Optional.empty());
+ groupInfoV2.setGroup(decryptedGroupState);
+
return new Pair<>(decryptedGroupState, signedGroupChange);
}
- private GroupChange commitChange(
+ private GroupChangeResponse commitChange(
GroupSecretParams groupSecretParams,
int currentRevision,
GroupChange.Actions.Builder change,
import org.asamk.signal.manager.util.PaymentUtils;
import org.asamk.signal.manager.util.ProfileUtils;
import org.asamk.signal.manager.util.Utils;
+import org.jetbrains.annotations.Nullable;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
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.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
private Single<ProfileAndCredential> retrieveProfile(
SignalServiceAddress address,
Optional<ProfileKey> profileKey,
- Optional<UnidentifiedAccess> unidentifiedAccess,
+ @Nullable SealedSenderAccess unidentifiedAccess,
SignalServiceProfile.RequestType requestType
) {
final var profileService = dependencies.getProfileService();
}
}
- private Optional<UnidentifiedAccess> getUnidentifiedAccess(RecipientId recipientId) {
- var unidentifiedAccess = context.getUnidentifiedAccessHelper().getAccessFor(recipientId, true);
-
- if (unidentifiedAccess.isPresent()) {
- return unidentifiedAccess.get().getTargetUnidentifiedAccess();
- }
-
- return Optional.empty();
+ private @Nullable SealedSenderAccess getUnidentifiedAccess(RecipientId recipientId) {
+ return context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId, true);
}
}
import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry;
+import org.jetbrains.annotations.Nullable;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.InvalidRegistrationIdException;
import org.signal.libsignal.protocol.NoSessionException;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
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;
return SendMessageResult.success(account.getSelfAddress(), List.of(), false, false, 0, Optional.empty());
}
try {
- return messageSender.sendSyncMessage(message, context.getUnidentifiedAccessHelper().getAccessForSync());
+ return messageSender.sendSyncMessage(message);
} catch (UnregisteredUserException e) {
var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId());
return SendMessageResult.unregisteredFailure(address);
() -> false,
urgent,
editTargetTimestamp.get());
- final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupDataMessage(
+ final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, groupSendEndorsements, isRecipientUpdate) -> messageSender.sendGroupDataMessage(
distId,
recipients,
unidentifiedAccess,
+ groupSendEndorsements,
isRecipientUpdate,
contentHint,
message,
unidentifiedAccess,
message,
() -> false),
- (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupTyping(distId,
+ (distId, recipients, unidentifiedAccess, groupSendEndorsements, isRecipientUpdate) -> messageSender.sendGroupTyping(
+ distId,
recipients,
unidentifiedAccess,
+ groupSendEndorsements,
message),
recipientIds,
distributionId);
final var senderKeyTargets = new HashSet<RecipientId>();
final var recipientList = new ArrayList<>(recipientIds);
for (final var recipientId : recipientList) {
- final var access = context.getUnidentifiedAccessHelper().getAccessFor(recipientId);
- if (access.isEmpty() || access.get().getTargetUnidentifiedAccess().isEmpty()) {
+ final var access = context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId);
+ if (access != null) {
continue;
}
final var addresses = recipientIdList.stream()
.map(context.getRecipientHelper()::resolveSignalServiceAddress)
.toList();
- final var unidentifiedAccesses = context.getUnidentifiedAccessHelper().getAccessFor(recipientIdList);
+ final var unidentifiedAccesses = context.getUnidentifiedAccessHelper()
+ .getSealedSenderAccessFor(recipientIdList);
try {
final var results = sender.send(addresses, unidentifiedAccesses, isRecipientUpdate);
List<UnidentifiedAccess> unidentifiedAccesses = context.getUnidentifiedAccessHelper()
.getAccessFor(recipientIdList)
.stream()
- .map(Optional::get)
- .map(UnidentifiedAccessPair::getTargetUnidentifiedAccess)
- .map(Optional::get)
.toList();
+ final GroupSendEndorsements groupSendEndorsements = null;//TODO
try {
List<SendMessageResult> results = sender.send(distributionId,
addresses,
unidentifiedAccesses,
+ groupSendEndorsements,
isRecipientUpdate);
final var successCount = results.stream().filter(SendMessageResult::isSuccess).count();
try {
return s.send(messageSender,
address,
- context.getUnidentifiedAccessHelper().getAccessFor(recipientId),
+ context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId),
includePniSignature);
} catch (UnregisteredUserException e) {
final RecipientId newRecipientId;
address = context.getRecipientHelper().resolveSignalServiceAddress(newRecipientId);
return s.send(messageSender,
address,
- context.getUnidentifiedAccessHelper().getAccessFor(newRecipientId),
+ context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(newRecipientId),
includePniSignature);
}
} catch (UnregisteredUserException e) {
SendMessageResult send(
SignalServiceMessageSender messageSender,
SignalServiceAddress address,
- Optional<UnidentifiedAccessPair> unidentifiedAccess,
+ @Nullable SealedSenderAccess unidentifiedAccess,
boolean includePniSignature
) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
}
DistributionId distributionId,
List<SignalServiceAddress> recipients,
List<UnidentifiedAccess> unidentifiedAccess,
+ GroupSendEndorsements groupSendEndorsements,
boolean isRecipientUpdate
) throws IOException, UntrustedIdentityException, NoSessionException, InvalidKeyException, InvalidRegistrationIdException;
}
List<SendMessageResult> send(
List<SignalServiceAddress> recipients,
- List<Optional<UnidentifiedAccessPair>> unidentifiedAccess,
+ List<SealedSenderAccess> unidentifiedAccess,
boolean isRecipientUpdate
) throws IOException, UntrustedIdentityException;
}
import org.asamk.signal.manager.internal.SignalDependencies;
import org.asamk.signal.manager.storage.SignalAccount;
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.SenderCertificate;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+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.List;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class UnidentifiedAccessHelper {
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();
}
- public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
+ private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipient) {
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);
if (recipientUnidentifiedAccessKey == null) {
logger.trace("Unidentified access not available for {}", recipientId);
- return Optional.empty();
+ return null;
}
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
if (selfUnidentifiedAccessKey == null) {
logger.trace("Unidentified access not available for self");
- return Optional.empty();
+ return null;
}
var senderCertificate = getSenderCertificateFor(recipientId);
if (senderCertificate == null) {
logger.trace("Unidentified access not available due to missing sender certificate");
- return Optional.empty();
- }
-
- try {
- return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey,
- senderCertificate,
- false), new UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate, false)));
- } catch (InvalidCertificateException e) {
- return Optional.empty();
- }
- }
-
- public Optional<UnidentifiedAccessPair> getAccessForSync() {
- var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(false);
- var selfUnidentifiedAccessCertificate = getSenderCertificate();
-
- if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
- return Optional.empty();
+ return null;
}
try {
- return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey,
- selfUnidentifiedAccessCertificate,
- false),
- new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate, false)));
+ return new UnidentifiedAccess(recipientUnidentifiedAccessKey, senderCertificate, false);
} catch (InvalidCertificateException e) {
- return Optional.empty();
+ return null;
}
}
privacySenderCertificate = new SenderCertificate(certificate);
return certificate;
} 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;
}
}
library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j")
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("hikari", "com.zaxxer", "HikariCP").version("5.1.0")
library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2")
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"))
- .orElse("Signal-Android/7.9.0");
+ .orElse("Signal-Android/7.12.1");
static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null
? "signal-cli"
: PROJECT_NAME + "/" + PROJECT_VERSION;