*/
package org.asamk.signal.manager;
+import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
+import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
-import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
+import org.whispersystems.signalservice.api.util.DeviceNameUtil;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.SleepTimer;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
private final PinHelper pinHelper;
private final AvatarStore avatarStore;
private final AttachmentStore attachmentStore;
+ private final SignalSessionLock sessionLock = new SignalSessionLock() {
+ private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
+
+ @Override
+ public Lock acquire() {
+ LEGACY_LOCK.lock();
+ return LEGACY_LOCK::unlock;
+ }
+ };
Manager(
SignalAccount account,
throw new NotRegisteredException();
}
- var account = SignalAccount.load(pathConfig.getDataPath(), username);
+ var account = SignalAccount.load(pathConfig.getDataPath(), username, true);
if (!account.isRegistered()) {
throw new NotRegisteredException();
}
public void updateAccountAttributes() throws IOException {
- accountManager.setAccountAttributes(null,
+ accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
+ null,
account.getLocalRegistrationId(),
true,
// set legacy pin only if no KBS master key is set
newProfile.getInternalServiceName(),
newProfile.getAbout() == null ? "" : newProfile.getAbout(),
newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
+ Optional.absent(),
streamDetails);
}
account.setRegistered(false);
}
- public List<DeviceInfo> getLinkedDevices() throws IOException {
+ public List<Device> getLinkedDevices() throws IOException {
var devices = accountManager.getDevices();
account.setMultiDevice(devices.size() > 1);
- return devices;
+ var identityKey = account.getIdentityKeyPair().getPrivateKey();
+ return devices.stream().map(d -> {
+ String deviceName = d.getName();
+ if (deviceName != null) {
+ try {
+ deviceName = DeviceNameUtil.decryptDeviceName(deviceName, identityKey);
+ } catch (IOException e) {
+ logger.debug("Failed to decrypt device name, maybe plain text?", e);
+ }
+ }
+ return new Device(d.getId(), deviceName, d.getCreated(), d.getLastSeen());
+ }).collect(Collectors.toList());
}
public void removeLinkedDevices(int deviceId) throws IOException {
account.getPassword(),
account.getDeviceId(),
account.getSignalProtocolStore(),
+ sessionLock,
userAgent,
account.isMultiDevice(),
Optional.fromNullable(messagePipe),
Profile getRecipientProfile(
RecipientId recipientId, boolean force
) {
- var profileKey = account.getProfileStore().getProfileKey(recipientId);
- if (profileKey == null) {
- if (force) {
- // retrieve profile to get identity key
- retrieveEncryptedProfile(recipientId);
- }
- return null;
- }
var profile = account.getProfileStore().getProfile(recipientId);
var now = new Date().getTime();
return null;
}
- profile = decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
+ var profileKey = account.getProfileStore().getProfileKey(recipientId);
+ if (profileKey == null) {
+ profile = new Profile(new Date().getTime(),
+ null,
+ null,
+ null,
+ null,
+ ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null),
+ ProfileUtils.getCapabilities(encryptedProfile));
+ } else {
+ profile = decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
+ }
account.getProfileStore().storeProfile(recipientId, profile);
return profile;
public Pair<GroupId, List<SendMessageResult>> updateGroup(
GroupId groupId, String name, List<String> members, File avatarFile
) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
- return sendUpdateGroupMessage(groupId,
- name,
- members == null ? null : getSignalServiceAddresses(members),
- avatarFile);
+ final var membersRecipientIds = members == null ? null : getSignalServiceAddresses(members);
+ if (membersRecipientIds != null) {
+ membersRecipientIds.remove(account.getSelfRecipientId());
+ }
+ return sendUpdateGroupMessage(groupId, name, membersRecipientIds, avatarFile);
}
private Pair<GroupId, List<SendMessageResult>> sendUpdateGroupMessage(
}
}
+ void renewSession(RecipientId recipientId) throws IOException {
+ account.getSessionStore().archiveSessions(recipientId);
+ if (!recipientId.equals(getSelfRecipientId())) {
+ sendNullMessage(recipientId);
+ }
+ }
+
public String getContactName(String number) throws InvalidNumberException {
var contact = account.getContactStore().getContact(canonicalizeAndResolveRecipient(number));
return contact == null || contact.getName() == null ? "" : contact.getName();
}
private byte[] getSenderCertificate() {
- // TODO support UUID capable sender certificates
- // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
byte[] certificate;
try {
- certificate = accountManager.getSenderCertificate();
+ if (account.isPhoneNumberShared()) {
+ certificate = accountManager.getSenderCertificate();
+ } else {
+ certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
+ }
} catch (IOException e) {
logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
return null;
}
}
- private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
- var cipher = new SignalServiceCipher(account.getSelfAddress(),
- account.getSignalProtocolStore(),
- certificateValidator);
+ private SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
+ var messageSender = createMessageSender();
+
+ final var address = resolveSignalServiceAddress(recipientId);
try {
- return cipher.decrypt(envelope);
- } catch (ProtocolUntrustedIdentityException e) {
- if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
- throw (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
+ try {
+ return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId));
+ } catch (UnregisteredUserException e) {
+ final var newRecipientId = refreshRegisteredUser(recipientId);
+ final var newAddress = resolveSignalServiceAddress(newRecipientId);
+ return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId));
}
- throw new AssertionError(e);
+ } catch (UntrustedIdentityException e) {
+ return SendMessageResult.identityFailure(address, e.getIdentityKey());
}
}
+ private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException {
+ var cipher = new SignalServiceCipher(account.getSelfAddress(),
+ account.getSignalProtocolStore(),
+ sessionLock,
+ certificateValidator);
+ return cipher.decrypt(envelope);
+ }
+
private void handleEndSession(RecipientId recipientId) {
account.getSessionStore().deleteAllSessions(recipientId);
}
if (!envelope.isReceipt()) {
try {
content = decryptMessage(envelope);
- } catch (org.whispersystems.libsignal.UntrustedIdentityException e) {
+ } catch (ProtocolUntrustedIdentityException e) {
if (!envelope.hasSource()) {
- final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) e).getName();
+ final var identifier = e.getSender();
final var recipientId = resolveRecipient(identifier);
try {
account.getMessageCache().replaceSender(cachedMessage, recipientId);
if (envelope.hasSource()) {
// Store uuid if we don't have it already
+ // address/uuid in envelope is sent by server
resolveRecipientTrusted(envelope.getSourceAddress());
}
final var notAGroupMember = isNotAGroupMember(envelope, content);
} catch (Exception e) {
exception = e;
}
+ if (!envelope.hasSource() && content != null) {
+ // Store uuid if we don't have it already
+ // address/uuid is validated by unidentified sender certificate
+ resolveRecipientTrusted(content.getSender());
+ }
var actions = handleMessage(envelope, content, ignoreAttachments);
+ if (exception instanceof ProtocolInvalidMessageException) {
+ final var sender = resolveRecipient(((ProtocolInvalidMessageException) exception).getSender());
+ logger.debug("Received invalid message, queuing renew session action.");
+ actions.add(new RenewSessionAction(sender));
+ }
if (hasCaughtUpWithOldMessages) {
for (var action : actions) {
try {
handler.handleMessage(envelope, content, exception);
}
if (cachedMessage[0] != null) {
- if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
- final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) exception).getName();
+ if (exception instanceof ProtocolUntrustedIdentityException) {
+ final var identifier = ((ProtocolUntrustedIdentityException) exception).getSender();
final var recipientId = resolveRecipient(identifier);
queuedActions.add(new RetrieveProfileAction(recipientId));
if (!envelope.hasSource()) {