mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Key tables on serviceId instead of recipientId
This commit is contained in:
parent
04fa046815
commit
280d8d7f10
25 changed files with 528 additions and 514 deletions
|
@ -69,6 +69,7 @@ import org.whispersystems.signalservice.api.messages.SignalServicePreview;
|
|||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
import org.whispersystems.signalservice.api.push.ACI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
|
@ -165,11 +166,12 @@ class ManagerImpl implements Manager {
|
|||
this.notifyAll();
|
||||
}
|
||||
});
|
||||
disposable.add(account.getIdentityKeyStore().getIdentityChanges().subscribe(recipientId -> {
|
||||
logger.trace("Archiving old sessions for {}", recipientId);
|
||||
account.getAciSessionStore().archiveSessions(recipientId);
|
||||
account.getPniSessionStore().archiveSessions(recipientId);
|
||||
account.getSenderKeyStore().deleteSharedWith(recipientId);
|
||||
disposable.add(account.getIdentityKeyStore().getIdentityChanges().subscribe(serviceId -> {
|
||||
logger.trace("Archiving old sessions for {}", serviceId);
|
||||
account.getAciSessionStore().archiveSessions(serviceId);
|
||||
account.getPniSessionStore().archiveSessions(serviceId);
|
||||
account.getSenderKeyStore().deleteSharedWith(serviceId);
|
||||
final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId);
|
||||
final var profile = account.getProfileStore().getProfile(recipientId);
|
||||
if (profile != null) {
|
||||
account.getProfileStore()
|
||||
|
@ -631,7 +633,11 @@ class ManagerImpl implements Manager {
|
|||
if (recipient instanceof RecipientIdentifier.Single r) {
|
||||
try {
|
||||
final var recipientId = context.getRecipientHelper().resolveRecipient(r);
|
||||
account.getMessageSendLogStore().deleteEntryForRecipientNonGroup(targetSentTimestamp, recipientId);
|
||||
account.getMessageSendLogStore()
|
||||
.deleteEntryForRecipientNonGroup(targetSentTimestamp,
|
||||
account.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(recipientId)
|
||||
.getServiceId());
|
||||
} catch (UnregisteredRecipientException ignored) {
|
||||
}
|
||||
} else if (recipient instanceof RecipientIdentifier.Group r) {
|
||||
|
@ -689,7 +695,11 @@ class ManagerImpl implements Manager {
|
|||
} catch (UnregisteredRecipientException e) {
|
||||
continue;
|
||||
}
|
||||
account.getAciSessionStore().deleteAllSessions(recipientId);
|
||||
final var serviceId = context.getAccount()
|
||||
.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(recipientId)
|
||||
.getServiceId();
|
||||
account.getAciSessionStore().deleteAllSessions(serviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1035,13 +1045,13 @@ class ManagerImpl implements Manager {
|
|||
}
|
||||
|
||||
final var address = account.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(identityInfo.getRecipientId());
|
||||
.resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(identityInfo.getServiceId()));
|
||||
final var scannableFingerprint = context.getIdentityHelper()
|
||||
.computeSafetyNumberForScanning(identityInfo.getRecipientId(), identityInfo.getIdentityKey());
|
||||
.computeSafetyNumberForScanning(identityInfo.getServiceId(), identityInfo.getIdentityKey());
|
||||
return new Identity(address,
|
||||
identityInfo.getIdentityKey(),
|
||||
context.getIdentityHelper()
|
||||
.computeSafetyNumber(identityInfo.getRecipientId(), identityInfo.getIdentityKey()),
|
||||
.computeSafetyNumber(identityInfo.getServiceId(), identityInfo.getIdentityKey()),
|
||||
scannableFingerprint == null ? null : scannableFingerprint.getSerialized(),
|
||||
identityInfo.getTrustLevel(),
|
||||
identityInfo.getDateAddedTimestamp());
|
||||
|
@ -1049,13 +1059,15 @@ class ManagerImpl implements Manager {
|
|||
|
||||
@Override
|
||||
public List<Identity> getIdentities(RecipientIdentifier.Single recipient) {
|
||||
IdentityInfo identity;
|
||||
ServiceId serviceId;
|
||||
try {
|
||||
identity = account.getIdentityKeyStore()
|
||||
.getIdentityInfo(context.getRecipientHelper().resolveRecipient(recipient));
|
||||
serviceId = account.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(context.getRecipientHelper().resolveRecipient(recipient))
|
||||
.getServiceId();
|
||||
} catch (UnregisteredRecipientException e) {
|
||||
identity = null;
|
||||
return List.of();
|
||||
}
|
||||
final var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId);
|
||||
return identity == null ? List.of() : List.of(toIdentity(identity));
|
||||
}
|
||||
|
||||
|
|
|
@ -2,18 +2,21 @@ package org.asamk.signal.manager.actions;
|
|||
|
||||
import org.asamk.signal.manager.helper.Context;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
public class RenewSessionAction implements HandleAction {
|
||||
|
||||
private final RecipientId recipientId;
|
||||
private final ServiceId serviceId;
|
||||
|
||||
public RenewSessionAction(final RecipientId recipientId) {
|
||||
public RenewSessionAction(final RecipientId recipientId, final ServiceId serviceId) {
|
||||
this.recipientId = recipientId;
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Context context) throws Throwable {
|
||||
context.getAccount().getAciSessionStore().archiveSessions(recipientId);
|
||||
context.getAccount().getAciSessionStore().archiveSessions(serviceId);
|
||||
if (!recipientId.equals(context.getAccount().getSelfRecipientId())) {
|
||||
context.getSendHelper().sendNullMessage(recipientId);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.signal.libsignal.metadata.ProtocolException;
|
|||
import org.signal.libsignal.protocol.message.CiphertextMessage;
|
||||
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
|
||||
import java.util.Optional;
|
||||
|
@ -14,22 +15,25 @@ import java.util.Optional;
|
|||
public class SendRetryMessageRequestAction implements HandleAction {
|
||||
|
||||
private final RecipientId recipientId;
|
||||
private final ServiceId serviceId;
|
||||
private final ProtocolException protocolException;
|
||||
private final SignalServiceEnvelope envelope;
|
||||
|
||||
public SendRetryMessageRequestAction(
|
||||
final RecipientId recipientId,
|
||||
final ServiceId serviceId,
|
||||
final ProtocolException protocolException,
|
||||
final SignalServiceEnvelope envelope
|
||||
) {
|
||||
this.recipientId = recipientId;
|
||||
this.serviceId = serviceId;
|
||||
this.protocolException = protocolException;
|
||||
this.envelope = envelope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Context context) throws Throwable {
|
||||
context.getAccount().getAciSessionStore().archiveSessions(recipientId);
|
||||
context.getAccount().getAciSessionStore().archiveSessions(serviceId);
|
||||
|
||||
int senderDevice = protocolException.getSenderDevice();
|
||||
Optional<GroupId> groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion(
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.asamk.signal.manager.helper;
|
|||
|
||||
import org.asamk.signal.manager.api.TrustLevel;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.util.Utils;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
|
@ -12,7 +13,7 @@ import org.signal.libsignal.protocol.fingerprint.ScannableFingerprint;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
@ -33,20 +34,23 @@ public class IdentityHelper {
|
|||
}
|
||||
|
||||
public boolean trustIdentityVerified(RecipientId recipientId, byte[] fingerprint) {
|
||||
return trustIdentity(recipientId,
|
||||
final var serviceId = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId).getServiceId();
|
||||
return trustIdentity(serviceId,
|
||||
identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
|
||||
TrustLevel.TRUSTED_VERIFIED);
|
||||
}
|
||||
|
||||
public boolean trustIdentityVerifiedSafetyNumber(RecipientId recipientId, String safetyNumber) {
|
||||
return trustIdentity(recipientId,
|
||||
identityKey -> safetyNumber.equals(computeSafetyNumber(recipientId, identityKey)),
|
||||
final var serviceId = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId).getServiceId();
|
||||
return trustIdentity(serviceId,
|
||||
identityKey -> safetyNumber.equals(computeSafetyNumber(serviceId, identityKey)),
|
||||
TrustLevel.TRUSTED_VERIFIED);
|
||||
}
|
||||
|
||||
public boolean trustIdentityVerifiedSafetyNumber(RecipientId recipientId, byte[] safetyNumber) {
|
||||
return trustIdentity(recipientId, identityKey -> {
|
||||
final var fingerprint = computeSafetyNumberForScanning(recipientId, identityKey);
|
||||
final var serviceId = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId).getServiceId();
|
||||
return trustIdentity(serviceId, identityKey -> {
|
||||
final var fingerprint = computeSafetyNumberForScanning(serviceId, identityKey);
|
||||
try {
|
||||
return fingerprint != null && fingerprint.compareTo(safetyNumber);
|
||||
} catch (FingerprintVersionMismatchException | FingerprintParsingException e) {
|
||||
|
@ -56,35 +60,39 @@ public class IdentityHelper {
|
|||
}
|
||||
|
||||
public boolean trustIdentityAllKeys(RecipientId recipientId) {
|
||||
return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
|
||||
final var serviceId = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId).getServiceId();
|
||||
return trustIdentity(serviceId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
|
||||
}
|
||||
|
||||
public String computeSafetyNumber(RecipientId recipientId, IdentityKey theirIdentityKey) {
|
||||
var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
|
||||
final Fingerprint fingerprint = computeSafetyNumberFingerprint(address, theirIdentityKey);
|
||||
public String computeSafetyNumber(ServiceId serviceId, IdentityKey theirIdentityKey) {
|
||||
final Fingerprint fingerprint = computeSafetyNumberFingerprint(serviceId, theirIdentityKey);
|
||||
return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
|
||||
}
|
||||
|
||||
public ScannableFingerprint computeSafetyNumberForScanning(RecipientId recipientId, IdentityKey theirIdentityKey) {
|
||||
var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
|
||||
final Fingerprint fingerprint = computeSafetyNumberFingerprint(address, theirIdentityKey);
|
||||
public ScannableFingerprint computeSafetyNumberForScanning(ServiceId serviceId, IdentityKey theirIdentityKey) {
|
||||
final Fingerprint fingerprint = computeSafetyNumberFingerprint(serviceId, theirIdentityKey);
|
||||
return fingerprint == null ? null : fingerprint.getScannableFingerprint();
|
||||
}
|
||||
|
||||
private Fingerprint computeSafetyNumberFingerprint(
|
||||
final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey
|
||||
final ServiceId serviceId, final IdentityKey theirIdentityKey
|
||||
) {
|
||||
final var address = account.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(serviceId));
|
||||
|
||||
return Utils.computeSafetyNumber(capabilities.isUuid(),
|
||||
account.getSelfAddress(),
|
||||
account.getSelfRecipientAddress(),
|
||||
account.getAciIdentityKeyPair().getPublicKey(),
|
||||
theirAddress,
|
||||
address.getServiceId().equals(serviceId)
|
||||
? address
|
||||
: new RecipientAddress(serviceId.uuid(), address.number().orElse(null)),
|
||||
theirIdentityKey);
|
||||
}
|
||||
|
||||
private boolean trustIdentity(
|
||||
RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
|
||||
ServiceId serviceId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
|
||||
) {
|
||||
var identity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
|
||||
var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId);
|
||||
if (identity == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -93,9 +101,11 @@ public class IdentityHelper {
|
|||
return false;
|
||||
}
|
||||
|
||||
account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
|
||||
account.getIdentityKeyStore().setIdentityTrustLevel(serviceId, identity.getIdentityKey(), trustLevel);
|
||||
try {
|
||||
var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
|
||||
final var address = account.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(serviceId))
|
||||
.toSignalServiceAddress();
|
||||
context.getSyncHelper().sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to send verification sync message: {}", e.getMessage());
|
||||
|
@ -105,11 +115,13 @@ public class IdentityHelper {
|
|||
}
|
||||
|
||||
public void handleIdentityFailure(
|
||||
final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure
|
||||
final RecipientId recipientId,
|
||||
final ServiceId serviceId,
|
||||
final SendMessageResult.IdentityFailure identityFailure
|
||||
) {
|
||||
final var identityKey = identityFailure.getIdentityKey();
|
||||
if (identityKey != null) {
|
||||
account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
|
||||
account.getIdentityKeyStore().saveIdentity(serviceId, identityKey);
|
||||
} else {
|
||||
// Retrieve profile to get the current identity key from the server
|
||||
context.getProfileHelper().refreshRecipientProfile(recipientId);
|
||||
|
|
|
@ -42,7 +42,6 @@ import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
|
|||
import org.signal.libsignal.metadata.ProtocolNoSessionException;
|
||||
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
||||
import org.signal.libsignal.metadata.SelfSendException;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
|
@ -57,6 +56,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage
|
|||
import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -137,16 +137,17 @@ public final class IncomingMessageHandler {
|
|||
} else {
|
||||
final var senderProfile = context.getProfileHelper().getRecipientProfile(sender);
|
||||
final var selfProfile = context.getProfileHelper().getSelfProfile();
|
||||
final var serviceId = ServiceId.parseOrThrow(e.getSender());
|
||||
if ((!sender.equals(account.getSelfRecipientId()) || e.getSenderDevice() != account.getDeviceId())
|
||||
&& senderProfile != null
|
||||
&& senderProfile.getCapabilities().contains(Profile.Capability.senderKey)
|
||||
&& selfProfile != null
|
||||
&& selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
|
||||
logger.debug("Received invalid message, requesting message resend.");
|
||||
actions.add(new SendRetryMessageRequestAction(sender, e, envelope));
|
||||
actions.add(new SendRetryMessageRequestAction(sender, serviceId, e, envelope));
|
||||
} else {
|
||||
logger.debug("Received invalid message, queuing renew session action.");
|
||||
actions.add(new RenewSessionAction(sender));
|
||||
actions.add(new RenewSessionAction(sender, serviceId));
|
||||
}
|
||||
}
|
||||
exception = e;
|
||||
|
@ -176,9 +177,9 @@ public final class IncomingMessageHandler {
|
|||
account.getRecipientTrustedResolver().resolveRecipientTrusted(content.getSender());
|
||||
}
|
||||
if (envelope.isReceipt()) {
|
||||
final var senderPair = getSender(envelope, content);
|
||||
final var sender = senderPair.first();
|
||||
final var senderDeviceId = senderPair.second();
|
||||
final var senderDeviceAddress = getSender(envelope, content);
|
||||
final var sender = senderDeviceAddress.serviceId();
|
||||
final var senderDeviceId = senderDeviceAddress.deviceId();
|
||||
account.getMessageSendLogStore().deleteEntryForRecipient(envelope.getTimestamp(), sender, senderDeviceId);
|
||||
}
|
||||
|
||||
|
@ -211,24 +212,23 @@ public final class IncomingMessageHandler {
|
|||
SignalServiceEnvelope envelope, SignalServiceContent content, ReceiveConfig receiveConfig
|
||||
) {
|
||||
var actions = new ArrayList<HandleAction>();
|
||||
final var senderPair = getSender(envelope, content);
|
||||
final var sender = senderPair.first();
|
||||
final var senderDeviceId = senderPair.second();
|
||||
final var senderDeviceAddress = getSender(envelope, content);
|
||||
final var sender = senderDeviceAddress.recipientId();
|
||||
final var senderServiceId = senderDeviceAddress.serviceId();
|
||||
final var senderDeviceId = senderDeviceAddress.deviceId();
|
||||
final var destination = getDestination(envelope);
|
||||
|
||||
if (content.getReceiptMessage().isPresent()) {
|
||||
final var message = content.getReceiptMessage().get();
|
||||
if (message.isDeliveryReceipt()) {
|
||||
account.getMessageSendLogStore()
|
||||
.deleteEntriesForRecipient(message.getTimestamps(), sender, senderDeviceId);
|
||||
.deleteEntriesForRecipient(message.getTimestamps(), senderServiceId, senderDeviceId);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.getSenderKeyDistributionMessage().isPresent()) {
|
||||
final var message = content.getSenderKeyDistributionMessage().get();
|
||||
final var protocolAddress = new SignalProtocolAddress(context.getRecipientHelper()
|
||||
.resolveSignalServiceAddress(sender)
|
||||
.getIdentifier(), senderDeviceId);
|
||||
final var protocolAddress = senderServiceId.toProtocolAddress(senderDeviceId);
|
||||
logger.debug("Received a sender key distribution message for distributionId {} from {}",
|
||||
message.getDistributionId(),
|
||||
protocolAddress);
|
||||
|
@ -242,7 +242,7 @@ public final class IncomingMessageHandler {
|
|||
senderDeviceId,
|
||||
message.getTimestamp());
|
||||
if (message.getDeviceId() == account.getDeviceId()) {
|
||||
handleDecryptionErrorMessage(actions, sender, senderDeviceId, message);
|
||||
handleDecryptionErrorMessage(actions, sender, senderServiceId, senderDeviceId, message);
|
||||
} else {
|
||||
logger.debug("Request is for another one of our devices");
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ public final class IncomingMessageHandler {
|
|||
|
||||
actions.addAll(handleSignalServiceDataMessage(message,
|
||||
false,
|
||||
sender,
|
||||
senderDeviceAddress,
|
||||
destination,
|
||||
receiveConfig.ignoreAttachments()));
|
||||
}
|
||||
|
@ -286,7 +286,7 @@ public final class IncomingMessageHandler {
|
|||
|
||||
if (content.getSyncMessage().isPresent()) {
|
||||
var syncMessage = content.getSyncMessage().get();
|
||||
actions.addAll(handleSyncMessage(syncMessage, sender, receiveConfig.ignoreAttachments()));
|
||||
actions.addAll(handleSyncMessage(syncMessage, senderDeviceAddress, receiveConfig.ignoreAttachments()));
|
||||
}
|
||||
|
||||
return actions;
|
||||
|
@ -295,11 +295,15 @@ public final class IncomingMessageHandler {
|
|||
private void handleDecryptionErrorMessage(
|
||||
final List<HandleAction> actions,
|
||||
final RecipientId sender,
|
||||
final ServiceId senderServiceId,
|
||||
final int senderDeviceId,
|
||||
final DecryptionErrorMessage message
|
||||
) {
|
||||
final var logEntries = account.getMessageSendLogStore()
|
||||
.findMessages(sender, senderDeviceId, message.getTimestamp(), message.getRatchetKey().isEmpty());
|
||||
.findMessages(senderServiceId,
|
||||
senderDeviceId,
|
||||
message.getTimestamp(),
|
||||
message.getRatchetKey().isEmpty());
|
||||
|
||||
for (final var logEntry : logEntries) {
|
||||
actions.add(new ResendMessageAction(sender, message.getTimestamp(), logEntry));
|
||||
|
@ -307,13 +311,13 @@ public final class IncomingMessageHandler {
|
|||
|
||||
if (message.getRatchetKey().isPresent()) {
|
||||
if (account.getAciSessionStore()
|
||||
.isCurrentRatchetKey(sender, senderDeviceId, message.getRatchetKey().get())) {
|
||||
.isCurrentRatchetKey(senderServiceId, senderDeviceId, message.getRatchetKey().get())) {
|
||||
if (logEntries.isEmpty()) {
|
||||
logger.debug("Renewing the session with sender");
|
||||
actions.add(new RenewSessionAction(sender));
|
||||
actions.add(new RenewSessionAction(sender, senderServiceId));
|
||||
} else {
|
||||
logger.trace("Archiving the session with sender, a resend message has already been queued");
|
||||
context.getAccount().getAciSessionStore().archiveSessions(sender);
|
||||
context.getAccount().getAciSessionStore().archiveSessions(senderServiceId);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
@ -333,16 +337,16 @@ public final class IncomingMessageHandler {
|
|||
sender,
|
||||
senderDeviceId,
|
||||
group.getDistributionId());
|
||||
account.getSenderKeyStore().deleteSharedWith(sender, senderDeviceId, group.getDistributionId());
|
||||
account.getSenderKeyStore().deleteSharedWith(senderServiceId, senderDeviceId, group.getDistributionId());
|
||||
}
|
||||
if (!found) {
|
||||
logger.debug("Reset all shared sender keys with this recipient, no related message found in send log");
|
||||
account.getSenderKeyStore().deleteSharedWith(sender);
|
||||
account.getSenderKeyStore().deleteSharedWith(senderServiceId);
|
||||
}
|
||||
}
|
||||
|
||||
private List<HandleAction> handleSyncMessage(
|
||||
final SignalServiceSyncMessage syncMessage, final RecipientId sender, final boolean ignoreAttachments
|
||||
final SignalServiceSyncMessage syncMessage, final DeviceAddress sender, final boolean ignoreAttachments
|
||||
) {
|
||||
var actions = new ArrayList<HandleAction>();
|
||||
account.setMultiDevice(true);
|
||||
|
@ -353,12 +357,16 @@ public final class IncomingMessageHandler {
|
|||
actions.addAll(handleSignalServiceDataMessage(message.getDataMessage().get(),
|
||||
true,
|
||||
sender,
|
||||
destination == null ? null : context.getRecipientHelper().resolveRecipient(destination),
|
||||
destination == null
|
||||
? null
|
||||
: new DeviceAddress(context.getRecipientHelper().resolveRecipient(destination),
|
||||
destination.getServiceId(),
|
||||
0),
|
||||
ignoreAttachments));
|
||||
}
|
||||
if (message.getStoryMessage().isPresent()) {
|
||||
actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(),
|
||||
sender,
|
||||
sender.recipientId(),
|
||||
ignoreAttachments));
|
||||
}
|
||||
}
|
||||
|
@ -423,8 +431,7 @@ public final class IncomingMessageHandler {
|
|||
if (syncMessage.getVerified().isPresent()) {
|
||||
final var verifiedMessage = syncMessage.getVerified().get();
|
||||
account.getIdentityKeyStore()
|
||||
.setIdentityTrustLevel(account.getRecipientTrustedResolver()
|
||||
.resolveRecipientTrusted(verifiedMessage.getDestination()),
|
||||
.setIdentityTrustLevel(verifiedMessage.getDestination().getServiceId(),
|
||||
verifiedMessage.getIdentityKey(),
|
||||
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
|
||||
}
|
||||
|
@ -575,8 +582,8 @@ public final class IncomingMessageHandler {
|
|||
private List<HandleAction> handleSignalServiceDataMessage(
|
||||
SignalServiceDataMessage message,
|
||||
boolean isSync,
|
||||
RecipientId source,
|
||||
RecipientId destination,
|
||||
DeviceAddress source,
|
||||
DeviceAddress destination,
|
||||
boolean ignoreAttachments
|
||||
) {
|
||||
var actions = new ArrayList<HandleAction>();
|
||||
|
@ -616,19 +623,19 @@ public final class IncomingMessageHandler {
|
|||
}
|
||||
case DELIVER:
|
||||
if (groupV1 == null && !isSync) {
|
||||
actions.add(new SendGroupInfoRequestAction(source, groupId));
|
||||
actions.add(new SendGroupInfoRequestAction(source.recipientId(), groupId));
|
||||
}
|
||||
break;
|
||||
case QUIT: {
|
||||
if (groupV1 != null) {
|
||||
groupV1.removeMember(source);
|
||||
groupV1.removeMember(source.recipientId());
|
||||
account.getGroupStore().updateGroup(groupV1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REQUEST_INFO:
|
||||
if (groupV1 != null && !isSync) {
|
||||
actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
|
||||
actions.add(new SendGroupInfoAction(source.recipientId(), groupV1.getGroupId()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -643,7 +650,7 @@ public final class IncomingMessageHandler {
|
|||
|
||||
final var conversationPartnerAddress = isSync ? destination : source;
|
||||
if (conversationPartnerAddress != null && message.isEndSession()) {
|
||||
account.getAciSessionStore().deleteAllSessions(conversationPartnerAddress);
|
||||
account.getAciSessionStore().deleteAllSessions(conversationPartnerAddress.serviceId());
|
||||
}
|
||||
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
|
||||
if (message.getGroupContext().isPresent()) {
|
||||
|
@ -662,7 +669,7 @@ public final class IncomingMessageHandler {
|
|||
}
|
||||
} else if (conversationPartnerAddress != null) {
|
||||
context.getContactHelper()
|
||||
.setExpirationTimer(conversationPartnerAddress, message.getExpiresInSeconds());
|
||||
.setExpirationTimer(conversationPartnerAddress.recipientId(), message.getExpiresInSeconds());
|
||||
}
|
||||
}
|
||||
if (!ignoreAttachments) {
|
||||
|
@ -698,7 +705,7 @@ public final class IncomingMessageHandler {
|
|||
}
|
||||
}
|
||||
if (message.getProfileKey().isPresent()) {
|
||||
handleIncomingProfileKey(message.getProfileKey().get(), source);
|
||||
handleIncomingProfileKey(message.getProfileKey().get(), source.recipientId());
|
||||
}
|
||||
if (message.getSticker().isPresent()) {
|
||||
final var messageSticker = message.getSticker().get();
|
||||
|
@ -769,24 +776,29 @@ public final class IncomingMessageHandler {
|
|||
this.account.getProfileStore().storeProfileKey(source, profileKey);
|
||||
}
|
||||
|
||||
private Pair<RecipientId, Integer> getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
|
||||
private DeviceAddress getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
|
||||
if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
|
||||
return new Pair<>(context.getRecipientHelper().resolveRecipient(envelope.getSourceAddress()),
|
||||
return new DeviceAddress(context.getRecipientHelper().resolveRecipient(envelope.getSourceAddress()),
|
||||
envelope.getSourceAddress().getServiceId(),
|
||||
envelope.getSourceDevice());
|
||||
} else {
|
||||
return new Pair<>(context.getRecipientHelper().resolveRecipient(content.getSender()),
|
||||
return new DeviceAddress(context.getRecipientHelper().resolveRecipient(content.getSender()),
|
||||
content.getSender().getServiceId(),
|
||||
content.getSenderDevice());
|
||||
}
|
||||
}
|
||||
|
||||
private RecipientId getDestination(SignalServiceEnvelope envelope) {
|
||||
private DeviceAddress getDestination(SignalServiceEnvelope envelope) {
|
||||
if (!envelope.hasDestinationUuid()) {
|
||||
return account.getSelfRecipientId();
|
||||
return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId());
|
||||
}
|
||||
final var addressOptional = SignalServiceAddress.fromRaw(envelope.getDestinationUuid(), null);
|
||||
if (addressOptional.isEmpty()) {
|
||||
return account.getSelfRecipientId();
|
||||
return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId());
|
||||
}
|
||||
return context.getRecipientHelper().resolveRecipient(addressOptional.get());
|
||||
final var address = addressOptional.get();
|
||||
return new DeviceAddress(context.getRecipientHelper().resolveRecipient(address), address.getServiceId(), 0);
|
||||
}
|
||||
|
||||
private record DeviceAddress(RecipientId recipientId, ServiceId serviceId, int deviceId) {}
|
||||
}
|
||||
|
|
|
@ -346,7 +346,7 @@ public final class ProfileHelper {
|
|||
try {
|
||||
logger.trace("Storing identity");
|
||||
final var identityKey = new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey()));
|
||||
account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
|
||||
account.getIdentityKeyStore().saveIdentity(p.getProfile().getServiceId(), identityKey);
|
||||
} catch (InvalidKeyException ignored) {
|
||||
logger.warn("Got invalid identity key in profile for {}",
|
||||
context.getRecipientHelper().resolveSignalServiceAddress(recipientId).getIdentifier());
|
||||
|
|
|
@ -486,7 +486,10 @@ public class SendHelper {
|
|||
continue;
|
||||
}
|
||||
|
||||
final var identity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
|
||||
final var serviceId = account.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(recipientId)
|
||||
.getServiceId();
|
||||
final var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId);
|
||||
if (identity == null || !identity.getTrustLevel().isTrusted()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -531,7 +534,7 @@ public class SendHelper {
|
|||
final var recipientIdList = new ArrayList<>(recipientIds);
|
||||
|
||||
long keyCreateTime = account.getSenderKeyStore()
|
||||
.getCreateTimeForOurKey(account.getSelfRecipientId(), account.getDeviceId(), distributionId);
|
||||
.getCreateTimeForOurKey(account.getAci(), account.getDeviceId(), distributionId);
|
||||
long keyAge = System.currentTimeMillis() - keyCreateTime;
|
||||
|
||||
if (keyCreateTime != -1 && keyAge > TimeUnit.DAYS.toMillis(14)) {
|
||||
|
@ -540,7 +543,7 @@ public class SendHelper {
|
|||
keyCreateTime,
|
||||
keyAge,
|
||||
TimeUnit.MILLISECONDS.toDays(keyAge));
|
||||
account.getSenderKeyStore().deleteOurKey(account.getSelfRecipientId(), distributionId);
|
||||
account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
|
||||
}
|
||||
|
||||
List<SignalServiceAddress> addresses = recipientIdList.stream()
|
||||
|
@ -573,11 +576,11 @@ public class SendHelper {
|
|||
return null;
|
||||
} catch (NoSessionException e) {
|
||||
logger.warn("No session. Falling back to legacy sends.", e);
|
||||
account.getSenderKeyStore().deleteOurKey(account.getSelfRecipientId(), distributionId);
|
||||
account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.warn("Invalid key. Falling back to legacy sends.", e);
|
||||
account.getSenderKeyStore().deleteOurKey(account.getSelfRecipientId(), distributionId);
|
||||
account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
|
||||
return null;
|
||||
} catch (InvalidRegistrationIdException e) {
|
||||
logger.warn("Invalid registrationId. Falling back to legacy sends.", e);
|
||||
|
@ -685,7 +688,8 @@ public class SendHelper {
|
|||
}
|
||||
if (r.getIdentityFailure() != null) {
|
||||
final var recipientId = context.getRecipientHelper().resolveRecipient(r.getAddress());
|
||||
context.getIdentityHelper().handleIdentityFailure(recipientId, r.getIdentityFailure());
|
||||
context.getIdentityHelper()
|
||||
.handleIdentityFailure(recipientId, r.getAddress().getServiceId(), r.getIdentityFailure());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -144,11 +144,12 @@ public class StorageHelper {
|
|||
try {
|
||||
logger.trace("Storing identity key {}", recipientId);
|
||||
final var identityKey = new IdentityKey(contactRecord.getIdentityKey().get());
|
||||
account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
|
||||
account.getIdentityKeyStore().saveIdentity(address.getServiceId(), identityKey);
|
||||
|
||||
final var trustLevel = TrustLevel.fromIdentityState(contactRecord.getIdentityState());
|
||||
if (trustLevel != null) {
|
||||
account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identityKey, trustLevel);
|
||||
account.getIdentityKeyStore()
|
||||
.setIdentityTrustLevel(address.getServiceId(), identityKey, trustLevel);
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.warn("Received invalid contact identity key from storage");
|
||||
|
|
|
@ -132,7 +132,7 @@ public class SyncHelper {
|
|||
final var contact = contactPair.second();
|
||||
final var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
|
||||
|
||||
var currentIdentity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
|
||||
var currentIdentity = account.getIdentityKeyStore().getIdentityInfo(address.getServiceId());
|
||||
VerifiedMessage verifiedMessage = null;
|
||||
if (currentIdentity != null) {
|
||||
verifiedMessage = new VerifiedMessage(address,
|
||||
|
@ -319,8 +319,7 @@ public class SyncHelper {
|
|||
if (c.getVerified().isPresent()) {
|
||||
final var verifiedMessage = c.getVerified().get();
|
||||
account.getIdentityKeyStore()
|
||||
.setIdentityTrustLevel(account.getRecipientTrustedResolver()
|
||||
.resolveRecipientTrusted(verifiedMessage.getDestination()),
|
||||
.setIdentityTrustLevel(verifiedMessage.getDestination().getServiceId(),
|
||||
verifiedMessage.getIdentityKey(),
|
||||
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import java.sql.SQLException;
|
|||
public class AccountDatabase extends Database {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
|
||||
private static final long DATABASE_VERSION = 9;
|
||||
private static final long DATABASE_VERSION = 10;
|
||||
|
||||
private AccountDatabase(final HikariDataSource dataSource) {
|
||||
super(logger, DATABASE_VERSION, dataSource);
|
||||
|
@ -212,5 +212,81 @@ public class AccountDatabase extends Database {
|
|||
""");
|
||||
}
|
||||
}
|
||||
if (oldVersion < 10) {
|
||||
logger.debug("Updating database: Key tables on serviceId instead of recipientId");
|
||||
try (final var statement = connection.createStatement()) {
|
||||
statement.executeUpdate("""
|
||||
CREATE TABLE identity2 (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
uuid BLOB UNIQUE NOT NULL,
|
||||
identity_key BLOB NOT NULL,
|
||||
added_timestamp INTEGER NOT NULL,
|
||||
trust_level INTEGER NOT NULL
|
||||
) STRICT;
|
||||
INSERT INTO identity2 (_id, uuid, identity_key, added_timestamp, trust_level)
|
||||
SELECT i._id, r.uuid, i.identity_key, i.added_timestamp, i.trust_level
|
||||
FROM identity i LEFT JOIN recipient r ON i.recipient_id = r._id
|
||||
WHERE uuid IS NOT NULL;
|
||||
DROP TABLE identity;
|
||||
ALTER TABLE identity2 RENAME TO identity;
|
||||
|
||||
DROP INDEX msl_recipient_index;
|
||||
ALTER TABLE message_send_log ADD COLUMN uuid BLOB;
|
||||
UPDATE message_send_log
|
||||
SET uuid = r.uuid
|
||||
FROM message_send_log i, (SELECT _id, uuid FROM recipient) AS r
|
||||
WHERE i.recipient_id = r._id;
|
||||
DELETE FROM message_send_log WHERE uuid IS NULL;
|
||||
ALTER TABLE message_send_log DROP COLUMN recipient_id;
|
||||
CREATE INDEX msl_recipient_index ON message_send_log (uuid, device_id, content_id);
|
||||
|
||||
CREATE TABLE sender_key2 (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
uuid BLOB NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
distribution_id BLOB NOT NULL,
|
||||
record BLOB NOT NULL,
|
||||
created_timestamp INTEGER NOT NULL,
|
||||
UNIQUE(uuid, device_id, distribution_id)
|
||||
) STRICT;
|
||||
INSERT INTO sender_key2 (_id, uuid, device_id, distribution_id, record, created_timestamp)
|
||||
SELECT s._id, r.uuid, s.device_id, s.distribution_id, s.record, s.created_timestamp
|
||||
FROM sender_key s LEFT JOIN recipient r ON s.recipient_id = r._id
|
||||
WHERE uuid IS NOT NULL;
|
||||
DROP TABLE sender_key;
|
||||
ALTER TABLE sender_key2 RENAME TO sender_key;
|
||||
|
||||
CREATE TABLE sender_key_shared2 (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
uuid BLOB NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
distribution_id BLOB NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
UNIQUE(uuid, device_id, distribution_id)
|
||||
) STRICT;
|
||||
INSERT INTO sender_key_shared2 (_id, uuid, device_id, distribution_id, timestamp)
|
||||
SELECT s._id, r.uuid, s.device_id, s.distribution_id, s.timestamp
|
||||
FROM sender_key_shared s LEFT JOIN recipient r ON s.recipient_id = r._id
|
||||
WHERE uuid IS NOT NULL;
|
||||
DROP TABLE sender_key_shared;
|
||||
ALTER TABLE sender_key_shared2 RENAME TO sender_key_shared;
|
||||
|
||||
CREATE TABLE session2 (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
account_id_type INTEGER NOT NULL,
|
||||
uuid BLOB NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
record BLOB NOT NULL,
|
||||
UNIQUE(account_id_type, uuid, device_id)
|
||||
) STRICT;
|
||||
INSERT INTO session2 (_id, account_id_type, uuid, device_id, record)
|
||||
SELECT s._id, s.account_id_type, r.uuid, s.device_id, s.record
|
||||
FROM session s LEFT JOIN recipient r ON s.recipient_id = r._id
|
||||
WHERE uuid IS NOT NULL;
|
||||
DROP TABLE session;
|
||||
ALTER TABLE session2 RENAME TO session;
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -383,6 +383,14 @@ public class SignalAccount implements Closeable {
|
|||
this.storageManifestVersion = -1;
|
||||
this.setStorageManifest(null);
|
||||
this.storageKey = null;
|
||||
final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
|
||||
getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
|
||||
getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
|
||||
if (getPniIdentityKeyPair() != null) {
|
||||
final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
|
||||
getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
|
||||
getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
private void migrateLegacyConfigs() {
|
||||
|
@ -400,21 +408,21 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
|
||||
private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
|
||||
getAciSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
getPniSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
getIdentityKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
getGroupStore().mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
getSenderKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
}
|
||||
|
||||
public void removeRecipient(final RecipientId recipientId) {
|
||||
getAciSessionStore().deleteAllSessions(recipientId);
|
||||
getPniSessionStore().deleteAllSessions(recipientId);
|
||||
getIdentityKeyStore().deleteIdentity(recipientId);
|
||||
getMessageCache().deleteMessages(recipientId);
|
||||
getSenderKeyStore().deleteAll(recipientId);
|
||||
getRecipientStore().deleteRecipientData(recipientId);
|
||||
getMessageCache().deleteMessages(recipientId);
|
||||
final var recipientAddress = getRecipientStore().resolveRecipientAddress(recipientId);
|
||||
if (recipientAddress.uuid().isPresent()) {
|
||||
final var serviceId = ServiceId.from(recipientAddress.uuid().get());
|
||||
getAciSessionStore().deleteAllSessions(serviceId);
|
||||
getPniSessionStore().deleteAllSessions(serviceId);
|
||||
getIdentityKeyStore().deleteIdentity(serviceId);
|
||||
getSenderKeyStore().deleteAll(serviceId);
|
||||
}
|
||||
}
|
||||
|
||||
public static File getFileName(File dataPath, String account) {
|
||||
|
@ -646,12 +654,18 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
final var legacySessionsPath = getSessionsPath(dataPath, accountPath);
|
||||
if (legacySessionsPath.exists()) {
|
||||
LegacySessionStore.migrate(legacySessionsPath, getRecipientResolver(), getAciSessionStore());
|
||||
LegacySessionStore.migrate(legacySessionsPath,
|
||||
getRecipientResolver(),
|
||||
getRecipientAddressResolver(),
|
||||
getAciSessionStore());
|
||||
migratedLegacyConfig = true;
|
||||
}
|
||||
final var legacyIdentitiesPath = getIdentitiesPath(dataPath, accountPath);
|
||||
if (legacyIdentitiesPath.exists()) {
|
||||
LegacyIdentityKeyStore.migrate(legacyIdentitiesPath, getRecipientResolver(), getIdentityKeyStore());
|
||||
LegacyIdentityKeyStore.migrate(legacyIdentitiesPath,
|
||||
getRecipientResolver(),
|
||||
getRecipientAddressResolver(),
|
||||
getIdentityKeyStore());
|
||||
migratedLegacyConfig = true;
|
||||
}
|
||||
final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
|
||||
|
@ -672,12 +686,18 @@ public class SignalAccount implements Closeable {
|
|||
|
||||
final var legacySenderKeysPath = getSenderKeysPath(dataPath, accountPath);
|
||||
if (legacySenderKeysPath.exists()) {
|
||||
LegacySenderKeyRecordStore.migrate(legacySenderKeysPath, getRecipientResolver(), getSenderKeyStore());
|
||||
LegacySenderKeyRecordStore.migrate(legacySenderKeysPath,
|
||||
getRecipientResolver(),
|
||||
getRecipientAddressResolver(),
|
||||
getSenderKeyStore());
|
||||
migratedLegacyConfig = true;
|
||||
}
|
||||
final var legacySenderKeysSharedPath = getSharedSenderKeysFile(dataPath, accountPath);
|
||||
if (legacySenderKeysSharedPath.exists()) {
|
||||
LegacySenderKeySharedStore.migrate(legacySenderKeysSharedPath, getRecipientResolver(), getSenderKeyStore());
|
||||
LegacySenderKeySharedStore.migrate(legacySenderKeysSharedPath,
|
||||
getRecipientResolver(),
|
||||
getRecipientAddressResolver(),
|
||||
getSenderKeyStore());
|
||||
migratedLegacyConfig = true;
|
||||
}
|
||||
if (rootNode.hasNonNull("groupStore")) {
|
||||
|
@ -770,9 +790,12 @@ public class SignalAccount implements Closeable {
|
|||
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
|
||||
logger.debug("Migrating legacy identity session store.");
|
||||
for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
|
||||
RecipientId recipientId = getRecipientStore().resolveRecipientTrusted(identity.getAddress());
|
||||
getIdentityKeyStore().saveIdentity(recipientId, identity.getIdentityKey());
|
||||
getIdentityKeyStore().setIdentityTrustLevel(recipientId,
|
||||
if (identity.getAddress().uuid().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final var serviceId = identity.getAddress().getServiceId();
|
||||
getIdentityKeyStore().saveIdentity(serviceId, identity.getIdentityKey());
|
||||
getIdentityKeyStore().setIdentityTrustLevel(serviceId,
|
||||
identity.getIdentityKey(),
|
||||
identity.getTrustLevel());
|
||||
}
|
||||
|
@ -1107,25 +1130,17 @@ public class SignalAccount implements Closeable {
|
|||
|
||||
public SessionStore getAciSessionStore() {
|
||||
return getOrCreate(() -> aciSessionStore,
|
||||
() -> aciSessionStore = new SessionStore(getAccountDatabase(),
|
||||
ServiceIdType.ACI,
|
||||
getRecipientResolver(),
|
||||
getRecipientIdCreator()));
|
||||
() -> aciSessionStore = new SessionStore(getAccountDatabase(), ServiceIdType.ACI));
|
||||
}
|
||||
|
||||
public SessionStore getPniSessionStore() {
|
||||
return getOrCreate(() -> pniSessionStore,
|
||||
() -> pniSessionStore = new SessionStore(getAccountDatabase(),
|
||||
ServiceIdType.PNI,
|
||||
getRecipientResolver(),
|
||||
getRecipientIdCreator()));
|
||||
() -> pniSessionStore = new SessionStore(getAccountDatabase(), ServiceIdType.PNI));
|
||||
}
|
||||
|
||||
public IdentityKeyStore getIdentityKeyStore() {
|
||||
return getOrCreate(() -> identityKeyStore,
|
||||
() -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(),
|
||||
getRecipientIdCreator(),
|
||||
trustNewIdentity));
|
||||
() -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(), trustNewIdentity));
|
||||
}
|
||||
|
||||
public SignalIdentityKeyStore getAciIdentityKeyStore() {
|
||||
|
@ -1207,11 +1222,7 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
|
||||
public SenderKeyStore getSenderKeyStore() {
|
||||
return getOrCreate(() -> senderKeyStore,
|
||||
() -> senderKeyStore = new SenderKeyStore(getAccountDatabase(),
|
||||
getRecipientAddressResolver(),
|
||||
getRecipientResolver(),
|
||||
getRecipientIdCreator()));
|
||||
return getOrCreate(() -> senderKeyStore, () -> senderKeyStore = new SenderKeyStore(getAccountDatabase()));
|
||||
}
|
||||
|
||||
public ConfigurationStore getConfigurationStore() {
|
||||
|
@ -1235,7 +1246,7 @@ public class SignalAccount implements Closeable {
|
|||
|
||||
public MessageSendLogStore getMessageSendLogStore() {
|
||||
return getOrCreate(() -> messageSendLogStore,
|
||||
() -> messageSendLogStore = new MessageSendLogStore(getRecipientResolver(), getAccountDatabase()));
|
||||
() -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase()));
|
||||
}
|
||||
|
||||
public CredentialsProvider getCredentialsProvider() {
|
||||
|
@ -1350,6 +1361,9 @@ public class SignalAccount implements Closeable {
|
|||
|
||||
public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
|
||||
pniIdentityKeyPair = identityKeyPair;
|
||||
final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
|
||||
getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
|
||||
getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
|
||||
save();
|
||||
}
|
||||
|
||||
|
@ -1553,10 +1567,13 @@ public class SignalAccount implements Closeable {
|
|||
getAciSessionStore().archiveAllSessions();
|
||||
getPniSessionStore().archiveAllSessions();
|
||||
getSenderKeyStore().deleteAll();
|
||||
final var recipientId = getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
|
||||
final var publicKey = getAciIdentityKeyPair().getPublicKey();
|
||||
getIdentityKeyStore().saveIdentity(recipientId, publicKey);
|
||||
getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
|
||||
getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
|
||||
final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
|
||||
getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
|
||||
getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
|
||||
final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
|
||||
getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
|
||||
getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
|
||||
}
|
||||
|
||||
public void deleteAccountData() throws IOException {
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
package org.asamk.signal.manager.storage.identities;
|
||||
|
||||
import org.asamk.signal.manager.api.TrustLevel;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
public class IdentityInfo {
|
||||
|
||||
private final RecipientId recipientId;
|
||||
private final ServiceId serviceId;
|
||||
private final IdentityKey identityKey;
|
||||
private final TrustLevel trustLevel;
|
||||
private final long addedTimestamp;
|
||||
|
||||
IdentityInfo(
|
||||
final RecipientId recipientId, IdentityKey identityKey, TrustLevel trustLevel, long addedTimestamp
|
||||
final ServiceId serviceId, IdentityKey identityKey, TrustLevel trustLevel, long addedTimestamp
|
||||
) {
|
||||
this.recipientId = recipientId;
|
||||
this.serviceId = serviceId;
|
||||
this.identityKey = identityKey;
|
||||
this.trustLevel = trustLevel;
|
||||
this.addedTimestamp = addedTimestamp;
|
||||
}
|
||||
|
||||
public RecipientId getRecipientId() {
|
||||
return recipientId;
|
||||
public ServiceId getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
|
|
|
@ -3,13 +3,12 @@ package org.asamk.signal.manager.storage.identities;
|
|||
import org.asamk.signal.manager.api.TrustLevel;
|
||||
import org.asamk.signal.manager.storage.Database;
|
||||
import org.asamk.signal.manager.storage.Utils;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.state.IdentityKeyStore.Direction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
|
@ -25,9 +24,8 @@ public class IdentityKeyStore {
|
|||
private final static Logger logger = LoggerFactory.getLogger(IdentityKeyStore.class);
|
||||
private static final String TABLE_IDENTITY = "identity";
|
||||
private final Database database;
|
||||
private final RecipientIdCreator recipientIdCreator;
|
||||
private final TrustNewIdentity trustNewIdentity;
|
||||
private final PublishSubject<RecipientId> identityChanges = PublishSubject.create();
|
||||
private final PublishSubject<ServiceId> identityChanges = PublishSubject.create();
|
||||
|
||||
private boolean isRetryingDecryption = false;
|
||||
|
||||
|
@ -37,42 +35,37 @@ public class IdentityKeyStore {
|
|||
statement.executeUpdate("""
|
||||
CREATE TABLE identity (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
recipient_id INTEGER UNIQUE NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
|
||||
uuid BLOB UNIQUE NOT NULL,
|
||||
identity_key BLOB NOT NULL,
|
||||
added_timestamp INTEGER NOT NULL,
|
||||
trust_level INTEGER NOT NULL
|
||||
);
|
||||
) STRICT;
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityKeyStore(
|
||||
final Database database,
|
||||
final RecipientIdCreator recipientIdCreator,
|
||||
final TrustNewIdentity trustNewIdentity
|
||||
) {
|
||||
public IdentityKeyStore(final Database database, final TrustNewIdentity trustNewIdentity) {
|
||||
this.database = database;
|
||||
this.recipientIdCreator = recipientIdCreator;
|
||||
this.trustNewIdentity = trustNewIdentity;
|
||||
}
|
||||
|
||||
public Observable<RecipientId> getIdentityChanges() {
|
||||
public Observable<ServiceId> getIdentityChanges() {
|
||||
return identityChanges;
|
||||
}
|
||||
|
||||
public boolean saveIdentity(final RecipientId recipientId, final IdentityKey identityKey) {
|
||||
public boolean saveIdentity(final ServiceId serviceId, final IdentityKey identityKey) {
|
||||
if (isRetryingDecryption) {
|
||||
return false;
|
||||
}
|
||||
try (final var connection = database.getConnection()) {
|
||||
final var identityInfo = loadIdentity(connection, recipientId);
|
||||
final var identityInfo = loadIdentity(connection, serviceId);
|
||||
if (identityInfo != null && identityInfo.getIdentityKey().equals(identityKey)) {
|
||||
// Identity already exists, not updating the trust level
|
||||
logger.trace("Not storing new identity for recipient {}, identity already stored", recipientId);
|
||||
logger.trace("Not storing new identity for recipient {}, identity already stored", serviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
saveNewIdentity(connection, recipientId, identityKey, identityInfo == null);
|
||||
saveNewIdentity(connection, serviceId, identityKey, identityInfo == null);
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed update identity store", e);
|
||||
|
@ -83,24 +76,24 @@ public class IdentityKeyStore {
|
|||
isRetryingDecryption = retryingDecryption;
|
||||
}
|
||||
|
||||
public boolean setIdentityTrustLevel(RecipientId recipientId, IdentityKey identityKey, TrustLevel trustLevel) {
|
||||
public boolean setIdentityTrustLevel(ServiceId serviceId, IdentityKey identityKey, TrustLevel trustLevel) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
final var identityInfo = loadIdentity(connection, recipientId);
|
||||
final var identityInfo = loadIdentity(connection, serviceId);
|
||||
if (identityInfo == null) {
|
||||
logger.debug("Not updating trust level for recipient {}, identity not found", recipientId);
|
||||
logger.debug("Not updating trust level for recipient {}, identity not found", serviceId);
|
||||
return false;
|
||||
}
|
||||
if (!identityInfo.getIdentityKey().equals(identityKey)) {
|
||||
logger.debug("Not updating trust level for recipient {}, different identity found", recipientId);
|
||||
logger.debug("Not updating trust level for recipient {}, different identity found", serviceId);
|
||||
return false;
|
||||
}
|
||||
if (identityInfo.getTrustLevel() == trustLevel) {
|
||||
logger.trace("Not updating trust level for recipient {}, trust level already matches", recipientId);
|
||||
logger.trace("Not updating trust level for recipient {}, trust level already matches", serviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug("Updating trust level for recipient {} with trust {}", recipientId, trustLevel);
|
||||
final var newIdentityInfo = new IdentityInfo(recipientId,
|
||||
logger.debug("Updating trust level for recipient {} with trust {}", serviceId, trustLevel);
|
||||
final var newIdentityInfo = new IdentityInfo(serviceId,
|
||||
identityKey,
|
||||
trustLevel,
|
||||
identityInfo.getDateAddedTimestamp());
|
||||
|
@ -111,41 +104,41 @@ public class IdentityKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isTrustedIdentity(RecipientId recipientId, IdentityKey identityKey, Direction direction) {
|
||||
public boolean isTrustedIdentity(ServiceId serviceId, IdentityKey identityKey, Direction direction) {
|
||||
if (trustNewIdentity == TrustNewIdentity.ALWAYS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try (final var connection = database.getConnection()) {
|
||||
// TODO implement possibility for different handling of incoming/outgoing trust decisions
|
||||
var identityInfo = loadIdentity(connection, recipientId);
|
||||
var identityInfo = loadIdentity(connection, serviceId);
|
||||
if (identityInfo == null) {
|
||||
logger.debug("Initial identity found for {}, saving.", recipientId);
|
||||
saveNewIdentity(connection, recipientId, identityKey, true);
|
||||
identityInfo = loadIdentity(connection, recipientId);
|
||||
logger.debug("Initial identity found for {}, saving.", serviceId);
|
||||
saveNewIdentity(connection, serviceId, identityKey, true);
|
||||
identityInfo = loadIdentity(connection, serviceId);
|
||||
} else if (!identityInfo.getIdentityKey().equals(identityKey)) {
|
||||
// Identity found, but different
|
||||
if (direction == Direction.SENDING) {
|
||||
logger.debug("Changed identity found for {}, saving.", recipientId);
|
||||
saveNewIdentity(connection, recipientId, identityKey, false);
|
||||
identityInfo = loadIdentity(connection, recipientId);
|
||||
logger.debug("Changed identity found for {}, saving.", serviceId);
|
||||
saveNewIdentity(connection, serviceId, identityKey, false);
|
||||
identityInfo = loadIdentity(connection, serviceId);
|
||||
} else {
|
||||
logger.trace("Trusting identity for {} for {}: {}", recipientId, direction, false);
|
||||
logger.trace("Trusting identity for {} for {}: {}", serviceId, direction, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final var isTrusted = identityInfo != null && identityInfo.isTrusted();
|
||||
logger.trace("Trusting identity for {} for {}: {}", recipientId, direction, isTrusted);
|
||||
logger.trace("Trusting identity for {} for {}: {}", serviceId, direction, isTrusted);
|
||||
return isTrusted;
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed read from identity store", e);
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityInfo getIdentityInfo(RecipientId recipientId) {
|
||||
public IdentityInfo getIdentityInfo(ServiceId serviceId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
return loadIdentity(connection, recipientId);
|
||||
return loadIdentity(connection, serviceId);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed read from identity store", e);
|
||||
}
|
||||
|
@ -155,7 +148,7 @@ public class IdentityKeyStore {
|
|||
try (final var connection = database.getConnection()) {
|
||||
final var sql = (
|
||||
"""
|
||||
SELECT i.recipient_id, i.identity_key, i.added_timestamp, i.trust_level
|
||||
SELECT i.uuid, i.identity_key, i.added_timestamp, i.trust_level
|
||||
FROM %s AS i
|
||||
"""
|
||||
).formatted(TABLE_IDENTITY);
|
||||
|
@ -167,32 +160,9 @@ public class IdentityKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
public void mergeRecipients(final RecipientId recipientId, final RecipientId toBeMergedRecipientId) {
|
||||
public void deleteIdentity(final ServiceId serviceId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
final var sql = (
|
||||
"""
|
||||
UPDATE OR IGNORE %s
|
||||
SET recipient_id = ?
|
||||
WHERE recipient_id = ?
|
||||
"""
|
||||
).formatted(TABLE_IDENTITY);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setLong(2, toBeMergedRecipientId.id());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
|
||||
deleteIdentity(connection, toBeMergedRecipientId);
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed update identity store", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteIdentity(final RecipientId recipientId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
deleteIdentity(connection, recipientId);
|
||||
deleteIdentity(connection, serviceId);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed update identity store", e);
|
||||
}
|
||||
|
@ -214,49 +184,49 @@ public class IdentityKeyStore {
|
|||
}
|
||||
|
||||
private IdentityInfo loadIdentity(
|
||||
final Connection connection, final RecipientId recipientId
|
||||
final Connection connection, final ServiceId serviceId
|
||||
) throws SQLException {
|
||||
final var sql = (
|
||||
"""
|
||||
SELECT i.recipient_id, i.identity_key, i.added_timestamp, i.trust_level
|
||||
SELECT i.uuid, i.identity_key, i.added_timestamp, i.trust_level
|
||||
FROM %s AS i
|
||||
WHERE i.recipient_id = ?
|
||||
WHERE i.uuid = ?
|
||||
"""
|
||||
).formatted(TABLE_IDENTITY);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setBytes(1, serviceId.toByteArray());
|
||||
return Utils.executeQueryForOptional(statement, this::getIdentityInfoFromResultSet).orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveNewIdentity(
|
||||
final Connection connection,
|
||||
final RecipientId recipientId,
|
||||
final ServiceId serviceId,
|
||||
final IdentityKey identityKey,
|
||||
final boolean firstIdentity
|
||||
) throws SQLException {
|
||||
final var trustLevel = trustNewIdentity == TrustNewIdentity.ALWAYS || (
|
||||
trustNewIdentity == TrustNewIdentity.ON_FIRST_USE && firstIdentity
|
||||
) ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED;
|
||||
logger.debug("Storing new identity for recipient {} with trust {}", recipientId, trustLevel);
|
||||
final var newIdentityInfo = new IdentityInfo(recipientId, identityKey, trustLevel, System.currentTimeMillis());
|
||||
logger.debug("Storing new identity for recipient {} with trust {}", serviceId, trustLevel);
|
||||
final var newIdentityInfo = new IdentityInfo(serviceId, identityKey, trustLevel, System.currentTimeMillis());
|
||||
storeIdentity(connection, newIdentityInfo);
|
||||
identityChanges.onNext(recipientId);
|
||||
identityChanges.onNext(serviceId);
|
||||
}
|
||||
|
||||
private void storeIdentity(final Connection connection, final IdentityInfo identityInfo) throws SQLException {
|
||||
logger.trace("Storing identity info for {}, trust: {}, added: {}",
|
||||
identityInfo.getRecipientId(),
|
||||
identityInfo.getServiceId(),
|
||||
identityInfo.getTrustLevel(),
|
||||
identityInfo.getDateAddedTimestamp());
|
||||
final var sql = (
|
||||
"""
|
||||
INSERT OR REPLACE INTO %s (recipient_id, identity_key, added_timestamp, trust_level)
|
||||
INSERT OR REPLACE INTO %s (uuid, identity_key, added_timestamp, trust_level)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
).formatted(TABLE_IDENTITY);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, identityInfo.getRecipientId().id());
|
||||
statement.setBytes(1, identityInfo.getServiceId().toByteArray());
|
||||
statement.setBytes(2, identityInfo.getIdentityKey().serialize());
|
||||
statement.setLong(3, identityInfo.getDateAddedTimestamp());
|
||||
statement.setInt(4, identityInfo.getTrustLevel().ordinal());
|
||||
|
@ -264,27 +234,27 @@ public class IdentityKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
private void deleteIdentity(final Connection connection, final RecipientId recipientId) throws SQLException {
|
||||
private void deleteIdentity(final Connection connection, final ServiceId serviceId) throws SQLException {
|
||||
final var sql = (
|
||||
"""
|
||||
DELETE FROM %s AS i
|
||||
WHERE i.recipient_id = ?
|
||||
WHERE i.uuid = ?
|
||||
"""
|
||||
).formatted(TABLE_IDENTITY);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setBytes(1, serviceId.toByteArray());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private IdentityInfo getIdentityInfoFromResultSet(ResultSet resultSet) throws SQLException {
|
||||
try {
|
||||
final var recipientId = recipientIdCreator.create(resultSet.getLong("recipient_id"));
|
||||
final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
|
||||
final var id = new IdentityKey(resultSet.getBytes("identity_key"));
|
||||
final var trustLevel = TrustLevel.fromInt(resultSet.getInt("trust_level"));
|
||||
final var added = resultSet.getLong("added_timestamp");
|
||||
|
||||
return new IdentityInfo(recipientId, id, trustLevel, added);
|
||||
return new IdentityInfo(serviceId, id, trustLevel, added);
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.warn("Failed to load identity key, resetting: {}", e.getMessage());
|
||||
return null;
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.asamk.signal.manager.storage.identities;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.asamk.signal.manager.api.TrustLevel;
|
||||
import org.asamk.signal.manager.helper.RecipientAddressResolver;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.asamk.signal.manager.util.IOUtils;
|
||||
|
@ -27,16 +28,21 @@ public class LegacyIdentityKeyStore {
|
|||
private static final ObjectMapper objectMapper = org.asamk.signal.manager.storage.Utils.createStorageObjectMapper();
|
||||
|
||||
public static void migrate(
|
||||
final File identitiesPath, final RecipientResolver resolver, final IdentityKeyStore identityKeyStore
|
||||
final File identitiesPath,
|
||||
final RecipientResolver resolver,
|
||||
final RecipientAddressResolver addressResolver,
|
||||
final IdentityKeyStore identityKeyStore
|
||||
) {
|
||||
final var identities = getIdentities(identitiesPath, resolver);
|
||||
final var identities = getIdentities(identitiesPath, resolver, addressResolver);
|
||||
identityKeyStore.addLegacyIdentities(identities);
|
||||
removeIdentityFiles(identitiesPath);
|
||||
}
|
||||
|
||||
static final Pattern identityFileNamePattern = Pattern.compile("(\\d+)");
|
||||
|
||||
private static List<IdentityInfo> getIdentities(final File identitiesPath, final RecipientResolver resolver) {
|
||||
private static List<IdentityInfo> getIdentities(
|
||||
final File identitiesPath, final RecipientResolver resolver, final RecipientAddressResolver addressResolver
|
||||
) {
|
||||
final var files = identitiesPath.listFiles();
|
||||
if (files == null) {
|
||||
return List.of();
|
||||
|
@ -45,7 +51,7 @@ public class LegacyIdentityKeyStore {
|
|||
.filter(f -> identityFileNamePattern.matcher(f.getName()).matches())
|
||||
.map(f -> resolver.resolveRecipient(Long.parseLong(f.getName())))
|
||||
.filter(Objects::nonNull)
|
||||
.map(recipientId -> loadIdentityLocked(recipientId, identitiesPath))
|
||||
.map(recipientId -> loadIdentityLocked(recipientId, addressResolver, identitiesPath))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
}
|
||||
|
@ -59,7 +65,9 @@ public class LegacyIdentityKeyStore {
|
|||
return new File(identitiesPath, String.valueOf(recipientId.id()));
|
||||
}
|
||||
|
||||
private static IdentityInfo loadIdentityLocked(final RecipientId recipientId, final File identitiesPath) {
|
||||
private static IdentityInfo loadIdentityLocked(
|
||||
final RecipientId recipientId, RecipientAddressResolver addressResolver, final File identitiesPath
|
||||
) {
|
||||
final var file = getIdentityFile(recipientId, identitiesPath);
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
|
@ -71,7 +79,8 @@ public class LegacyIdentityKeyStore {
|
|||
var trustLevel = TrustLevel.fromInt(storage.trustLevel());
|
||||
var added = storage.addedTimestamp();
|
||||
|
||||
return new IdentityInfo(recipientId, id, trustLevel, added);
|
||||
final var serviceId = addressResolver.resolveRecipientAddress(recipientId).getServiceId();
|
||||
return new IdentityInfo(serviceId, id, trustLevel, added);
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
logger.warn("Failed to load identity key: {}", e.getMessage());
|
||||
return null;
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
package org.asamk.signal.manager.storage.identities;
|
||||
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.state.IdentityKeyStore {
|
||||
|
||||
private final RecipientResolver resolver;
|
||||
private final Supplier<IdentityKeyPair> identityKeyPairSupplier;
|
||||
private final int localRegistrationId;
|
||||
private final IdentityKeyStore identityKeyStore;
|
||||
|
@ -21,7 +20,6 @@ public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.sta
|
|||
final int localRegistrationId,
|
||||
final IdentityKeyStore identityKeyStore
|
||||
) {
|
||||
this.resolver = resolver;
|
||||
this.identityKeyPairSupplier = identityKeyPairSupplier;
|
||||
this.localRegistrationId = localRegistrationId;
|
||||
this.identityKeyStore = identityKeyStore;
|
||||
|
@ -39,29 +37,22 @@ public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.sta
|
|||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
final var recipientId = resolveRecipient(address.getName());
|
||||
final var serviceId = ServiceId.parseOrThrow(address.getName());
|
||||
|
||||
return identityKeyStore.saveIdentity(recipientId, identityKey);
|
||||
return identityKeyStore.saveIdentity(serviceId, identityKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
|
||||
var recipientId = resolveRecipient(address.getName());
|
||||
final var serviceId = ServiceId.parseOrThrow(address.getName());
|
||||
|
||||
return identityKeyStore.isTrustedIdentity(recipientId, identityKey, direction);
|
||||
return identityKeyStore.isTrustedIdentity(serviceId, identityKey, direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey getIdentity(SignalProtocolAddress address) {
|
||||
var recipientId = resolveRecipient(address.getName());
|
||||
final var identityInfo = identityKeyStore.getIdentityInfo(recipientId);
|
||||
final var serviceId = ServiceId.parseOrThrow(address.getName());
|
||||
final var identityInfo = identityKeyStore.getIdentityInfo(serviceId);
|
||||
return identityInfo == null ? null : identityInfo.getIdentityKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param identifier can be either a serialized uuid or an e164 phone number
|
||||
*/
|
||||
private RecipientId resolveRecipient(String identifier) {
|
||||
return resolver.resolveRecipient(identifier);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,10 @@ public record RecipientAddress(Optional<UUID> uuid, Optional<String> number) {
|
|||
this(Optional.of(uuid), Optional.empty());
|
||||
}
|
||||
|
||||
public ServiceId getServiceId() {
|
||||
return ServiceId.from(uuid.orElse(UNKNOWN_UUID));
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
if (uuid.isPresent()) {
|
||||
return uuid.get().toString();
|
||||
|
@ -62,6 +66,6 @@ public record RecipientAddress(Optional<UUID> uuid, Optional<String> number) {
|
|||
}
|
||||
|
||||
public SignalServiceAddress toSignalServiceAddress() {
|
||||
return new SignalServiceAddress(ServiceId.from(uuid.orElse(UNKNOWN_UUID)), number);
|
||||
return new SignalServiceAddress(getServiceId(), number);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,13 @@ import org.asamk.signal.manager.groups.GroupId;
|
|||
import org.asamk.signal.manager.groups.GroupUtils;
|
||||
import org.asamk.signal.manager.storage.Database;
|
||||
import org.asamk.signal.manager.storage.Utils;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -32,14 +31,10 @@ public class MessageSendLogStore implements AutoCloseable {
|
|||
|
||||
private static final Duration LOG_DURATION = Duration.ofDays(1);
|
||||
|
||||
private final RecipientResolver recipientResolver;
|
||||
private final Database database;
|
||||
private final Thread cleanupThread;
|
||||
|
||||
public MessageSendLogStore(
|
||||
final RecipientResolver recipientResolver, final Database database
|
||||
) {
|
||||
this.recipientResolver = recipientResolver;
|
||||
public MessageSendLogStore(final Database database) {
|
||||
this.database = database;
|
||||
this.cleanupThread = new Thread(() -> {
|
||||
try {
|
||||
|
@ -69,9 +64,9 @@ public class MessageSendLogStore implements AutoCloseable {
|
|||
CREATE TABLE message_send_log (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
content_id INTEGER NOT NULL REFERENCES message_send_log_content (_id) ON DELETE CASCADE,
|
||||
recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
|
||||
uuid BLOB NOT NULL,
|
||||
device_id INTEGER NOT NULL
|
||||
);
|
||||
) STRICT;
|
||||
CREATE TABLE message_send_log_content (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
group_id BLOB,
|
||||
|
@ -81,26 +76,26 @@ public class MessageSendLogStore implements AutoCloseable {
|
|||
urgent BOOLEAN NOT NULL
|
||||
);
|
||||
CREATE INDEX mslc_timestamp_index ON message_send_log_content (timestamp);
|
||||
CREATE INDEX msl_recipient_index ON message_send_log (recipient_id, device_id, content_id);
|
||||
CREATE INDEX msl_recipient_index ON message_send_log (uuid, device_id, content_id);
|
||||
CREATE INDEX msl_content_index ON message_send_log (content_id);
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
public List<MessageSendLogEntry> findMessages(
|
||||
final RecipientId recipientId, final int deviceId, final long timestamp, final boolean isSenderKey
|
||||
final ServiceId serviceId, final int deviceId, final long timestamp, final boolean isSenderKey
|
||||
) {
|
||||
final var sql = """
|
||||
SELECT group_id, content, content_hint
|
||||
FROM %s l
|
||||
INNER JOIN %s lc ON l.content_id = lc._id
|
||||
WHERE l.recipient_id = ? AND l.device_id = ? AND lc.timestamp = ?
|
||||
WHERE l.uuid = ? AND l.device_id = ? AND lc.timestamp = ?
|
||||
""".formatted(TABLE_MESSAGE_SEND_LOG, TABLE_MESSAGE_SEND_LOG_CONTENT);
|
||||
try (final var connection = database.getConnection()) {
|
||||
deleteOutdatedEntries(connection);
|
||||
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setBytes(1, serviceId.toByteArray());
|
||||
statement.setInt(2, deviceId);
|
||||
statement.setLong(3, timestamp);
|
||||
try (var result = Utils.executeQueryForStream(statement, this::getMessageSendLogEntryFromResultSet)) {
|
||||
|
@ -189,16 +184,16 @@ public class MessageSendLogStore implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
public void deleteEntryForRecipientNonGroup(long sentTimestamp, RecipientId recipientId) {
|
||||
public void deleteEntryForRecipientNonGroup(long sentTimestamp, ServiceId serviceId) {
|
||||
final var sql = """
|
||||
DELETE FROM %s AS lc
|
||||
WHERE lc.timestamp = ? AND lc.group_id IS NULL AND lc._id IN (SELECT content_id FROM %s l WHERE l.recipient_id = ?)
|
||||
WHERE lc.timestamp = ? AND lc.group_id IS NULL AND lc._id IN (SELECT content_id FROM %s l WHERE l.uuid = ?)
|
||||
""".formatted(TABLE_MESSAGE_SEND_LOG_CONTENT, TABLE_MESSAGE_SEND_LOG);
|
||||
try (final var connection = database.getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, sentTimestamp);
|
||||
statement.setLong(2, recipientId.id());
|
||||
statement.setBytes(2, serviceId.toByteArray());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
|
||||
|
@ -209,21 +204,21 @@ public class MessageSendLogStore implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
public void deleteEntryForRecipient(long sentTimestamp, RecipientId recipientId, int deviceId) {
|
||||
deleteEntriesForRecipient(List.of(sentTimestamp), recipientId, deviceId);
|
||||
public void deleteEntryForRecipient(long sentTimestamp, ServiceId serviceId, int deviceId) {
|
||||
deleteEntriesForRecipient(List.of(sentTimestamp), serviceId, deviceId);
|
||||
}
|
||||
|
||||
public void deleteEntriesForRecipient(List<Long> sentTimestamps, RecipientId recipientId, int deviceId) {
|
||||
public void deleteEntriesForRecipient(List<Long> sentTimestamps, ServiceId serviceId, int deviceId) {
|
||||
final var sql = """
|
||||
DELETE FROM %s AS l
|
||||
WHERE l.content_id IN (SELECT _id FROM %s lc WHERE lc.timestamp = ?) AND l.recipient_id = ? AND l.device_id = ?
|
||||
WHERE l.content_id IN (SELECT _id FROM %s lc WHERE lc.timestamp = ?) AND l.uuid = ? AND l.device_id = ?
|
||||
""".formatted(TABLE_MESSAGE_SEND_LOG, TABLE_MESSAGE_SEND_LOG_CONTENT);
|
||||
try (final var connection = database.getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
for (final var sentTimestamp : sentTimestamps) {
|
||||
statement.setLong(1, sentTimestamp);
|
||||
statement.setLong(2, recipientId.id());
|
||||
statement.setBytes(2, serviceId.toByteArray());
|
||||
statement.setInt(3, deviceId);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
|
@ -247,8 +242,8 @@ public class MessageSendLogStore implements AutoCloseable {
|
|||
|
||||
private RecipientDevices getRecipientDevices(final SendMessageResult sendMessageResult) {
|
||||
if (sendMessageResult.isSuccess() && sendMessageResult.getSuccess().getContent().isPresent()) {
|
||||
final var recipientId = recipientResolver.resolveRecipient(sendMessageResult.getAddress());
|
||||
return new RecipientDevices(recipientId, sendMessageResult.getSuccess().getDevices());
|
||||
final var serviceId = sendMessageResult.getAddress().getServiceId();
|
||||
return new RecipientDevices(serviceId, sendMessageResult.getSuccess().getDevices());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -332,13 +327,13 @@ public class MessageSendLogStore implements AutoCloseable {
|
|||
final long contentId, final List<RecipientDevices> recipientDevices, final Connection connection
|
||||
) throws SQLException {
|
||||
final var sql = """
|
||||
INSERT INTO %s (recipient_id, device_id, content_id)
|
||||
INSERT INTO %s (uuid, device_id, content_id)
|
||||
VALUES (?,?,?)
|
||||
""".formatted(TABLE_MESSAGE_SEND_LOG);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
for (final var recipientDevice : recipientDevices) {
|
||||
for (final var deviceId : recipientDevice.deviceIds()) {
|
||||
statement.setLong(1, recipientDevice.recipientId().id());
|
||||
statement.setBytes(1, recipientDevice.serviceId().toByteArray());
|
||||
statement.setInt(2, deviceId);
|
||||
statement.setLong(3, contentId);
|
||||
statement.executeUpdate();
|
||||
|
@ -387,5 +382,5 @@ public class MessageSendLogStore implements AutoCloseable {
|
|||
return new MessageSendLogEntry(groupId, content, contentHint, urgent);
|
||||
}
|
||||
|
||||
private record RecipientDevices(RecipientId recipientId, List<Integer> deviceIds) {}
|
||||
private record RecipientDevices(ServiceId serviceId, List<Integer> deviceIds) {}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package org.asamk.signal.manager.storage.senderKeys;
|
||||
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.asamk.signal.manager.helper.RecipientAddressResolver;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -18,14 +21,15 @@ import java.util.UUID;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.asamk.signal.manager.storage.senderKeys.SenderKeyRecordStore.Key;
|
||||
|
||||
public class LegacySenderKeyRecordStore {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(LegacySenderKeyRecordStore.class);
|
||||
|
||||
public static void migrate(
|
||||
final File senderKeysPath, final RecipientResolver resolver, SenderKeyStore senderKeyStore
|
||||
final File senderKeysPath,
|
||||
final RecipientResolver resolver,
|
||||
final RecipientAddressResolver addressResolver,
|
||||
final SenderKeyStore senderKeyStore
|
||||
) {
|
||||
final var files = senderKeysPath.listFiles();
|
||||
if (files == null) {
|
||||
|
@ -34,10 +38,13 @@ public class LegacySenderKeyRecordStore {
|
|||
|
||||
final var senderKeys = parseFileNames(files, resolver).stream().map(key -> {
|
||||
final var record = loadSenderKeyLocked(key, senderKeysPath);
|
||||
if (record == null) {
|
||||
final var uuid = addressResolver.resolveRecipientAddress(key.recipientId).uuid();
|
||||
if (record == null || uuid.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new Pair<>(key, record);
|
||||
return new Pair<>(new SenderKeyRecordStore.Key(ServiceId.from(uuid.get()),
|
||||
key.deviceId,
|
||||
key.distributionId), record);
|
||||
}).filter(Objects::nonNull).toList();
|
||||
|
||||
senderKeyStore.addLegacySenderKeys(senderKeys);
|
||||
|
@ -98,4 +105,6 @@ public class LegacySenderKeyRecordStore {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
record Key(RecipientId recipientId, int deviceId, UUID distributionId) {}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package org.asamk.signal.manager.storage.senderKeys;
|
||||
|
||||
import org.asamk.signal.manager.helper.RecipientAddressResolver;
|
||||
import org.asamk.signal.manager.storage.Utils;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore.SenderKeySharedEntry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -21,7 +23,10 @@ public class LegacySenderKeySharedStore {
|
|||
private final static Logger logger = LoggerFactory.getLogger(LegacySenderKeySharedStore.class);
|
||||
|
||||
public static void migrate(
|
||||
final File file, final RecipientResolver resolver, SenderKeyStore senderKeyStore
|
||||
final File file,
|
||||
final RecipientResolver resolver,
|
||||
final RecipientAddressResolver addressResolver,
|
||||
final SenderKeyStore senderKeyStore
|
||||
) {
|
||||
final var objectMapper = Utils.createStorageObjectMapper();
|
||||
try (var inputStream = new FileInputStream(file)) {
|
||||
|
@ -32,7 +37,11 @@ public class LegacySenderKeySharedStore {
|
|||
if (recipientId == null) {
|
||||
continue;
|
||||
}
|
||||
final var entry = new SenderKeySharedEntry(recipientId, senderKey.deviceId);
|
||||
final var uuid = addressResolver.resolveRecipientAddress(recipientId).uuid();
|
||||
if (uuid.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final var entry = new SenderKeySharedEntry(ServiceId.from(uuid.get()), senderKey.deviceId);
|
||||
final var distributionId = DistributionId.from(senderKey.distributionId);
|
||||
var entries = sharedSenderKeys.get(distributionId);
|
||||
if (entries == null) {
|
||||
|
|
|
@ -3,14 +3,13 @@ package org.asamk.signal.manager.storage.senderKeys;
|
|||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.asamk.signal.manager.storage.Database;
|
||||
import org.asamk.signal.manager.storage.Utils;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord;
|
||||
import org.signal.libsignal.protocol.groups.state.SenderKeyStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
@ -25,7 +24,6 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
private final static String TABLE_SENDER_KEY = "sender_key";
|
||||
|
||||
private final Database database;
|
||||
private final RecipientResolver resolver;
|
||||
|
||||
public static void createSql(Connection connection) throws SQLException {
|
||||
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java
|
||||
|
@ -33,22 +31,19 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
statement.executeUpdate("""
|
||||
CREATE TABLE sender_key (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
|
||||
uuid BLOB NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
distribution_id BLOB NOT NULL,
|
||||
record BLOB NOT NULL,
|
||||
created_timestamp INTEGER NOT NULL,
|
||||
UNIQUE(recipient_id, device_id, distribution_id)
|
||||
);
|
||||
UNIQUE(uuid, device_id, distribution_id)
|
||||
) STRICT;
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
SenderKeyRecordStore(
|
||||
final Database database, final RecipientResolver resolver
|
||||
) {
|
||||
SenderKeyRecordStore(final Database database) {
|
||||
this.database = database;
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,17 +70,17 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
long getCreateTimeForKey(final RecipientId selfRecipientId, final int selfDeviceId, final UUID distributionId) {
|
||||
long getCreateTimeForKey(final ServiceId selfServiceId, final int selfDeviceId, final UUID distributionId) {
|
||||
final var sql = (
|
||||
"""
|
||||
SELECT s.created_timestamp
|
||||
FROM %s AS s
|
||||
WHERE s.recipient_id = ? AND s.device_id = ? AND s.distribution_id = ?
|
||||
WHERE s.uuid = ? AND s.device_id = ? AND s.distribution_id = ?
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY);
|
||||
try (final var connection = database.getConnection()) {
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, selfRecipientId.id());
|
||||
statement.setBytes(1, selfServiceId.toByteArray());
|
||||
statement.setInt(2, selfDeviceId);
|
||||
statement.setBytes(3, UuidUtil.toByteArray(distributionId));
|
||||
return Utils.executeQueryForOptional(statement, res -> res.getLong("created_timestamp")).orElse(-1L);
|
||||
|
@ -95,16 +90,16 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
void deleteSenderKey(final RecipientId recipientId, final UUID distributionId) {
|
||||
void deleteSenderKey(final ServiceId serviceId, final UUID distributionId) {
|
||||
final var sql = (
|
||||
"""
|
||||
DELETE FROM %s AS s
|
||||
WHERE s.recipient_id = ? AND s.distribution_id = ?
|
||||
WHERE s.uuid = ? AND s.distribution_id = ?
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY);
|
||||
try (final var connection = database.getConnection()) {
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setBytes(1, serviceId.toByteArray());
|
||||
statement.setBytes(2, UuidUtil.toByteArray(distributionId));
|
||||
statement.executeUpdate();
|
||||
}
|
||||
|
@ -126,33 +121,9 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
void deleteAllFor(final RecipientId recipientId) {
|
||||
void deleteAllFor(final ServiceId serviceId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
deleteAllFor(connection, recipientId);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed update sender key store", e);
|
||||
}
|
||||
}
|
||||
|
||||
void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
final var sql = """
|
||||
UPDATE OR IGNORE %s
|
||||
SET recipient_id = ?
|
||||
WHERE recipient_id = ?
|
||||
""".formatted(TABLE_SENDER_KEY);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setLong(2, toBeMergedRecipientId.id());
|
||||
final var rows = statement.executeUpdate();
|
||||
if (rows > 0) {
|
||||
logger.debug("Reassigned {} sender keys of to be merged recipient.", rows);
|
||||
}
|
||||
}
|
||||
// Delete all conflicting sender keys now
|
||||
deleteAllFor(connection, toBeMergedRecipientId);
|
||||
connection.commit();
|
||||
deleteAllFor(connection, serviceId);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed update sender key store", e);
|
||||
}
|
||||
|
@ -173,16 +144,9 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
logger.debug("Complete sender keys migration took {}ms", (System.nanoTime() - start) / 1000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param identifier can be either a serialized uuid or an e164 phone number
|
||||
*/
|
||||
private RecipientId resolveRecipient(String identifier) {
|
||||
return resolver.resolveRecipient(identifier);
|
||||
}
|
||||
|
||||
private Key getKey(final SignalProtocolAddress address, final UUID distributionId) {
|
||||
final var recipientId = resolveRecipient(address.getName());
|
||||
return new Key(recipientId, address.getDeviceId(), distributionId);
|
||||
final var serviceId = ServiceId.parseOrThrow(address.getName());
|
||||
return new Key(serviceId, address.getDeviceId(), distributionId);
|
||||
}
|
||||
|
||||
private SenderKeyRecord loadSenderKey(final Connection connection, final Key key) throws SQLException {
|
||||
|
@ -190,11 +154,11 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
"""
|
||||
SELECT s.record
|
||||
FROM %s AS s
|
||||
WHERE s.recipient_id = ? AND s.device_id = ? AND s.distribution_id = ?
|
||||
WHERE s.uuid = ? AND s.device_id = ? AND s.distribution_id = ?
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, key.recipientId().id());
|
||||
statement.setBytes(1, key.serviceId().toByteArray());
|
||||
statement.setInt(2, key.deviceId());
|
||||
statement.setBytes(3, UuidUtil.toByteArray(key.distributionId()));
|
||||
return Utils.executeQueryForOptional(statement, this::getSenderKeyRecordFromResultSet).orElse(null);
|
||||
|
@ -207,11 +171,11 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
final var sqlUpdate = """
|
||||
UPDATE %s
|
||||
SET record = ?
|
||||
WHERE recipient_id = ? AND device_id = ? and distribution_id = ?
|
||||
WHERE uuid = ? AND device_id = ? and distribution_id = ?
|
||||
""".formatted(TABLE_SENDER_KEY);
|
||||
try (final var statement = connection.prepareStatement(sqlUpdate)) {
|
||||
statement.setBytes(1, senderKeyRecord.serialize());
|
||||
statement.setLong(2, key.recipientId().id());
|
||||
statement.setBytes(2, key.serviceId().toByteArray());
|
||||
statement.setLong(3, key.deviceId());
|
||||
statement.setBytes(4, UuidUtil.toByteArray(key.distributionId()));
|
||||
final var rows = statement.executeUpdate();
|
||||
|
@ -223,12 +187,12 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
// Record doesn't exist yet, creating a new one
|
||||
final var sqlInsert = (
|
||||
"""
|
||||
INSERT OR REPLACE INTO %s (recipient_id, device_id, distribution_id, record, created_timestamp)
|
||||
INSERT OR REPLACE INTO %s (uuid, device_id, distribution_id, record, created_timestamp)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY);
|
||||
try (final var statement = connection.prepareStatement(sqlInsert)) {
|
||||
statement.setLong(1, key.recipientId().id());
|
||||
statement.setBytes(1, key.serviceId().toByteArray());
|
||||
statement.setInt(2, key.deviceId());
|
||||
statement.setBytes(3, UuidUtil.toByteArray(key.distributionId()));
|
||||
statement.setBytes(4, senderKeyRecord.serialize());
|
||||
|
@ -237,15 +201,15 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
private void deleteAllFor(final Connection connection, final RecipientId recipientId) throws SQLException {
|
||||
private void deleteAllFor(final Connection connection, final ServiceId serviceId) throws SQLException {
|
||||
final var sql = (
|
||||
"""
|
||||
DELETE FROM %s AS s
|
||||
WHERE s.recipient_id = ?
|
||||
WHERE s.uuid = ?
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setBytes(1, serviceId.toByteArray());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -261,5 +225,5 @@ public class SenderKeyRecordStore implements SenderKeyStore {
|
|||
}
|
||||
}
|
||||
|
||||
record Key(RecipientId recipientId, int deviceId, UUID distributionId) {}
|
||||
record Key(ServiceId serviceId, int deviceId, UUID distributionId) {}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
package org.asamk.signal.manager.storage.senderKeys;
|
||||
|
||||
import org.asamk.signal.manager.helper.RecipientAddressResolver;
|
||||
import org.asamk.signal.manager.storage.Database;
|
||||
import org.asamk.signal.manager.storage.Utils;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
@ -26,9 +23,6 @@ public class SenderKeySharedStore {
|
|||
private final static String TABLE_SENDER_KEY_SHARED = "sender_key_shared";
|
||||
|
||||
private final Database database;
|
||||
private final RecipientIdCreator recipientIdCreator;
|
||||
private final RecipientResolver resolver;
|
||||
private final RecipientAddressResolver addressResolver;
|
||||
|
||||
public static void createSql(Connection connection) throws SQLException {
|
||||
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java
|
||||
|
@ -36,33 +30,25 @@ public class SenderKeySharedStore {
|
|||
statement.executeUpdate("""
|
||||
CREATE TABLE sender_key_shared (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
|
||||
uuid BLOB NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
distribution_id BLOB NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
UNIQUE(recipient_id, device_id, distribution_id)
|
||||
);
|
||||
UNIQUE(uuid, device_id, distribution_id)
|
||||
) STRICT;
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
SenderKeySharedStore(
|
||||
final Database database,
|
||||
final RecipientIdCreator recipientIdCreator,
|
||||
final RecipientAddressResolver addressResolver,
|
||||
final RecipientResolver resolver
|
||||
) {
|
||||
SenderKeySharedStore(final Database database) {
|
||||
this.database = database;
|
||||
this.recipientIdCreator = recipientIdCreator;
|
||||
this.addressResolver = addressResolver;
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
public Set<SignalProtocolAddress> getSenderKeySharedWith(final DistributionId distributionId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
final var sql = (
|
||||
"""
|
||||
SELECT s.recipient_id, s.device_id
|
||||
SELECT s.uuid, s.device_id
|
||||
FROM %s AS s
|
||||
WHERE s.distribution_id = ?
|
||||
"""
|
||||
|
@ -70,8 +56,7 @@ public class SenderKeySharedStore {
|
|||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setBytes(1, UuidUtil.toByteArray(distributionId.asUuid()));
|
||||
return Utils.executeQueryForStream(statement, this::getSenderKeySharedEntryFromResultSet)
|
||||
.map(k -> new SignalProtocolAddress(addressResolver.resolveRecipientAddress(k.recipientId())
|
||||
.getIdentifier(), k.deviceId()))
|
||||
.map(k -> k.serviceId.toProtocolAddress(k.deviceId()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
|
@ -83,7 +68,7 @@ public class SenderKeySharedStore {
|
|||
final DistributionId distributionId, final Collection<SignalProtocolAddress> addresses
|
||||
) {
|
||||
final var newEntries = addresses.stream()
|
||||
.map(a -> new SenderKeySharedEntry(resolver.resolveRecipient(a.getName()), a.getDeviceId()))
|
||||
.map(a -> new SenderKeySharedEntry(ServiceId.parseOrThrow(a.getName()), a.getDeviceId()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
try (final var connection = database.getConnection()) {
|
||||
|
@ -97,7 +82,8 @@ public class SenderKeySharedStore {
|
|||
|
||||
public void clearSenderKeySharedWith(final Collection<SignalProtocolAddress> addresses) {
|
||||
final var entriesToDelete = addresses.stream()
|
||||
.map(a -> new SenderKeySharedEntry(resolver.resolveRecipient(a.getName()), a.getDeviceId()))
|
||||
.filter(a -> UuidUtil.isUuid(a.getName()))
|
||||
.map(a -> new SenderKeySharedEntry(ServiceId.parseOrThrow(a.getName()), a.getDeviceId()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
try (final var connection = database.getConnection()) {
|
||||
|
@ -105,12 +91,12 @@ public class SenderKeySharedStore {
|
|||
final var sql = (
|
||||
"""
|
||||
DELETE FROM %s AS s
|
||||
WHERE recipient_id = ? AND device_id = ?
|
||||
WHERE uuid = ? AND device_id = ?
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY_SHARED);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
for (final var entry : entriesToDelete) {
|
||||
statement.setLong(1, entry.recipientId().id());
|
||||
statement.setBytes(1, entry.serviceId().toByteArray());
|
||||
statement.setInt(2, entry.deviceId());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
|
@ -136,16 +122,16 @@ public class SenderKeySharedStore {
|
|||
}
|
||||
}
|
||||
|
||||
public void deleteAllFor(final RecipientId recipientId) {
|
||||
public void deleteAllFor(final ServiceId serviceId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
final var sql = (
|
||||
"""
|
||||
DELETE FROM %s AS s
|
||||
WHERE recipient_id = ?
|
||||
WHERE uuid = ?
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY_SHARED);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setBytes(1, serviceId.toByteArray());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
|
@ -154,17 +140,17 @@ public class SenderKeySharedStore {
|
|||
}
|
||||
|
||||
public void deleteSharedWith(
|
||||
final RecipientId recipientId, final int deviceId, final DistributionId distributionId
|
||||
final ServiceId serviceId, final int deviceId, final DistributionId distributionId
|
||||
) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
final var sql = (
|
||||
"""
|
||||
DELETE FROM %s AS s
|
||||
WHERE recipient_id = ? AND device_id = ? AND distribution_id = ?
|
||||
WHERE uuid = ? AND device_id = ? AND distribution_id = ?
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY_SHARED);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setBytes(1, serviceId.toByteArray());
|
||||
statement.setInt(2, deviceId);
|
||||
statement.setBytes(3, UuidUtil.toByteArray(distributionId.asUuid()));
|
||||
statement.executeUpdate();
|
||||
|
@ -191,25 +177,6 @@ public class SenderKeySharedStore {
|
|||
}
|
||||
}
|
||||
|
||||
public void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
final var sql = (
|
||||
"""
|
||||
UPDATE OR REPLACE %s
|
||||
SET recipient_id = ?
|
||||
WHERE recipient_id = ?
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY_SHARED);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setLong(2, toBeMergedRecipientId.id());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed update shared sender key store", e);
|
||||
}
|
||||
}
|
||||
|
||||
void addLegacySenderKeysShared(final Map<DistributionId, Set<SenderKeySharedEntry>> sharedSenderKeys) {
|
||||
logger.debug("Migrating legacy sender keys shared to database");
|
||||
long start = System.nanoTime();
|
||||
|
@ -230,13 +197,13 @@ public class SenderKeySharedStore {
|
|||
) throws SQLException {
|
||||
final var sql = (
|
||||
"""
|
||||
INSERT OR REPLACE INTO %s (recipient_id, device_id, distribution_id, timestamp)
|
||||
INSERT OR REPLACE INTO %s (uuid, device_id, distribution_id, timestamp)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"""
|
||||
).formatted(TABLE_SENDER_KEY_SHARED);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
for (final var entry : newEntries) {
|
||||
statement.setLong(1, entry.recipientId().id());
|
||||
statement.setBytes(1, entry.serviceId().toByteArray());
|
||||
statement.setInt(2, entry.deviceId());
|
||||
statement.setBytes(3, UuidUtil.toByteArray(distributionId.asUuid()));
|
||||
statement.setLong(4, System.currentTimeMillis());
|
||||
|
@ -246,10 +213,10 @@ public class SenderKeySharedStore {
|
|||
}
|
||||
|
||||
private SenderKeySharedEntry getSenderKeySharedEntryFromResultSet(ResultSet resultSet) throws SQLException {
|
||||
final var recipientId = resultSet.getLong("recipient_id");
|
||||
final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
|
||||
final var deviceId = resultSet.getInt("device_id");
|
||||
return new SenderKeySharedEntry(recipientIdCreator.create(recipientId), deviceId);
|
||||
return new SenderKeySharedEntry(serviceId, deviceId);
|
||||
}
|
||||
|
||||
record SenderKeySharedEntry(RecipientId recipientId, int deviceId) {}
|
||||
record SenderKeySharedEntry(ServiceId serviceId, int deviceId) {}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
package org.asamk.signal.manager.storage.senderKeys;
|
||||
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.asamk.signal.manager.helper.RecipientAddressResolver;
|
||||
import org.asamk.signal.manager.storage.Database;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord;
|
||||
import org.whispersystems.signalservice.api.SignalServiceSenderKeyStore;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
@ -21,14 +18,9 @@ public class SenderKeyStore implements SignalServiceSenderKeyStore {
|
|||
private final SenderKeyRecordStore senderKeyRecordStore;
|
||||
private final SenderKeySharedStore senderKeySharedStore;
|
||||
|
||||
public SenderKeyStore(
|
||||
final Database database,
|
||||
final RecipientAddressResolver addressResolver,
|
||||
final RecipientResolver resolver,
|
||||
final RecipientIdCreator recipientIdCreator
|
||||
) {
|
||||
this.senderKeyRecordStore = new SenderKeyRecordStore(database, resolver);
|
||||
this.senderKeySharedStore = new SenderKeySharedStore(database, recipientIdCreator, addressResolver, resolver);
|
||||
public SenderKeyStore(final Database database) {
|
||||
this.senderKeyRecordStore = new SenderKeyRecordStore(database);
|
||||
this.senderKeySharedStore = new SenderKeySharedStore(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,31 +57,26 @@ public class SenderKeyStore implements SignalServiceSenderKeyStore {
|
|||
senderKeyRecordStore.deleteAll();
|
||||
}
|
||||
|
||||
public void deleteAll(RecipientId recipientId) {
|
||||
senderKeySharedStore.deleteAllFor(recipientId);
|
||||
senderKeyRecordStore.deleteAllFor(recipientId);
|
||||
public void deleteAll(ServiceId serviceId) {
|
||||
senderKeySharedStore.deleteAllFor(serviceId);
|
||||
senderKeyRecordStore.deleteAllFor(serviceId);
|
||||
}
|
||||
|
||||
public void deleteSharedWith(RecipientId recipientId) {
|
||||
senderKeySharedStore.deleteAllFor(recipientId);
|
||||
public void deleteSharedWith(ServiceId serviceId) {
|
||||
senderKeySharedStore.deleteAllFor(serviceId);
|
||||
}
|
||||
|
||||
public void deleteSharedWith(RecipientId recipientId, int deviceId, DistributionId distributionId) {
|
||||
senderKeySharedStore.deleteSharedWith(recipientId, deviceId, distributionId);
|
||||
public void deleteSharedWith(ServiceId serviceId, int deviceId, DistributionId distributionId) {
|
||||
senderKeySharedStore.deleteSharedWith(serviceId, deviceId, distributionId);
|
||||
}
|
||||
|
||||
public void deleteOurKey(RecipientId selfRecipientId, DistributionId distributionId) {
|
||||
public void deleteOurKey(ServiceId selfServiceId, DistributionId distributionId) {
|
||||
senderKeySharedStore.deleteAllFor(distributionId);
|
||||
senderKeyRecordStore.deleteSenderKey(selfRecipientId, distributionId.asUuid());
|
||||
senderKeyRecordStore.deleteSenderKey(selfServiceId, distributionId.asUuid());
|
||||
}
|
||||
|
||||
public long getCreateTimeForOurKey(RecipientId selfRecipientId, int deviceId, DistributionId distributionId) {
|
||||
return senderKeyRecordStore.getCreateTimeForKey(selfRecipientId, deviceId, distributionId.asUuid());
|
||||
}
|
||||
|
||||
public void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
|
||||
senderKeySharedStore.mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
senderKeyRecordStore.mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||
public long getCreateTimeForOurKey(ServiceId selfServiceId, int deviceId, DistributionId distributionId) {
|
||||
return senderKeyRecordStore.getCreateTimeForKey(selfServiceId, deviceId, distributionId.asUuid());
|
||||
}
|
||||
|
||||
void addLegacySenderKeys(final Collection<Pair<SenderKeyRecordStore.Key, SenderKeyRecord>> senderKeys) {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package org.asamk.signal.manager.storage.sessions;
|
||||
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.asamk.signal.manager.helper.RecipientAddressResolver;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.asamk.signal.manager.storage.sessions.SessionStore.Key;
|
||||
import org.asamk.signal.manager.util.IOUtils;
|
||||
import org.signal.libsignal.protocol.state.SessionRecord;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -24,15 +26,19 @@ public class LegacySessionStore {
|
|||
private final static Logger logger = LoggerFactory.getLogger(LegacySessionStore.class);
|
||||
|
||||
public static void migrate(
|
||||
final File sessionsPath, final RecipientResolver resolver, final SessionStore sessionStore
|
||||
final File sessionsPath,
|
||||
final RecipientResolver resolver,
|
||||
final RecipientAddressResolver addressResolver,
|
||||
final SessionStore sessionStore
|
||||
) {
|
||||
final var keys = getKeysLocked(sessionsPath, resolver);
|
||||
final var sessions = keys.stream().map(key -> {
|
||||
final var record = loadSessionLocked(key, sessionsPath);
|
||||
if (record == null) {
|
||||
final var uuid = addressResolver.resolveRecipientAddress(key.recipientId).uuid();
|
||||
if (record == null || uuid.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new Pair<>(key, record);
|
||||
return new Pair<>(new SessionStore.Key(ServiceId.from(uuid.get()), key.deviceId()), record);
|
||||
}).filter(Objects::nonNull).toList();
|
||||
sessionStore.addLegacySessions(sessions);
|
||||
deleteAllSessions(sessionsPath);
|
||||
|
@ -104,4 +110,6 @@ public class LegacySessionStore {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
record Key(RecipientId recipientId, int deviceId) {}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,6 @@ package org.asamk.signal.manager.storage.sessions;
|
|||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.asamk.signal.manager.storage.Database;
|
||||
import org.asamk.signal.manager.storage.Utils;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.NoSessionException;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
|
@ -15,7 +12,9 @@ import org.signal.libsignal.protocol.state.SessionRecord;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.SignalServiceSessionStore;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
|
@ -38,8 +37,6 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
|
||||
private final Database database;
|
||||
private final int accountIdType;
|
||||
private final RecipientResolver resolver;
|
||||
private final RecipientIdCreator recipientIdCreator;
|
||||
|
||||
public static void createSql(Connection connection) throws SQLException {
|
||||
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java
|
||||
|
@ -48,25 +45,18 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
CREATE TABLE session (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
account_id_type INTEGER NOT NULL,
|
||||
recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
|
||||
uuid BLOB NOT NULL,
|
||||
device_id INTEGER NOT NULL,
|
||||
record BLOB NOT NULL,
|
||||
UNIQUE(account_id_type, recipient_id, device_id)
|
||||
);
|
||||
UNIQUE(account_id_type, uuid, device_id)
|
||||
) STRICT;
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
public SessionStore(
|
||||
final Database database,
|
||||
final ServiceIdType serviceIdType,
|
||||
final RecipientResolver resolver,
|
||||
final RecipientIdCreator recipientIdCreator
|
||||
) {
|
||||
public SessionStore(final Database database, final ServiceIdType serviceIdType) {
|
||||
this.database = database;
|
||||
this.accountIdType = Utils.getAccountIdType(serviceIdType);
|
||||
this.resolver = resolver;
|
||||
this.recipientIdCreator = recipientIdCreator;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -111,19 +101,19 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
|
||||
@Override
|
||||
public List<Integer> getSubDeviceSessions(String name) {
|
||||
final var recipientId = resolver.resolveRecipient(name);
|
||||
final var serviceId = ServiceId.parseOrThrow(name);
|
||||
// get all sessions for recipient except primary device session
|
||||
final var sql = (
|
||||
"""
|
||||
SELECT s.device_id
|
||||
FROM %s AS s
|
||||
WHERE s.account_id_type = ? AND s.recipient_id = ? AND s.device_id != 1
|
||||
WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id != 1
|
||||
"""
|
||||
).formatted(TABLE_SESSION);
|
||||
try (final var connection = database.getConnection()) {
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setInt(1, accountIdType);
|
||||
statement.setLong(2, recipientId.id());
|
||||
statement.setBytes(2, serviceId.toByteArray());
|
||||
return Utils.executeQueryForStream(statement, res -> res.getInt("device_id")).toList();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
|
@ -131,8 +121,8 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isCurrentRatchetKey(RecipientId recipientId, int deviceId, ECPublicKey ratchetKey) {
|
||||
final var key = new Key(recipientId, deviceId);
|
||||
public boolean isCurrentRatchetKey(ServiceId serviceId, int deviceId, ECPublicKey ratchetKey) {
|
||||
final var key = new Key(serviceId, deviceId);
|
||||
|
||||
try (final var connection = database.getConnection()) {
|
||||
final var session = loadSession(connection, key);
|
||||
|
@ -181,13 +171,13 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
|
||||
@Override
|
||||
public void deleteAllSessions(String name) {
|
||||
final var recipientId = resolver.resolveRecipient(name);
|
||||
deleteAllSessions(recipientId);
|
||||
final var serviceId = ServiceId.parseOrThrow(name);
|
||||
deleteAllSessions(serviceId);
|
||||
}
|
||||
|
||||
public void deleteAllSessions(RecipientId recipientId) {
|
||||
public void deleteAllSessions(ServiceId serviceId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
deleteAllSessions(connection, recipientId);
|
||||
deleteAllSessions(connection, serviceId);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed update session store", e);
|
||||
}
|
||||
|
@ -195,6 +185,10 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
|
||||
@Override
|
||||
public void archiveSession(final SignalProtocolAddress address) {
|
||||
if (!UuidUtil.isUuid(address.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var key = getKey(address);
|
||||
|
||||
try (final var connection = database.getConnection()) {
|
||||
|
@ -212,19 +206,17 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
|
||||
@Override
|
||||
public Set<SignalProtocolAddress> getAllAddressesWithActiveSessions(final List<String> addressNames) {
|
||||
final var recipientIdToNameMap = addressNames.stream()
|
||||
.collect(Collectors.toMap(resolver::resolveRecipient, name -> name));
|
||||
final var recipientIdsCommaSeparated = recipientIdToNameMap.keySet()
|
||||
.stream()
|
||||
.map(recipientId -> String.valueOf(recipientId.id()))
|
||||
final var serviceIdsCommaSeparated = addressNames.stream()
|
||||
.map(ServiceId::parseOrThrow)
|
||||
.map(ServiceId::toString)
|
||||
.collect(Collectors.joining(","));
|
||||
final var sql = (
|
||||
"""
|
||||
SELECT s.recipient_id, s.device_id, s.record
|
||||
SELECT s.uuid, s.device_id, s.record
|
||||
FROM %s AS s
|
||||
WHERE s.account_id_type = ? AND s.recipient_id IN (%s)
|
||||
WHERE s.account_id_type = ? AND s.uuid IN (%s)
|
||||
"""
|
||||
).formatted(TABLE_SESSION, recipientIdsCommaSeparated);
|
||||
).formatted(TABLE_SESSION, serviceIdsCommaSeparated);
|
||||
try (final var connection = database.getConnection()) {
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setInt(1, accountIdType);
|
||||
|
@ -232,8 +224,7 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
res -> new Pair<>(getKeyFromResultSet(res), getSessionRecordFromResultSet(res)))
|
||||
.filter(pair -> isActive(pair.second()))
|
||||
.map(Pair::first)
|
||||
.map(key -> new SignalProtocolAddress(recipientIdToNameMap.get(key.recipientId),
|
||||
key.deviceId()))
|
||||
.map(key -> key.serviceId().toProtocolAddress(key.deviceId()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
|
@ -244,7 +235,7 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
public void archiveAllSessions() {
|
||||
final var sql = (
|
||||
"""
|
||||
SELECT s.recipient_id, s.device_id, s.record
|
||||
SELECT s.uuid, s.device_id, s.record
|
||||
FROM %s AS s
|
||||
WHERE s.account_id_type = ?
|
||||
"""
|
||||
|
@ -267,12 +258,12 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
}
|
||||
}
|
||||
|
||||
public void archiveSessions(final RecipientId recipientId) {
|
||||
public void archiveSessions(final ServiceId serviceId) {
|
||||
final var sql = (
|
||||
"""
|
||||
SELECT s.recipient_id, s.device_id, s.record
|
||||
SELECT s.uuid, s.device_id, s.record
|
||||
FROM %s AS s
|
||||
WHERE s.account_id_type = ? AND s.recipient_id = ?
|
||||
WHERE s.account_id_type = ? AND s.uuid = ?
|
||||
"""
|
||||
).formatted(TABLE_SESSION);
|
||||
try (final var connection = database.getConnection()) {
|
||||
|
@ -280,7 +271,7 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
final List<Pair<Key, SessionRecord>> records;
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setInt(1, accountIdType);
|
||||
statement.setLong(2, recipientId.id());
|
||||
statement.setBytes(2, serviceId.toByteArray());
|
||||
records = Utils.executeQueryForStream(statement,
|
||||
res -> new Pair<>(getKeyFromResultSet(res), getSessionRecordFromResultSet(res))).toList();
|
||||
}
|
||||
|
@ -294,35 +285,6 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
}
|
||||
}
|
||||
|
||||
public void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
|
||||
try (final var connection = database.getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
synchronized (cachedSessions) {
|
||||
cachedSessions.clear();
|
||||
}
|
||||
|
||||
final var sql = """
|
||||
UPDATE OR IGNORE %s
|
||||
SET recipient_id = ?
|
||||
WHERE account_id_type = ? AND recipient_id = ?
|
||||
""".formatted(TABLE_SESSION);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setLong(1, recipientId.id());
|
||||
statement.setInt(2, accountIdType);
|
||||
statement.setLong(3, toBeMergedRecipientId.id());
|
||||
final var rows = statement.executeUpdate();
|
||||
if (rows > 0) {
|
||||
logger.debug("Reassigned {} sessions of to be merged recipient.", rows);
|
||||
}
|
||||
}
|
||||
// Delete all conflicting sessions now
|
||||
deleteAllSessions(connection, toBeMergedRecipientId);
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("Failed update session store", e);
|
||||
}
|
||||
}
|
||||
|
||||
void addLegacySessions(final Collection<Pair<Key, SessionRecord>> sessions) {
|
||||
logger.debug("Migrating legacy sessions to database");
|
||||
long start = System.nanoTime();
|
||||
|
@ -339,8 +301,8 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
}
|
||||
|
||||
private Key getKey(final SignalProtocolAddress address) {
|
||||
final var recipientId = resolver.resolveRecipient(address.getName());
|
||||
return new Key(recipientId, address.getDeviceId());
|
||||
final var serviceId = ServiceId.parseOrThrow(address.getName());
|
||||
return new Key(serviceId, address.getDeviceId());
|
||||
}
|
||||
|
||||
private SessionRecord loadSession(Connection connection, final Key key) throws SQLException {
|
||||
|
@ -354,21 +316,21 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
"""
|
||||
SELECT s.record
|
||||
FROM %s AS s
|
||||
WHERE s.account_id_type = ? AND s.recipient_id = ? AND s.device_id = ?
|
||||
WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id = ?
|
||||
"""
|
||||
).formatted(TABLE_SESSION);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setInt(1, accountIdType);
|
||||
statement.setLong(2, key.recipientId().id());
|
||||
statement.setBytes(2, key.serviceId().toByteArray());
|
||||
statement.setInt(3, key.deviceId());
|
||||
return Utils.executeQueryForOptional(statement, this::getSessionRecordFromResultSet).orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private Key getKeyFromResultSet(ResultSet resultSet) throws SQLException {
|
||||
final var recipientId = resultSet.getLong("recipient_id");
|
||||
final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
|
||||
final var deviceId = resultSet.getInt("device_id");
|
||||
return new Key(recipientIdCreator.create(recipientId), deviceId);
|
||||
return new Key(serviceId, deviceId);
|
||||
}
|
||||
|
||||
private SessionRecord getSessionRecordFromResultSet(ResultSet resultSet) throws SQLException {
|
||||
|
@ -389,19 +351,19 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
}
|
||||
|
||||
final var sql = """
|
||||
INSERT OR REPLACE INTO %s (account_id_type, recipient_id, device_id, record)
|
||||
INSERT OR REPLACE INTO %s (account_id_type, uuid, device_id, record)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""".formatted(TABLE_SESSION);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setInt(1, accountIdType);
|
||||
statement.setLong(2, key.recipientId().id());
|
||||
statement.setBytes(2, key.serviceId().toByteArray());
|
||||
statement.setInt(3, key.deviceId());
|
||||
statement.setBytes(4, session.serialize());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteAllSessions(final Connection connection, final RecipientId recipientId) throws SQLException {
|
||||
private void deleteAllSessions(final Connection connection, final ServiceId serviceId) throws SQLException {
|
||||
synchronized (cachedSessions) {
|
||||
cachedSessions.clear();
|
||||
}
|
||||
|
@ -409,12 +371,12 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
final var sql = (
|
||||
"""
|
||||
DELETE FROM %s AS s
|
||||
WHERE s.account_id_type = ? AND s.recipient_id = ?
|
||||
WHERE s.account_id_type = ? AND s.uuid = ?
|
||||
"""
|
||||
).formatted(TABLE_SESSION);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setInt(1, accountIdType);
|
||||
statement.setLong(2, recipientId.id());
|
||||
statement.setBytes(2, serviceId.toByteArray());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -427,12 +389,12 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
final var sql = (
|
||||
"""
|
||||
DELETE FROM %s AS s
|
||||
WHERE s.account_id_type = ? AND s.recipient_id = ? AND s.device_id = ?
|
||||
WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id = ?
|
||||
"""
|
||||
).formatted(TABLE_SESSION);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
statement.setInt(1, accountIdType);
|
||||
statement.setLong(2, key.recipientId().id());
|
||||
statement.setBytes(2, key.serviceId().toByteArray());
|
||||
statement.setInt(3, key.deviceId());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
|
@ -444,5 +406,5 @@ public class SessionStore implements SignalServiceSessionStore {
|
|||
&& record.getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
|
||||
}
|
||||
|
||||
record Key(RecipientId recipientId, int deviceId) {}
|
||||
record Key(ServiceId serviceId, int deviceId) {}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package org.asamk.signal.manager.util;
|
||||
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.fingerprint.Fingerprint;
|
||||
import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
|
@ -73,28 +73,27 @@ public class Utils {
|
|||
|
||||
public static Fingerprint computeSafetyNumber(
|
||||
boolean isUuidCapable,
|
||||
SignalServiceAddress ownAddress,
|
||||
RecipientAddress ownAddress,
|
||||
IdentityKey ownIdentityKey,
|
||||
SignalServiceAddress theirAddress,
|
||||
RecipientAddress theirAddress,
|
||||
IdentityKey theirIdentityKey
|
||||
) {
|
||||
int version;
|
||||
byte[] ownId;
|
||||
byte[] theirId;
|
||||
|
||||
if (isUuidCapable) {
|
||||
if (!isUuidCapable && ownAddress.number().isPresent() && theirAddress.number().isPresent()) {
|
||||
// Version 1: E164 user
|
||||
version = 1;
|
||||
ownId = ownAddress.number().get().getBytes();
|
||||
theirId = theirAddress.number().get().getBytes();
|
||||
} else if (isUuidCapable && theirAddress.uuid().isPresent()) {
|
||||
// Version 2: UUID user
|
||||
version = 2;
|
||||
ownId = ownAddress.getServiceId().toByteArray();
|
||||
theirId = theirAddress.getServiceId().toByteArray();
|
||||
} else {
|
||||
// Version 1: E164 user
|
||||
version = 1;
|
||||
if (ownAddress.getNumber().isEmpty() || theirAddress.getNumber().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
ownId = ownAddress.getNumber().get().getBytes();
|
||||
theirId = theirAddress.getNumber().get().getBytes();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new NumericFingerprintGenerator(5200).createFor(version,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue