Store uuids in identity and session store
This commit is contained in:
		
							parent
							
								
									416f43b225
								
							
						
					
					
						commit
						7e5aec6e15
					
				| @ -158,7 +158,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { | ||||
|                         System.out.println("Received sync message with verified identities:"); | ||||
|                         final VerifiedMessage verifiedMessage = syncMessage.getVerified().get(); | ||||
|                         System.out.println(" - " + verifiedMessage.getDestination() + ": " + verifiedMessage.getVerified()); | ||||
|                         String safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey())); | ||||
|                         String safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey())); | ||||
|                         System.out.println("   " + safetyNumber); | ||||
|                     } | ||||
|                     if (syncMessage.getConfiguration().isPresent()) { | ||||
|  | ||||
| @ -7,17 +7,15 @@ import org.asamk.signal.manager.Manager; | ||||
| import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; | ||||
| import org.asamk.signal.util.Hex; | ||||
| import org.asamk.signal.util.Util; | ||||
| import org.whispersystems.libsignal.util.Pair; | ||||
| import org.whispersystems.signalservice.api.util.InvalidNumberException; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public class ListIdentitiesCommand implements LocalCommand { | ||||
| 
 | ||||
|     private static void printIdentityFingerprint(Manager m, String theirUsername, JsonIdentityKeyStore.Identity theirId) { | ||||
|         String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey())); | ||||
|         System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirUsername, | ||||
|     private static void printIdentityFingerprint(Manager m, JsonIdentityKeyStore.Identity theirId) { | ||||
|         String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirId.getAddress(), theirId.getIdentityKey())); | ||||
|         System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirId.getAddress().getNumber().orNull(), | ||||
|                 theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toString(theirId.getFingerprint()), digits)); | ||||
|     } | ||||
| 
 | ||||
| @ -34,17 +32,15 @@ public class ListIdentitiesCommand implements LocalCommand { | ||||
|             return 1; | ||||
|         } | ||||
|         if (ns.get("number") == null) { | ||||
|             for (Map.Entry<String, List<JsonIdentityKeyStore.Identity>> keys : m.getIdentities().entrySet()) { | ||||
|                 for (JsonIdentityKeyStore.Identity id : keys.getValue()) { | ||||
|                     printIdentityFingerprint(m, keys.getKey(), id); | ||||
|                 } | ||||
|             for (JsonIdentityKeyStore.Identity identity : m.getIdentities()) { | ||||
|                 printIdentityFingerprint(m, identity); | ||||
|             } | ||||
|         } else { | ||||
|             String number = ns.getString("number"); | ||||
|             try { | ||||
|                 Pair<String, List<JsonIdentityKeyStore.Identity>> key = m.getIdentities(number); | ||||
|                 for (JsonIdentityKeyStore.Identity id : key.second()) { | ||||
|                     printIdentityFingerprint(m, key.first(), id); | ||||
|                 List<JsonIdentityKeyStore.Identity> identities = m.getIdentities(number); | ||||
|                 for (JsonIdentityKeyStore.Identity id : identities) { | ||||
|                     printIdentityFingerprint(m, id); | ||||
|                 } | ||||
|             } catch (InvalidNumberException e) { | ||||
|                 System.out.println("Invalid number: " + e.getMessage()); | ||||
|  | ||||
| @ -9,7 +9,6 @@ import org.asamk.signal.GroupNotFoundException; | ||||
| import org.asamk.signal.NotAGroupMemberException; | ||||
| import org.asamk.signal.manager.Manager; | ||||
| import org.asamk.signal.util.Util; | ||||
| import org.whispersystems.signalservice.api.push.SignalServiceAddress; | ||||
| import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; | ||||
| import org.whispersystems.signalservice.api.util.InvalidNumberException; | ||||
| 
 | ||||
| @ -63,7 +62,7 @@ public class SendReactionCommand implements LocalCommand { | ||||
| 
 | ||||
|         String emoji = ns.getString("emoji"); | ||||
|         boolean isRemove = ns.getBoolean("remove"); | ||||
|         SignalServiceAddress targetAuthor = new SignalServiceAddress(null, ns.getString("target_author")); | ||||
|         String targetAuthor = ns.getString("target_author"); | ||||
|         long targetTimestamp = ns.getLong("target_timestamp"); | ||||
| 
 | ||||
|         try { | ||||
|  | ||||
| @ -6,7 +6,9 @@ import net.sourceforge.argparse4j.inf.Namespace; | ||||
| import net.sourceforge.argparse4j.inf.Subparser; | ||||
| 
 | ||||
| import org.asamk.signal.manager.Manager; | ||||
| import org.asamk.signal.util.ErrorUtils; | ||||
| import org.asamk.signal.util.Hex; | ||||
| import org.whispersystems.signalservice.api.util.InvalidNumberException; | ||||
| 
 | ||||
| import java.util.Locale; | ||||
| 
 | ||||
| @ -50,13 +52,25 @@ public class TrustCommand implements LocalCommand { | ||||
|                         System.err.println("Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters."); | ||||
|                         return 1; | ||||
|                     } | ||||
|                     boolean res = m.trustIdentityVerified(number, fingerprintBytes); | ||||
|                     boolean res; | ||||
|                     try { | ||||
|                         res = m.trustIdentityVerified(number, fingerprintBytes); | ||||
|                     } catch (InvalidNumberException e) { | ||||
|                         ErrorUtils.handleInvalidNumberException(e); | ||||
|                         return 1; | ||||
|                     } | ||||
|                     if (!res) { | ||||
|                         System.err.println("Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct."); | ||||
|                         return 1; | ||||
|                     } | ||||
|                 } else if (fingerprint.length() == 60) { | ||||
|                     boolean res = m.trustIdentityVerifiedSafetyNumber(number, fingerprint); | ||||
|                     boolean res; | ||||
|                     try { | ||||
|                         res = m.trustIdentityVerifiedSafetyNumber(number, fingerprint); | ||||
|                     } catch (InvalidNumberException e) { | ||||
|                         ErrorUtils.handleInvalidNumberException(e); | ||||
|                         return 1; | ||||
|                     } | ||||
|                     if (!res) { | ||||
|                         System.err.println("Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct."); | ||||
|                         return 1; | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| package org.asamk.signal.manager; | ||||
| 
 | ||||
| import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; | ||||
| import org.whispersystems.signalservice.api.push.TrustStore; | ||||
| import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; | ||||
| import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl; | ||||
| @ -49,6 +50,8 @@ public class BaseConfig { | ||||
|             zkGroupServerPublicParams | ||||
|     ); | ||||
| 
 | ||||
|     static final SignalServiceProfile.Capabilities capabilities = new SignalServiceProfile.Capabilities(false, false); | ||||
| 
 | ||||
|     private BaseConfig() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -106,6 +106,7 @@ import org.whispersystems.signalservice.api.util.InvalidNumberException; | ||||
| import org.whispersystems.signalservice.api.util.SleepTimer; | ||||
| import org.whispersystems.signalservice.api.util.StreamDetails; | ||||
| import org.whispersystems.signalservice.api.util.UptimeSleepTimer; | ||||
| import org.whispersystems.signalservice.api.util.UuidUtil; | ||||
| import org.whispersystems.signalservice.internal.push.SignalServiceProtos; | ||||
| import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; | ||||
| import org.whispersystems.signalservice.internal.util.Hex; | ||||
| @ -133,7 +134,6 @@ import java.util.HashSet; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.Set; | ||||
| import java.util.UUID; | ||||
| @ -144,8 +144,6 @@ import java.util.zip.ZipFile; | ||||
| 
 | ||||
| public class Manager implements Signal { | ||||
| 
 | ||||
|     private static final SignalServiceProfile.Capabilities capabilities = new SignalServiceProfile.Capabilities(false, false); | ||||
| 
 | ||||
|     private final String settingsPath; | ||||
|     private final String dataPath; | ||||
|     private final String attachmentsPath; | ||||
| @ -192,6 +190,10 @@ public class Manager implements Signal { | ||||
|     } | ||||
| 
 | ||||
|     private String getMessageCachePath(String sender) { | ||||
|         if (sender == null || sender.isEmpty()) { | ||||
|             return getMessageCachePath(); | ||||
|         } | ||||
| 
 | ||||
|         return getMessageCachePath() + "/" + sender.replace("/", "_"); | ||||
|     } | ||||
| 
 | ||||
| @ -210,6 +212,7 @@ public class Manager implements Signal { | ||||
|             return; | ||||
|         } | ||||
|         account = SignalAccount.load(dataPath, username); | ||||
|         account.setResolver(this::resolveSignalServiceAddress); | ||||
| 
 | ||||
|         migrateLegacyConfigs(); | ||||
| 
 | ||||
| @ -257,11 +260,11 @@ public class Manager implements Signal { | ||||
|         int registrationId = KeyHelper.generateRegistrationId(false); | ||||
|         if (username == null) { | ||||
|             account = SignalAccount.createTemporaryAccount(identityKey, registrationId); | ||||
|             account.setResolver(this::resolveSignalServiceAddress); | ||||
|         } else { | ||||
|             account.getSignalProtocolStore().saveIdentity(username, identityKey.getPublicKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
| 
 | ||||
|             ProfileKey profileKey = KeyUtils.createProfileKey(); | ||||
|             account = SignalAccount.create(dataPath, username, identityKey, registrationId, profileKey); | ||||
|             account.setResolver(this::resolveSignalServiceAddress); | ||||
|             account.save(); | ||||
|         } | ||||
|     } | ||||
| @ -289,7 +292,7 @@ public class Manager implements Signal { | ||||
|     } | ||||
| 
 | ||||
|     public void updateAccountAttributes() throws IOException { | ||||
|         accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities); | ||||
|         accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities); | ||||
|     } | ||||
| 
 | ||||
|     public void setProfileName(String name) throws IOException { | ||||
| @ -350,6 +353,7 @@ public class Manager implements Signal { | ||||
|             } | ||||
|         } | ||||
|         account = SignalAccount.createLinkedAccount(dataPath, username, ret.getUuid(), account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey); | ||||
|         account.setResolver(this::resolveSignalServiceAddress); | ||||
| 
 | ||||
|         refreshPreKeys(); | ||||
| 
 | ||||
| @ -427,12 +431,13 @@ public class Manager implements Signal { | ||||
|         verificationCode = verificationCode.replace("-", ""); | ||||
|         account.setSignalingKey(KeyUtils.createSignalingKey()); | ||||
|         // TODO make unrestricted unidentified access configurable | ||||
|         UUID uuid = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, capabilities); | ||||
|         UUID uuid = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities); | ||||
| 
 | ||||
|         //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); | ||||
|         account.setRegistered(true); | ||||
|         account.setUuid(uuid); | ||||
|         account.setRegistrationLockPin(pin); | ||||
|         account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
| 
 | ||||
|         refreshPreKeys(); | ||||
|         account.save(); | ||||
| @ -540,10 +545,10 @@ public class Manager implements Signal { | ||||
|         sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); | ||||
|     } | ||||
| 
 | ||||
|     public void sendGroupMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor, | ||||
|     public void sendGroupMessageReaction(String emoji, boolean remove, String targetAuthor, | ||||
|                                          long targetSentTimestamp, byte[] groupId) | ||||
|             throws IOException, EncapsulatedExceptions, AttachmentInvalidException { | ||||
|         SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, targetAuthor, targetSentTimestamp); | ||||
|             throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException { | ||||
|         SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp); | ||||
|         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() | ||||
|                 .withReaction(reaction); | ||||
|         if (groupId != null) { | ||||
| @ -713,10 +718,10 @@ public class Manager implements Signal { | ||||
|         sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients)); | ||||
|     } | ||||
| 
 | ||||
|     public void sendMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor, | ||||
|     public void sendMessageReaction(String emoji, boolean remove, String targetAuthor, | ||||
|                                     long targetSentTimestamp, List<String> recipients) | ||||
|             throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException { | ||||
|         SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, targetAuthor, targetSentTimestamp); | ||||
|         SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp); | ||||
|         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() | ||||
|                 .withReaction(reaction); | ||||
|         sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients)); | ||||
| @ -732,8 +737,7 @@ public class Manager implements Signal { | ||||
| 
 | ||||
|     @Override | ||||
|     public String getContactName(String number) throws InvalidNumberException { | ||||
|         String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername()); | ||||
|         ContactInfo contact = account.getContactStore().getContact(new SignalServiceAddress(null, canonicalizedNumber)); | ||||
|         ContactInfo contact = account.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number)); | ||||
|         if (contact == null) { | ||||
|             return ""; | ||||
|         } else { | ||||
| @ -743,14 +747,13 @@ public class Manager implements Signal { | ||||
| 
 | ||||
|     @Override | ||||
|     public void setContactName(String number, String name) throws InvalidNumberException { | ||||
|         String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername()); | ||||
|         final SignalServiceAddress address = new SignalServiceAddress(null, canonicalizedNumber); | ||||
|         final SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(number); | ||||
|         ContactInfo contact = account.getContactStore().getContact(address); | ||||
|         if (contact == null) { | ||||
|             contact = new ContactInfo(address); | ||||
|             System.err.println("Add contact " + canonicalizedNumber + " named " + name); | ||||
|             System.err.println("Add contact " + contact.number + " named " + name); | ||||
|         } else { | ||||
|             System.err.println("Updating contact " + canonicalizedNumber + " name " + contact.name + " -> " + name); | ||||
|             System.err.println("Updating contact " + contact.number + " name " + contact.name + " -> " + name); | ||||
|         } | ||||
|         contact.name = name; | ||||
|         account.getContactStore().updateContact(contact); | ||||
| @ -759,14 +762,16 @@ public class Manager implements Signal { | ||||
| 
 | ||||
|     @Override | ||||
|     public void setContactBlocked(String number, boolean blocked) throws InvalidNumberException { | ||||
|         number = Utils.canonicalizeNumber(number, account.getUsername()); | ||||
|         final SignalServiceAddress address = new SignalServiceAddress(null, number); | ||||
|         setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number), blocked); | ||||
|     } | ||||
| 
 | ||||
|     private void setContactBlocked(SignalServiceAddress address, boolean blocked) { | ||||
|         ContactInfo contact = account.getContactStore().getContact(address); | ||||
|         if (contact == null) { | ||||
|             contact = new ContactInfo(address); | ||||
|             System.err.println("Adding and " + (blocked ? "blocking" : "unblocking") + " contact " + number); | ||||
|             System.err.println("Adding and " + (blocked ? "blocking" : "unblocking") + " contact " + address.getNumber().orNull()); | ||||
|         } else { | ||||
|             System.err.println((blocked ? "Blocking" : "Unblocking") + " contact " + number); | ||||
|             System.err.println((blocked ? "Blocking" : "Unblocking") + " contact " + address.getNumber().orNull()); | ||||
|         } | ||||
|         contact.blocked = blocked; | ||||
|         account.getContactStore().updateContact(contact); | ||||
| @ -996,10 +1001,16 @@ public class Manager implements Signal { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private byte[] getSenderCertificate() throws IOException { | ||||
|     private byte[] getSenderCertificate() { | ||||
|         // TODO support UUID capable sender certificates | ||||
|         // byte[] certificate = accountManager.getSenderCertificate(); | ||||
|         byte[] certificate = accountManager.getSenderCertificateLegacy(); | ||||
|         byte[] certificate; | ||||
|         try { | ||||
|             certificate = accountManager.getSenderCertificateLegacy(); | ||||
|         } catch (IOException e) { | ||||
|             System.err.println("Failed to get sender certificate: " + e); | ||||
|             return null; | ||||
|         } | ||||
|         // TODO cache for a day | ||||
|         return certificate; | ||||
|     } | ||||
| @ -1023,7 +1034,7 @@ public class Manager implements Signal { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) throws IOException { | ||||
|     private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) { | ||||
|         ContactInfo contact = account.getContactStore().getContact(recipient); | ||||
|         if (contact == null || contact.profileKey == null) { | ||||
|             return null; | ||||
| @ -1031,10 +1042,16 @@ public class Manager implements Signal { | ||||
|         ProfileKey theirProfileKey; | ||||
|         try { | ||||
|             theirProfileKey = new ProfileKey(Base64.decode(contact.profileKey)); | ||||
|         } catch (InvalidInputException e) { | ||||
|         } catch (InvalidInputException | IOException e) { | ||||
|             throw new AssertionError(e); | ||||
|         } | ||||
|         SignalProfile targetProfile = decryptProfile(getRecipientProfile(recipient, Optional.absent()), theirProfileKey); | ||||
|         SignalProfile targetProfile; | ||||
|         try { | ||||
|             targetProfile = decryptProfile(getRecipientProfile(recipient, Optional.absent()), theirProfileKey); | ||||
|         } catch (IOException e) { | ||||
|             System.err.println("Failed to get recipient profile: " + e); | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         if (targetProfile == null || targetProfile.getUnidentifiedAccess() == null) { | ||||
|             return null; | ||||
| @ -1047,7 +1064,7 @@ public class Manager implements Signal { | ||||
|         return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); | ||||
|     } | ||||
| 
 | ||||
|     private Optional<UnidentifiedAccessPair> getAccessForSync() throws IOException { | ||||
|     private Optional<UnidentifiedAccessPair> getAccessForSync() { | ||||
|         byte[] selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(); | ||||
|         byte[] selfUnidentifiedAccessCertificate = getSenderCertificate(); | ||||
| 
 | ||||
| @ -1065,7 +1082,7 @@ public class Manager implements Signal { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private List<Optional<UnidentifiedAccessPair>> getAccessFor(Collection<SignalServiceAddress> recipients) throws IOException { | ||||
|     private List<Optional<UnidentifiedAccessPair>> getAccessFor(Collection<SignalServiceAddress> recipients) { | ||||
|         List<Optional<UnidentifiedAccessPair>> result = new ArrayList<>(recipients.size()); | ||||
|         for (SignalServiceAddress recipient : recipients) { | ||||
|             result.add(getAccessFor(recipient)); | ||||
| @ -1073,7 +1090,7 @@ public class Manager implements Signal { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     private Optional<UnidentifiedAccessPair> getAccessFor(SignalServiceAddress recipient) throws IOException { | ||||
|     private Optional<UnidentifiedAccessPair> getAccessFor(SignalServiceAddress recipient) { | ||||
|         byte[] recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient); | ||||
|         byte[] selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(); | ||||
|         byte[] selfUnidentifiedAccessCertificate = getSenderCertificate(); | ||||
| @ -1092,13 +1109,23 @@ public class Manager implements Signal { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private Optional<UnidentifiedAccess> getUnidentifiedAccess(SignalServiceAddress recipient) { | ||||
|         Optional<UnidentifiedAccessPair> unidentifiedAccess = getAccessFor(recipient); | ||||
| 
 | ||||
|         if (unidentifiedAccess.isPresent()) { | ||||
|             return unidentifiedAccess.get().getTargetUnidentifiedAccess(); | ||||
|         } | ||||
| 
 | ||||
|         return Optional.absent(); | ||||
|     } | ||||
| 
 | ||||
|     private void sendSyncMessage(SignalServiceSyncMessage message) | ||||
|             throws IOException, UntrustedIdentityException { | ||||
|         SignalServiceMessageSender messageSender = getMessageSender(); | ||||
|         try { | ||||
|             messageSender.sendMessage(message, getAccessForSync()); | ||||
|         } catch (UntrustedIdentityException e) { | ||||
|             account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|             account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|             throw e; | ||||
|         } | ||||
|     } | ||||
| @ -1116,11 +1143,11 @@ public class Manager implements Signal { | ||||
| 
 | ||||
|         for (SendMessageResult result : results) { | ||||
|             if (result.isUnregisteredFailure()) { | ||||
|                 unregisteredUsers.add(new UnregisteredUserException(result.getAddress().getNumber().get(), null)); | ||||
|                 unregisteredUsers.add(new UnregisteredUserException(result.getAddress().getLegacyIdentifier(), null)); | ||||
|             } else if (result.isNetworkFailure()) { | ||||
|                 networkExceptions.add(new NetworkFailureException(result.getAddress().getNumber().get(), null)); | ||||
|                 networkExceptions.add(new NetworkFailureException(result.getAddress().getLegacyIdentifier(), null)); | ||||
|             } else if (result.getIdentityFailure() != null) { | ||||
|                 untrustedIdentities.add(new UntrustedIdentityException("Untrusted", result.getAddress().getNumber().get(), result.getIdentityFailure().getIdentityKey())); | ||||
|                 untrustedIdentities.add(new UntrustedIdentityException("Untrusted", result.getAddress().getLegacyIdentifier(), result.getIdentityFailure().getIdentityKey())); | ||||
|             } | ||||
|         } | ||||
|         if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) { | ||||
| @ -1130,19 +1157,9 @@ public class Manager implements Signal { | ||||
| 
 | ||||
|     private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException { | ||||
|         final Set<SignalServiceAddress> signalServiceAddresses = new HashSet<>(numbers.size()); | ||||
|         final String username = account.getUsername(); | ||||
| 
 | ||||
|         for (String number : numbers) { | ||||
|             String canonicalizedNumber = Utils.canonicalizeNumber(number, username); | ||||
|             if (canonicalizedNumber.equals(username)) { | ||||
|                 signalServiceAddresses.add(account.getSelfAddress()); | ||||
|             } else { | ||||
|                 SignalServiceAddress address = new SignalServiceAddress(null, canonicalizedNumber); | ||||
|                 ContactInfo contact = account.getContactStore().getContact(address); | ||||
|                 signalServiceAddresses.add(contact == null | ||||
|                         ? address | ||||
|                         : contact.getAddress()); | ||||
|             } | ||||
|             signalServiceAddresses.add(canonicalizeAndResolveSignalServiceAddress(number)); | ||||
|         } | ||||
|         return signalServiceAddresses; | ||||
|     } | ||||
| @ -1166,12 +1183,12 @@ public class Manager implements Signal { | ||||
|                     List<SendMessageResult> result = messageSender.sendMessage(new ArrayList<>(recipients), getAccessFor(recipients), isRecipientUpdate, message); | ||||
|                     for (SendMessageResult r : result) { | ||||
|                         if (r.getIdentityFailure() != null) { | ||||
|                             account.getSignalProtocolStore().saveIdentity(r.getAddress().getNumber().get(), r.getIdentityFailure().getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|                             account.getSignalProtocolStore().saveIdentity(r.getAddress(), r.getIdentityFailure().getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|                         } | ||||
|                     } | ||||
|                     return result; | ||||
|                 } catch (UntrustedIdentityException e) { | ||||
|                     account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|                     account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|                     return Collections.emptyList(); | ||||
|                 } | ||||
|             } else if (recipients.size() == 1 && recipients.contains(account.getSelfAddress())) { | ||||
| @ -1189,7 +1206,7 @@ public class Manager implements Signal { | ||||
|                 try { | ||||
|                     messageSender.sendMessage(syncMessage, unidentifiedAccess); | ||||
|                 } catch (UntrustedIdentityException e) { | ||||
|                     account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|                     account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|                     results.add(SendMessageResult.identityFailure(recipient, e.getIdentityKey())); | ||||
|                 } | ||||
|                 return results; | ||||
| @ -1210,7 +1227,7 @@ public class Manager implements Signal { | ||||
|                         SendMessageResult result = messageSender.sendMessage(address, getAccessFor(address), message); | ||||
|                         results.add(result); | ||||
|                     } catch (UntrustedIdentityException e) { | ||||
|                         account.getSignalProtocolStore().saveIdentity(e.getIdentifier(), e.getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|                         account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); | ||||
|                         results.add(SendMessageResult.identityFailure(address, e.getIdentityKey())); | ||||
|                     } | ||||
|                 } | ||||
| @ -1219,7 +1236,7 @@ public class Manager implements Signal { | ||||
|         } finally { | ||||
|             if (message != null && message.isEndSession()) { | ||||
|                 for (SignalServiceAddress recipient : recipients) { | ||||
|                     handleEndSession(recipient.getNumber().get()); | ||||
|                     handleEndSession(recipient); | ||||
|                 } | ||||
|             } | ||||
|             account.save(); | ||||
| @ -1237,7 +1254,7 @@ public class Manager implements Signal { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void handleEndSession(String source) { | ||||
|     private void handleEndSession(SignalServiceAddress source) { | ||||
|         account.getSignalProtocolStore().deleteAllSessions(source); | ||||
|     } | ||||
| 
 | ||||
| @ -1307,7 +1324,7 @@ public class Manager implements Signal { | ||||
|             } | ||||
|         } | ||||
|         if (message.isEndSession()) { | ||||
|             handleEndSession(isSync ? destination.getNumber().get() : source.getNumber().get()); | ||||
|             handleEndSession(isSync ? destination : source); | ||||
|         } | ||||
|         if (message.isExpirationUpdate() || message.getBody().isPresent()) { | ||||
|             if (message.getGroupInfo().isPresent()) { | ||||
| @ -1384,6 +1401,7 @@ public class Manager implements Signal { | ||||
|         } | ||||
|         for (final File dir : Objects.requireNonNull(cachePath.listFiles())) { | ||||
|             if (!dir.isDirectory()) { | ||||
|                 retryFailedReceivedMessage(handler, ignoreAttachments, dir); | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
| @ -1391,38 +1409,42 @@ public class Manager implements Signal { | ||||
|                 if (!fileEntry.isFile()) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 SignalServiceEnvelope envelope; | ||||
|                 try { | ||||
|                     envelope = Utils.loadEnvelope(fileEntry); | ||||
|                     if (envelope == null) { | ||||
|                         continue; | ||||
|                     } | ||||
|                 } catch (IOException e) { | ||||
|                     e.printStackTrace(); | ||||
|                     continue; | ||||
|                 } | ||||
|                 SignalServiceContent content = null; | ||||
|                 if (!envelope.isReceipt()) { | ||||
|                     try { | ||||
|                         content = decryptMessage(envelope); | ||||
|                     } catch (Exception e) { | ||||
|                         continue; | ||||
|                     } | ||||
|                     handleMessage(envelope, content, ignoreAttachments); | ||||
|                 } | ||||
|                 account.save(); | ||||
|                 handler.handleMessage(envelope, content, null); | ||||
|                 try { | ||||
|                     Files.delete(fileEntry.toPath()); | ||||
|                 } catch (IOException e) { | ||||
|                     System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage()); | ||||
|                 } | ||||
|                 retryFailedReceivedMessage(handler, ignoreAttachments, fileEntry); | ||||
|             } | ||||
|             // Try to delete directory if empty | ||||
|             dir.delete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void retryFailedReceivedMessage(final ReceiveMessageHandler handler, final boolean ignoreAttachments, final File fileEntry) { | ||||
|         SignalServiceEnvelope envelope; | ||||
|         try { | ||||
|             envelope = Utils.loadEnvelope(fileEntry); | ||||
|             if (envelope == null) { | ||||
|                 return; | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return; | ||||
|         } | ||||
|         SignalServiceContent content = null; | ||||
|         if (!envelope.isReceipt()) { | ||||
|             try { | ||||
|                 content = decryptMessage(envelope); | ||||
|             } catch (Exception e) { | ||||
|                 return; | ||||
|             } | ||||
|             handleMessage(envelope, content, ignoreAttachments); | ||||
|         } | ||||
|         account.save(); | ||||
|         handler.handleMessage(envelope, content, null); | ||||
|         try { | ||||
|             Files.delete(fileEntry.toPath()); | ||||
|         } catch (IOException e) { | ||||
|             System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void receiveMessages(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException { | ||||
|         retryFailedReceivedMessages(handler, ignoreAttachments); | ||||
|         final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); | ||||
| @ -1496,7 +1518,7 @@ public class Manager implements Signal { | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
|         ContactInfo sourceContact = getContact(source.getNumber().get()); | ||||
|         ContactInfo sourceContact = account.getContactStore().getContact(source); | ||||
|         if (sourceContact != null && sourceContact.blocked) { | ||||
|             return true; | ||||
|         } | ||||
| @ -1617,13 +1639,7 @@ public class Manager implements Signal { | ||||
|                 if (syncMessage.getBlockedList().isPresent()) { | ||||
|                     final BlockedListMessage blockedListMessage = syncMessage.getBlockedList().get(); | ||||
|                     for (SignalServiceAddress address : blockedListMessage.getAddresses()) { | ||||
|                         if (address.getNumber().isPresent()) { | ||||
|                             try { | ||||
|                                 setContactBlocked(address.getNumber().get(), true); | ||||
|                             } catch (InvalidNumberException e) { | ||||
|                                 e.printStackTrace(); | ||||
|                             } | ||||
|                         } | ||||
|                         setContactBlocked(address, true); | ||||
|                     } | ||||
|                     for (byte[] groupId : blockedListMessage.getGroupIds()) { | ||||
|                         try { | ||||
| @ -1663,7 +1679,7 @@ public class Manager implements Signal { | ||||
|                                 } | ||||
|                                 if (c.getVerified().isPresent()) { | ||||
|                                     final VerifiedMessage verifiedMessage = c.getVerified().get(); | ||||
|                                     account.getSignalProtocolStore().saveIdentity(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); | ||||
|                                     account.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); | ||||
|                                 } | ||||
|                                 if (c.getExpirationTimer().isPresent()) { | ||||
|                                     contact.messageExpirationTime = c.getExpirationTimer().get(); | ||||
| @ -1692,7 +1708,7 @@ public class Manager implements Signal { | ||||
|                 } | ||||
|                 if (syncMessage.getVerified().isPresent()) { | ||||
|                     final VerifiedMessage verifiedMessage = syncMessage.getVerified().get(); | ||||
|                     account.getSignalProtocolStore().saveIdentity(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); | ||||
|                     account.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); | ||||
|                 } | ||||
|                 if (syncMessage.getConfiguration().isPresent()) { | ||||
|                     // TODO | ||||
| @ -1829,16 +1845,9 @@ public class Manager implements Signal { | ||||
|                 DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos); | ||||
|                 for (ContactInfo record : account.getContactStore().getContacts()) { | ||||
|                     VerifiedMessage verifiedMessage = null; | ||||
|                     if (getIdentities().containsKey(record.number)) { | ||||
|                         JsonIdentityKeyStore.Identity currentIdentity = null; | ||||
|                         for (JsonIdentityKeyStore.Identity id : getIdentities().get(record.number)) { | ||||
|                             if (currentIdentity == null || id.getDateAdded().after(currentIdentity.getDateAdded())) { | ||||
|                                 currentIdentity = id; | ||||
|                             } | ||||
|                         } | ||||
|                         if (currentIdentity != null) { | ||||
|                             verifiedMessage = new VerifiedMessage(record.getAddress(), currentIdentity.getIdentityKey(), currentIdentity.getTrustLevel().toVerifiedState(), currentIdentity.getDateAdded().getTime()); | ||||
|                         } | ||||
|                     JsonIdentityKeyStore.Identity currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress()); | ||||
|                     if (currentIdentity != null) { | ||||
|                         verifiedMessage = new VerifiedMessage(record.getAddress(), currentIdentity.getIdentityKey(), currentIdentity.getTrustLevel().toVerifiedState(), currentIdentity.getDateAdded().getTime()); | ||||
|                     } | ||||
| 
 | ||||
|                     ProfileKey profileKey = null; | ||||
| @ -1909,20 +1918,19 @@ public class Manager implements Signal { | ||||
|     } | ||||
| 
 | ||||
|     public ContactInfo getContact(String number) { | ||||
|         return account.getContactStore().getContact(new SignalServiceAddress(null, number)); | ||||
|         return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number)); | ||||
|     } | ||||
| 
 | ||||
|     public GroupInfo getGroup(byte[] groupId) { | ||||
|         return account.getGroupStore().getGroup(groupId); | ||||
|     } | ||||
| 
 | ||||
|     public Map<String, List<JsonIdentityKeyStore.Identity>> getIdentities() { | ||||
|     public List<JsonIdentityKeyStore.Identity> getIdentities() { | ||||
|         return account.getSignalProtocolStore().getIdentities(); | ||||
|     } | ||||
| 
 | ||||
|     public Pair<String, List<JsonIdentityKeyStore.Identity>> getIdentities(String number) throws InvalidNumberException { | ||||
|         String canonicalizedNumber = Utils.canonicalizeNumber(number, account.getUsername()); | ||||
|         return new Pair<>(canonicalizedNumber, account.getSignalProtocolStore().getIdentities(canonicalizedNumber)); | ||||
|     public List<JsonIdentityKeyStore.Identity> getIdentities(String number) throws InvalidNumberException { | ||||
|         return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1931,8 +1939,9 @@ public class Manager implements Signal { | ||||
|      * @param name        username of the identity | ||||
|      * @param fingerprint Fingerprint | ||||
|      */ | ||||
|     public boolean trustIdentityVerified(String name, byte[] fingerprint) { | ||||
|         List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(name); | ||||
|     public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException { | ||||
|         SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name); | ||||
|         List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address); | ||||
|         if (ids == null) { | ||||
|             return false; | ||||
|         } | ||||
| @ -1941,9 +1950,9 @@ public class Manager implements Signal { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             account.getSignalProtocolStore().saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
|             account.getSignalProtocolStore().setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
|             try { | ||||
|                 sendVerifiedMessage(new SignalServiceAddress(null, name), id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
|                 sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
|             } catch (IOException | UntrustedIdentityException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
| @ -1959,19 +1968,20 @@ public class Manager implements Signal { | ||||
|      * @param name         username of the identity | ||||
|      * @param safetyNumber Safety number | ||||
|      */ | ||||
|     public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) { | ||||
|         List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(name); | ||||
|     public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException { | ||||
|         SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name); | ||||
|         List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address); | ||||
|         if (ids == null) { | ||||
|             return false; | ||||
|         } | ||||
|         for (JsonIdentityKeyStore.Identity id : ids) { | ||||
|             if (!safetyNumber.equals(computeSafetyNumber(name, id.getIdentityKey()))) { | ||||
|             if (!safetyNumber.equals(computeSafetyNumber(address, id.getIdentityKey()))) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             account.getSignalProtocolStore().saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
|             account.getSignalProtocolStore().setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
|             try { | ||||
|                 sendVerifiedMessage(new SignalServiceAddress(null, name), id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
|                 sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED); | ||||
|             } catch (IOException | UntrustedIdentityException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
| @ -1987,15 +1997,16 @@ public class Manager implements Signal { | ||||
|      * @param name username of the identity | ||||
|      */ | ||||
|     public boolean trustIdentityAllKeys(String name) { | ||||
|         List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(name); | ||||
|         SignalServiceAddress address = resolveSignalServiceAddress(name); | ||||
|         List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address); | ||||
|         if (ids == null) { | ||||
|             return false; | ||||
|         } | ||||
|         for (JsonIdentityKeyStore.Identity id : ids) { | ||||
|             if (id.getTrustLevel() == TrustLevel.UNTRUSTED) { | ||||
|                 account.getSignalProtocolStore().saveIdentity(name, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); | ||||
|                 account.getSignalProtocolStore().setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); | ||||
|                 try { | ||||
|                     sendVerifiedMessage(new SignalServiceAddress(null, name), id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); | ||||
|                     sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); | ||||
|                 } catch (IOException | UntrustedIdentityException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
| @ -2005,8 +2016,26 @@ public class Manager implements Signal { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public String computeSafetyNumber(String theirUsername, IdentityKey theirIdentityKey) { | ||||
|         return Utils.computeSafetyNumber(account.getUsername(), getIdentity(), theirUsername, theirIdentityKey); | ||||
|     public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) { | ||||
|         return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentity(), theirAddress, theirIdentityKey); | ||||
|     } | ||||
| 
 | ||||
|     public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException { | ||||
|         String canonicalizedNumber = UuidUtil.isUuid(identifier) ? identifier : Util.canonicalizeNumber(identifier, account.getUsername()); | ||||
|         return resolveSignalServiceAddress(canonicalizedNumber); | ||||
|     } | ||||
| 
 | ||||
|     public SignalServiceAddress resolveSignalServiceAddress(String identifier) { | ||||
|         SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier); | ||||
|         if (address.matches(account.getSelfAddress())) { | ||||
|             return account.getSelfAddress(); | ||||
|         } | ||||
| 
 | ||||
|         ContactInfo contactInfo = account.getContactStore().getContact(address); | ||||
|         if (contactInfo == null) { | ||||
|             return address; | ||||
|         } | ||||
|         return contactInfo.getAddress(); | ||||
|     } | ||||
| 
 | ||||
|     public interface ReceiveMessageHandler { | ||||
|  | ||||
| @ -13,9 +13,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; | ||||
| import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; | ||||
| import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; | ||||
| import org.whispersystems.signalservice.api.push.SignalServiceAddress; | ||||
| import org.whispersystems.signalservice.api.util.InvalidNumberException; | ||||
| import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; | ||||
| import org.whispersystems.signalservice.api.util.StreamDetails; | ||||
| import org.whispersystems.signalservice.api.util.UuidUtil; | ||||
| import org.whispersystems.util.Base64; | ||||
| 
 | ||||
| import java.io.BufferedInputStream; | ||||
| @ -38,6 +37,7 @@ import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import static org.whispersystems.signalservice.internal.util.Util.isEmpty; | ||||
| 
 | ||||
| @ -146,19 +146,19 @@ class Utils { | ||||
|         return new DeviceLinkInfo(deviceIdentifier, deviceKey); | ||||
|     } | ||||
| 
 | ||||
|     static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException { | ||||
|         return PhoneNumberFormatter.formatNumber(number, localNumber); | ||||
|     } | ||||
| 
 | ||||
|     static SignalServiceEnvelope loadEnvelope(File file) throws IOException { | ||||
|         try (FileInputStream f = new FileInputStream(file)) { | ||||
|             DataInputStream in = new DataInputStream(f); | ||||
|             int version = in.readInt(); | ||||
|             if (version > 2) { | ||||
|             if (version > 3) { | ||||
|                 return null; | ||||
|             } | ||||
|             int type = in.readInt(); | ||||
|             String source = in.readUTF(); | ||||
|             UUID sourceUuid = null; | ||||
|             if (version >= 3) { | ||||
|                 sourceUuid = UuidUtil.parseOrNull(in.readUTF()); | ||||
|             } | ||||
|             int sourceDevice = in.readInt(); | ||||
|             if (version == 1) { | ||||
|                 // read legacy relay field | ||||
| @ -186,16 +186,20 @@ class Utils { | ||||
|                     uuid = null; | ||||
|                 } | ||||
|             } | ||||
|             return new SignalServiceEnvelope(type, Optional.of(new SignalServiceAddress(null, source)), sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid); | ||||
|             Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty() | ||||
|                     ? Optional.absent() | ||||
|                     : Optional.of(new SignalServiceAddress(sourceUuid, source)); | ||||
|             return new SignalServiceEnvelope(type, addressOptional, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException { | ||||
|         try (FileOutputStream f = new FileOutputStream(file)) { | ||||
|             try (DataOutputStream out = new DataOutputStream(f)) { | ||||
|                 out.writeInt(2); // version | ||||
|                 out.writeInt(3); // version | ||||
|                 out.writeInt(envelope.getType()); | ||||
|                 out.writeUTF(envelope.getSourceE164().get()); | ||||
|                 out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : ""); | ||||
|                 out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : ""); | ||||
|                 out.writeInt(envelope.getSourceDevice()); | ||||
|                 out.writeLong(envelope.getTimestamp()); | ||||
|                 if (envelope.hasContent()) { | ||||
| @ -234,10 +238,25 @@ class Utils { | ||||
|         return outputFile; | ||||
|     } | ||||
| 
 | ||||
|     static String computeSafetyNumber(String ownUsername, IdentityKey ownIdentityKey, String theirUsername, IdentityKey theirIdentityKey) { | ||||
|         // Version 1: E164 user | ||||
|         // Version 2: UUID user | ||||
|         Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(1, ownUsername.getBytes(), ownIdentityKey, theirUsername.getBytes(), theirIdentityKey); | ||||
|     static String computeSafetyNumber(SignalServiceAddress ownAddress, IdentityKey ownIdentityKey, SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) { | ||||
|         int version; | ||||
|         byte[] ownId; | ||||
|         byte[] theirId; | ||||
| 
 | ||||
|         if (BaseConfig.capabilities.isUuid() | ||||
|                 && ownAddress.getUuid().isPresent() && theirAddress.getUuid().isPresent()) { | ||||
|             // Version 2: UUID user | ||||
|             version = 2; | ||||
|             ownId = UuidUtil.toByteArray(ownAddress.getUuid().get()); | ||||
|             theirId = UuidUtil.toByteArray(theirAddress.getUuid().get()); | ||||
|         } else { | ||||
|             // Version 1: E164 user | ||||
|             version = 1; | ||||
|             ownId = ownAddress.getNumber().get().getBytes(); | ||||
|             theirId = theirAddress.getNumber().get().getBytes(); | ||||
|         } | ||||
| 
 | ||||
|         Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version, ownId, ownIdentityKey, theirId, theirIdentityKey); | ||||
|         return fingerprint.getDisplayableFingerprint().getDisplayText(); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -15,6 +15,7 @@ import org.asamk.signal.storage.contacts.JsonContactsStore; | ||||
| import org.asamk.signal.storage.groups.GroupInfo; | ||||
| import org.asamk.signal.storage.groups.JsonGroupStore; | ||||
| import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; | ||||
| import org.asamk.signal.storage.protocol.SignalServiceAddressResolver; | ||||
| import org.asamk.signal.storage.threads.LegacyJsonThreadStore; | ||||
| import org.asamk.signal.storage.threads.ThreadInfo; | ||||
| import org.asamk.signal.util.IOUtils; | ||||
| @ -273,6 +274,10 @@ public class SignalAccount { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void setResolver(final SignalServiceAddressResolver resolver) { | ||||
|         signalProtocolStore.setResolver(resolver); | ||||
|     } | ||||
| 
 | ||||
|     public void addPreKeys(Collection<PreKeyRecord> records) { | ||||
|         for (PreKeyRecord record : records) { | ||||
|             signalProtocolStore.storePreKey(record.getId(), record); | ||||
|  | ||||
| @ -9,32 +9,48 @@ import com.fasterxml.jackson.databind.JsonSerializer; | ||||
| import com.fasterxml.jackson.databind.SerializerProvider; | ||||
| 
 | ||||
| import org.asamk.signal.TrustLevel; | ||||
| import org.asamk.signal.util.Util; | ||||
| import org.whispersystems.libsignal.IdentityKey; | ||||
| import org.whispersystems.libsignal.IdentityKeyPair; | ||||
| import org.whispersystems.libsignal.InvalidKeyException; | ||||
| import org.whispersystems.libsignal.SignalProtocolAddress; | ||||
| import org.whispersystems.libsignal.state.IdentityKeyStore; | ||||
| import org.whispersystems.signalservice.api.push.SignalServiceAddress; | ||||
| import org.whispersystems.signalservice.api.util.UuidUtil; | ||||
| import org.whispersystems.util.Base64; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| public class JsonIdentityKeyStore implements IdentityKeyStore { | ||||
| 
 | ||||
|     private final Map<String, List<Identity>> trustedKeys = new HashMap<>(); | ||||
|     private final List<Identity> identities = new ArrayList<>(); | ||||
| 
 | ||||
|     private final IdentityKeyPair identityKeyPair; | ||||
|     private final int localRegistrationId; | ||||
| 
 | ||||
|     private SignalServiceAddressResolver resolver; | ||||
| 
 | ||||
|     public JsonIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) { | ||||
|         this.identityKeyPair = identityKeyPair; | ||||
|         this.localRegistrationId = localRegistrationId; | ||||
|     } | ||||
| 
 | ||||
|     public void setResolver(final SignalServiceAddressResolver resolver) { | ||||
|         this.resolver = resolver; | ||||
|     } | ||||
| 
 | ||||
|     private SignalServiceAddress resolveSignalServiceAddress(String identifier) { | ||||
|         if (resolver != null) { | ||||
|             return resolver.resolveSignalServiceAddress(identifier); | ||||
|         } else { | ||||
|             return Util.getSignalServiceAddressFromIdentifier(identifier); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public IdentityKeyPair getIdentityKeyPair() { | ||||
|         return identityKeyPair; | ||||
| @ -47,85 +63,116 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { | ||||
|         return saveIdentity(address.getName(), identityKey, TrustLevel.TRUSTED_UNVERIFIED, null); | ||||
|         return saveIdentity(resolveSignalServiceAddress(address.getName()), identityKey, TrustLevel.TRUSTED_UNVERIFIED, null); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds or updates the given identityKey for the user name and sets the trustLevel and added timestamp. | ||||
|      * Adds the given identityKey for the user name and sets the trustLevel and added timestamp. | ||||
|      * If the identityKey already exists, the trustLevel and added timestamp are NOT updated. | ||||
|      * | ||||
|      * @param name        User name, i.e. phone number | ||||
|      * @param identityKey The user's public key | ||||
|      * @param trustLevel  Level of trust: untrusted, trusted, trusted and verified | ||||
|      * @param added       Added timestamp, if null and the key is newly added, the current time is used. | ||||
|      * @param serviceAddress User address, i.e. phone number and/or uuid | ||||
|      * @param identityKey    The user's public key | ||||
|      * @param trustLevel     Level of trust: untrusted, trusted, trusted and verified | ||||
|      * @param added          Added timestamp, if null and the key is newly added, the current time is used. | ||||
|      */ | ||||
|     public boolean saveIdentity(String name, IdentityKey identityKey, TrustLevel trustLevel, Date added) { | ||||
|         List<Identity> identities = trustedKeys.get(name); | ||||
|         if (identities == null) { | ||||
|             identities = new ArrayList<>(); | ||||
|             trustedKeys.put(name, identities); | ||||
|         } else { | ||||
|             for (Identity id : identities) { | ||||
|                 if (!id.identityKey.equals(identityKey)) | ||||
|                     continue; | ||||
| 
 | ||||
|                 if (id.trustLevel.compareTo(trustLevel) < 0) { | ||||
|                     id.trustLevel = trustLevel; | ||||
|                 } | ||||
|                 if (added != null) { | ||||
|                     id.added = added; | ||||
|                 } | ||||
|                 return true; | ||||
|     public boolean saveIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel, Date added) { | ||||
|         for (Identity id : identities) { | ||||
|             if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (!id.address.getUuid().isPresent() || !id.address.getNumber().isPresent()) { | ||||
|                 id.address = serviceAddress; | ||||
|             } | ||||
|             // Identity already exists, not updating the trust level | ||||
|             return true; | ||||
|         } | ||||
|         identities.add(new Identity(identityKey, trustLevel, added != null ? added : new Date())); | ||||
| 
 | ||||
|         identities.add(new Identity(serviceAddress, identityKey, trustLevel, added != null ? added : new Date())); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update trustLevel for the given identityKey for the user name. | ||||
|      * | ||||
|      * @param serviceAddress User address, i.e. phone number and/or uuid | ||||
|      * @param identityKey    The user's public key | ||||
|      * @param trustLevel     Level of trust: untrusted, trusted, trusted and verified | ||||
|      */ | ||||
|     public void setIdentityTrustLevel(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel) { | ||||
|         for (Identity id : identities) { | ||||
|             if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (!id.address.getUuid().isPresent() || !id.address.getNumber().isPresent()) { | ||||
|                 id.address = serviceAddress; | ||||
|             } | ||||
|             id.trustLevel = trustLevel; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         identities.add(new Identity(serviceAddress, identityKey, trustLevel, new Date())); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { | ||||
|         // TODO implement possibility for different handling of incoming/outgoing trust decisions | ||||
|         List<Identity> identities = trustedKeys.get(address.getName()); | ||||
|         if (identities == null) { | ||||
|             // Trust on first use | ||||
|             return true; | ||||
|         } | ||||
|         SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); | ||||
|         boolean trustOnFirstUse = true; | ||||
| 
 | ||||
|         for (Identity id : identities) { | ||||
|             if (!id.address.matches(serviceAddress)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (id.identityKey.equals(identityKey)) { | ||||
|                 return id.isTrusted(); | ||||
|             } else { | ||||
|                 trustOnFirstUse = false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|         return trustOnFirstUse; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public IdentityKey getIdentity(SignalProtocolAddress address) { | ||||
|         List<Identity> identities = trustedKeys.get(address.getName()); | ||||
|         if (identities == null || identities.size() == 0) { | ||||
|             return null; | ||||
|         } | ||||
|         SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); | ||||
|         Identity identity = getIdentity(serviceAddress); | ||||
|         return identity == null ? null : identity.getIdentityKey(); | ||||
|     } | ||||
| 
 | ||||
|     public Identity getIdentity(SignalServiceAddress serviceAddress) { | ||||
|         long maxDate = 0; | ||||
|         Identity maxIdentity = null; | ||||
|         for (Identity id : identities) { | ||||
|         for (Identity id : this.identities) { | ||||
|             if (!id.address.matches(serviceAddress)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             final long time = id.getDateAdded().getTime(); | ||||
|             if (maxIdentity == null || maxDate <= time) { | ||||
|                 maxDate = time; | ||||
|                 maxIdentity = id; | ||||
|             } | ||||
|         } | ||||
|         return maxIdentity.getIdentityKey(); | ||||
|         return maxIdentity; | ||||
|     } | ||||
| 
 | ||||
|     public Map<String, List<Identity>> getIdentities() { | ||||
|     public List<Identity> getIdentities() { | ||||
|         // TODO deep copy | ||||
|         return trustedKeys; | ||||
|         return identities; | ||||
|     } | ||||
| 
 | ||||
|     public List<Identity> getIdentities(String name) { | ||||
|         // TODO deep copy | ||||
|         return trustedKeys.get(name); | ||||
|     public List<Identity> getIdentities(SignalServiceAddress serviceAddress) { | ||||
|         List<Identity> identities = new ArrayList<>(); | ||||
|         for (Identity identity : this.identities) { | ||||
|             if (identity.address.matches(serviceAddress)) { | ||||
|                 identities.add(identity); | ||||
|             } | ||||
|         } | ||||
|         return identities; | ||||
|     } | ||||
| 
 | ||||
|     public static class JsonIdentityKeyStoreDeserializer extends JsonDeserializer<JsonIdentityKeyStore> { | ||||
| @ -143,12 +190,26 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { | ||||
|                 JsonNode trustedKeysNode = node.get("trustedKeys"); | ||||
|                 if (trustedKeysNode.isArray()) { | ||||
|                     for (JsonNode trustedKey : trustedKeysNode) { | ||||
|                         String trustedKeyName = trustedKey.get("name").asText(); | ||||
|                         String trustedKeyName = trustedKey.has("name") | ||||
|                                 ? trustedKey.get("name").asText() | ||||
|                                 : null; | ||||
| 
 | ||||
|                         if (UuidUtil.isUuid(trustedKeyName)) { | ||||
|                             // Ignore identities that were incorrectly created with UUIDs as name | ||||
|                             continue; | ||||
|                         } | ||||
| 
 | ||||
|                         UUID uuid = trustedKey.hasNonNull("uuid") | ||||
|                                 ? UuidUtil.parseOrNull(trustedKey.get("uuid").asText()) | ||||
|                                 : null; | ||||
|                         final SignalServiceAddress serviceAddress = uuid == null | ||||
|                                 ? Util.getSignalServiceAddressFromIdentifier(trustedKeyName) | ||||
|                                 : new SignalServiceAddress(uuid, trustedKeyName); | ||||
|                         try { | ||||
|                             IdentityKey id = new IdentityKey(Base64.decode(trustedKey.get("identityKey").asText()), 0); | ||||
|                             TrustLevel trustLevel = trustedKey.has("trustLevel") ? TrustLevel.fromInt(trustedKey.get("trustLevel").asInt()) : TrustLevel.TRUSTED_UNVERIFIED; | ||||
|                             Date added = trustedKey.has("addedTimestamp") ? new Date(trustedKey.get("addedTimestamp").asLong()) : new Date(); | ||||
|                             keyStore.saveIdentity(trustedKeyName, id, trustLevel, added); | ||||
|                             keyStore.saveIdentity(serviceAddress, id, trustLevel, added); | ||||
|                         } catch (InvalidKeyException | IOException e) { | ||||
|                             System.out.println(String.format("Error while decoding key for: %s", trustedKeyName)); | ||||
|                         } | ||||
| @ -170,15 +231,18 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { | ||||
|             json.writeNumberField("registrationId", jsonIdentityKeyStore.getLocalRegistrationId()); | ||||
|             json.writeStringField("identityKey", Base64.encodeBytes(jsonIdentityKeyStore.getIdentityKeyPair().serialize())); | ||||
|             json.writeArrayFieldStart("trustedKeys"); | ||||
|             for (Map.Entry<String, List<Identity>> trustedKey : jsonIdentityKeyStore.trustedKeys.entrySet()) { | ||||
|                 for (Identity id : trustedKey.getValue()) { | ||||
|                     json.writeStartObject(); | ||||
|                     json.writeStringField("name", trustedKey.getKey()); | ||||
|                     json.writeStringField("identityKey", Base64.encodeBytes(id.identityKey.serialize())); | ||||
|                     json.writeNumberField("trustLevel", id.trustLevel.ordinal()); | ||||
|                     json.writeNumberField("addedTimestamp", id.added.getTime()); | ||||
|                     json.writeEndObject(); | ||||
|             for (Identity trustedKey : jsonIdentityKeyStore.identities) { | ||||
|                 json.writeStartObject(); | ||||
|                 if (trustedKey.getAddress().getNumber().isPresent()) { | ||||
|                     json.writeStringField("name", trustedKey.getAddress().getNumber().get()); | ||||
|                 } | ||||
|                 if (trustedKey.getAddress().getUuid().isPresent()) { | ||||
|                     json.writeStringField("uuid", trustedKey.getAddress().getUuid().get().toString()); | ||||
|                 } | ||||
|                 json.writeStringField("identityKey", Base64.encodeBytes(trustedKey.identityKey.serialize())); | ||||
|                 json.writeNumberField("trustLevel", trustedKey.trustLevel.ordinal()); | ||||
|                 json.writeNumberField("addedTimestamp", trustedKey.added.getTime()); | ||||
|                 json.writeEndObject(); | ||||
|             } | ||||
|             json.writeEndArray(); | ||||
|             json.writeEndObject(); | ||||
| @ -187,22 +251,33 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { | ||||
| 
 | ||||
|     public static class Identity { | ||||
| 
 | ||||
|         SignalServiceAddress address; | ||||
|         IdentityKey identityKey; | ||||
|         TrustLevel trustLevel; | ||||
|         Date added; | ||||
| 
 | ||||
|         public Identity(IdentityKey identityKey, TrustLevel trustLevel) { | ||||
|         public Identity(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel) { | ||||
|             this.address = address; | ||||
|             this.identityKey = identityKey; | ||||
|             this.trustLevel = trustLevel; | ||||
|             this.added = new Date(); | ||||
|         } | ||||
| 
 | ||||
|         Identity(IdentityKey identityKey, TrustLevel trustLevel, Date added) { | ||||
|         Identity(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) { | ||||
|             this.address = address; | ||||
|             this.identityKey = identityKey; | ||||
|             this.trustLevel = trustLevel; | ||||
|             this.added = added; | ||||
|         } | ||||
| 
 | ||||
|         public SignalServiceAddress getAddress() { | ||||
|             return address; | ||||
|         } | ||||
| 
 | ||||
|         public void setAddress(final SignalServiceAddress address) { | ||||
|             this.address = address; | ||||
|         } | ||||
| 
 | ||||
|         boolean isTrusted() { | ||||
|             return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || | ||||
|                     trustLevel == TrustLevel.TRUSTED_VERIFIED; | ||||
|  | ||||
| @ -8,51 +8,68 @@ import com.fasterxml.jackson.databind.JsonNode; | ||||
| import com.fasterxml.jackson.databind.JsonSerializer; | ||||
| import com.fasterxml.jackson.databind.SerializerProvider; | ||||
| 
 | ||||
| import org.asamk.signal.util.Util; | ||||
| import org.whispersystems.libsignal.SignalProtocolAddress; | ||||
| import org.whispersystems.libsignal.state.SessionRecord; | ||||
| import org.whispersystems.libsignal.state.SessionStore; | ||||
| import org.whispersystems.signalservice.api.push.SignalServiceAddress; | ||||
| import org.whispersystems.signalservice.api.util.UuidUtil; | ||||
| import org.whispersystems.util.Base64; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| class JsonSessionStore implements SessionStore { | ||||
| 
 | ||||
|     private final Map<SignalProtocolAddress, byte[]> sessions = new HashMap<>(); | ||||
|     private final List<SessionInfo> sessions = new ArrayList<>(); | ||||
| 
 | ||||
|     private SignalServiceAddressResolver resolver; | ||||
| 
 | ||||
|     public JsonSessionStore() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private void addSessions(Map<SignalProtocolAddress, byte[]> sessions) { | ||||
|         this.sessions.putAll(sessions); | ||||
|     public void setResolver(final SignalServiceAddressResolver resolver) { | ||||
|         this.resolver = resolver; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public synchronized SessionRecord loadSession(SignalProtocolAddress remoteAddress) { | ||||
|         try { | ||||
|             if (containsSession(remoteAddress)) { | ||||
|                 return new SessionRecord(sessions.get(remoteAddress)); | ||||
|             } else { | ||||
|                 return new SessionRecord(); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             throw new AssertionError(e); | ||||
|     private SignalServiceAddress resolveSignalServiceAddress(String identifier) { | ||||
|         if (resolver != null) { | ||||
|             return resolver.resolveSignalServiceAddress(identifier); | ||||
|         } else { | ||||
|             return Util.getSignalServiceAddressFromIdentifier(identifier); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public synchronized List<Integer> getSubDeviceSessions(String name) { | ||||
|         List<Integer> deviceIds = new LinkedList<>(); | ||||
|     public synchronized SessionRecord loadSession(SignalProtocolAddress address) { | ||||
|         SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); | ||||
|         for (SessionInfo info : sessions) { | ||||
|             if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) { | ||||
|                 try { | ||||
|                     return new SessionRecord(info.sessionRecord); | ||||
|                 } catch (IOException e) { | ||||
|                     System.err.println("Failed to load session, resetting session: " + e); | ||||
|                     final SessionRecord sessionRecord = new SessionRecord(); | ||||
|                     info.sessionRecord = sessionRecord.serialize(); | ||||
|                     return sessionRecord; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (SignalProtocolAddress key : sessions.keySet()) { | ||||
|             if (key.getName().equals(name) && | ||||
|                     key.getDeviceId() != 1) { | ||||
|                 deviceIds.add(key.getDeviceId()); | ||||
|         return new SessionRecord(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public synchronized List<Integer> getSubDeviceSessions(String name) { | ||||
|         SignalServiceAddress serviceAddress = resolveSignalServiceAddress(name); | ||||
| 
 | ||||
|         List<Integer> deviceIds = new LinkedList<>(); | ||||
|         for (SessionInfo info : sessions) { | ||||
|             if (info.address.matches(serviceAddress) && info.deviceId != 1) { | ||||
|                 deviceIds.add(info.deviceId); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -61,26 +78,45 @@ class JsonSessionStore implements SessionStore { | ||||
| 
 | ||||
|     @Override | ||||
|     public synchronized void storeSession(SignalProtocolAddress address, SessionRecord record) { | ||||
|         sessions.put(address, record.serialize()); | ||||
|         SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); | ||||
|         for (SessionInfo info : sessions) { | ||||
|             if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) { | ||||
|                 if (!info.address.getUuid().isPresent() || !info.address.getNumber().isPresent()) { | ||||
|                     info.address = serviceAddress; | ||||
|                 } | ||||
|                 info.sessionRecord = record.serialize(); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         sessions.add(new SessionInfo(serviceAddress, address.getDeviceId(), record.serialize())); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public synchronized boolean containsSession(SignalProtocolAddress address) { | ||||
|         return sessions.containsKey(address); | ||||
|         SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); | ||||
|         for (SessionInfo info : sessions) { | ||||
|             if (info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public synchronized void deleteSession(SignalProtocolAddress address) { | ||||
|         sessions.remove(address); | ||||
|         SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); | ||||
|         sessions.removeIf(info -> info.address.matches(serviceAddress) && info.deviceId == address.getDeviceId()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public synchronized void deleteAllSessions(String name) { | ||||
|         for (SignalProtocolAddress key : new ArrayList<>(sessions.keySet())) { | ||||
|             if (key.getName().equals(name)) { | ||||
|                 sessions.remove(key); | ||||
|             } | ||||
|         } | ||||
|         SignalServiceAddress serviceAddress = resolveSignalServiceAddress(name); | ||||
|         deleteAllSessions(serviceAddress); | ||||
|     } | ||||
| 
 | ||||
|     public synchronized void deleteAllSessions(SignalServiceAddress serviceAddress) { | ||||
|         sessions.removeIf(info -> info.address.matches(serviceAddress)); | ||||
|     } | ||||
| 
 | ||||
|     public static class JsonSessionStoreDeserializer extends JsonDeserializer<JsonSessionStore> { | ||||
| @ -89,23 +125,36 @@ class JsonSessionStore implements SessionStore { | ||||
|         public JsonSessionStore deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { | ||||
|             JsonNode node = jsonParser.getCodec().readTree(jsonParser); | ||||
| 
 | ||||
|             Map<SignalProtocolAddress, byte[]> sessionMap = new HashMap<>(); | ||||
|             JsonSessionStore sessionStore = new JsonSessionStore(); | ||||
| 
 | ||||
|             if (node.isArray()) { | ||||
|                 for (JsonNode session : node) { | ||||
|                     String sessionName = session.get("name").asText(); | ||||
|                     String sessionName = session.has("name") | ||||
|                             ? session.get("name").asText() | ||||
|                             : null; | ||||
|                     if (UuidUtil.isUuid(sessionName)) { | ||||
|                         // Ignore sessions that were incorrectly created with UUIDs as name | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     UUID uuid = session.hasNonNull("uuid") | ||||
|                             ? UuidUtil.parseOrNull(session.get("uuid").asText()) | ||||
|                             : null; | ||||
|                     final SignalServiceAddress serviceAddress = uuid == null | ||||
|                             ? Util.getSignalServiceAddressFromIdentifier(sessionName) | ||||
|                             : new SignalServiceAddress(uuid, sessionName); | ||||
|                     final int deviceId = session.get("deviceId").asInt(); | ||||
|                     final String record = session.get("record").asText(); | ||||
|                     try { | ||||
|                         sessionMap.put(new SignalProtocolAddress(sessionName, session.get("deviceId").asInt()), Base64.decode(session.get("record").asText())); | ||||
|                         SessionInfo sessionInfo = new SessionInfo(serviceAddress, deviceId, Base64.decode(record)); | ||||
|                         sessionStore.sessions.add(sessionInfo); | ||||
|                     } catch (IOException e) { | ||||
|                         System.out.println(String.format("Error while decoding session for: %s", sessionName)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             JsonSessionStore sessionStore = new JsonSessionStore(); | ||||
|             sessionStore.addSessions(sessionMap); | ||||
| 
 | ||||
|             return sessionStore; | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -114,14 +163,20 @@ class JsonSessionStore implements SessionStore { | ||||
|         @Override | ||||
|         public void serialize(JsonSessionStore jsonSessionStore, JsonGenerator json, SerializerProvider serializerProvider) throws IOException { | ||||
|             json.writeStartArray(); | ||||
|             for (Map.Entry<SignalProtocolAddress, byte[]> preKey : jsonSessionStore.sessions.entrySet()) { | ||||
|             for (SessionInfo sessionInfo : jsonSessionStore.sessions) { | ||||
|                 json.writeStartObject(); | ||||
|                 json.writeStringField("name", preKey.getKey().getName()); | ||||
|                 json.writeNumberField("deviceId", preKey.getKey().getDeviceId()); | ||||
|                 json.writeStringField("record", Base64.encodeBytes(preKey.getValue())); | ||||
|                 if (sessionInfo.address.getNumber().isPresent()) { | ||||
|                     json.writeStringField("name", sessionInfo.address.getNumber().get()); | ||||
|                 } | ||||
|                 if (sessionInfo.address.getUuid().isPresent()) { | ||||
|                     json.writeStringField("uuid", sessionInfo.address.getUuid().get().toString()); | ||||
|                 } | ||||
|                 json.writeNumberField("deviceId", sessionInfo.deviceId); | ||||
|                 json.writeStringField("record", Base64.encodeBytes(sessionInfo.sessionRecord)); | ||||
|                 json.writeEndObject(); | ||||
|             } | ||||
|             json.writeEndArray(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -13,9 +13,9 @@ import org.whispersystems.libsignal.state.PreKeyRecord; | ||||
| import org.whispersystems.libsignal.state.SessionRecord; | ||||
| import org.whispersystems.libsignal.state.SignalProtocolStore; | ||||
| import org.whispersystems.libsignal.state.SignedPreKeyRecord; | ||||
| import org.whispersystems.signalservice.api.push.SignalServiceAddress; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public class JsonSignalProtocolStore implements SignalProtocolStore { | ||||
| 
 | ||||
| @ -56,6 +56,11 @@ public class JsonSignalProtocolStore implements SignalProtocolStore { | ||||
|         this.identityKeyStore = new JsonIdentityKeyStore(identityKeyPair, registrationId); | ||||
|     } | ||||
| 
 | ||||
|     public void setResolver(final SignalServiceAddressResolver resolver) { | ||||
|         sessionStore.setResolver(resolver); | ||||
|         identityKeyStore.setResolver(resolver); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public IdentityKeyPair getIdentityKeyPair() { | ||||
|         return identityKeyStore.getIdentityKeyPair(); | ||||
| @ -71,16 +76,20 @@ public class JsonSignalProtocolStore implements SignalProtocolStore { | ||||
|         return identityKeyStore.saveIdentity(address, identityKey); | ||||
|     } | ||||
| 
 | ||||
|     public void saveIdentity(String name, IdentityKey identityKey, TrustLevel trustLevel) { | ||||
|         identityKeyStore.saveIdentity(name, identityKey, trustLevel, null); | ||||
|     public void saveIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel) { | ||||
|         identityKeyStore.saveIdentity(serviceAddress, identityKey, trustLevel, null); | ||||
|     } | ||||
| 
 | ||||
|     public Map<String, List<JsonIdentityKeyStore.Identity>> getIdentities() { | ||||
|     public void setIdentityTrustLevel(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel) { | ||||
|         identityKeyStore.setIdentityTrustLevel(serviceAddress, identityKey, trustLevel); | ||||
|     } | ||||
| 
 | ||||
|     public List<JsonIdentityKeyStore.Identity> getIdentities() { | ||||
|         return identityKeyStore.getIdentities(); | ||||
|     } | ||||
| 
 | ||||
|     public List<JsonIdentityKeyStore.Identity> getIdentities(String name) { | ||||
|         return identityKeyStore.getIdentities(name); | ||||
|     public List<JsonIdentityKeyStore.Identity> getIdentities(SignalServiceAddress serviceAddress) { | ||||
|         return identityKeyStore.getIdentities(serviceAddress); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -93,6 +102,10 @@ public class JsonSignalProtocolStore implements SignalProtocolStore { | ||||
|         return identityKeyStore.getIdentity(address); | ||||
|     } | ||||
| 
 | ||||
|     public JsonIdentityKeyStore.Identity getIdentity(SignalServiceAddress serviceAddress) { | ||||
|         return identityKeyStore.getIdentity(serviceAddress); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { | ||||
|         return preKeyStore.loadPreKey(preKeyId); | ||||
| @ -143,6 +156,10 @@ public class JsonSignalProtocolStore implements SignalProtocolStore { | ||||
|         sessionStore.deleteAllSessions(name); | ||||
|     } | ||||
| 
 | ||||
|     public void deleteAllSessions(SignalServiceAddress serviceAddress) { | ||||
|         sessionStore.deleteAllSessions(serviceAddress); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { | ||||
|         return signedPreKeyStore.loadSignedPreKey(signedPreKeyId); | ||||
|  | ||||
| @ -0,0 +1,18 @@ | ||||
| package org.asamk.signal.storage.protocol; | ||||
| 
 | ||||
| import org.whispersystems.signalservice.api.push.SignalServiceAddress; | ||||
| 
 | ||||
| public class SessionInfo { | ||||
| 
 | ||||
|     public SignalServiceAddress address; | ||||
| 
 | ||||
|     public int deviceId; | ||||
| 
 | ||||
|     public byte[] sessionRecord; | ||||
| 
 | ||||
|     public SessionInfo(final SignalServiceAddress address, final int deviceId, final byte[] sessionRecord) { | ||||
|         this.address = address; | ||||
|         this.deviceId = deviceId; | ||||
|         this.sessionRecord = sessionRecord; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| package org.asamk.signal.storage.protocol; | ||||
| 
 | ||||
| import org.whispersystems.signalservice.api.push.SignalServiceAddress; | ||||
| 
 | ||||
| public interface SignalServiceAddressResolver { | ||||
| 
 | ||||
|     /** | ||||
|      * Get a SignalServiceAddress with number and/or uuid from an identifier name. | ||||
|      * | ||||
|      * @param identifier can be either a serialized uuid or a e164 phone number | ||||
|      */ | ||||
|     SignalServiceAddress resolveSignalServiceAddress(String identifier); | ||||
| } | ||||
| @ -3,6 +3,10 @@ package org.asamk.signal.util; | ||||
| import com.fasterxml.jackson.databind.JsonNode; | ||||
| 
 | ||||
| import org.asamk.signal.GroupIdFormatException; | ||||
| import org.whispersystems.signalservice.api.push.SignalServiceAddress; | ||||
| import org.whispersystems.signalservice.api.util.InvalidNumberException; | ||||
| import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; | ||||
| import org.whispersystems.signalservice.api.util.UuidUtil; | ||||
| import org.whispersystems.util.Base64; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| @ -51,4 +55,16 @@ public class Util { | ||||
|             throw new GroupIdFormatException(groupId, e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException { | ||||
|         return PhoneNumberFormatter.formatNumber(number, localNumber); | ||||
|     } | ||||
| 
 | ||||
|     public static SignalServiceAddress getSignalServiceAddressFromIdentifier(final String identifier) { | ||||
|         if (UuidUtil.isUuid(identifier)) { | ||||
|             return new SignalServiceAddress(UuidUtil.parseOrNull(identifier), null); | ||||
|         } else { | ||||
|             return new SignalServiceAddress(null, identifier); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 AsamK
						AsamK