]> nmode's Git Repositories - signal-cli/commitdiff
Refactor identity key store
authorAsamK <asamk@gmx.de>
Sun, 18 Apr 2021 16:26:12 +0000 (18:26 +0200)
committerAsamK <asamk@gmx.de>
Sat, 1 May 2021 06:46:00 +0000 (08:46 +0200)
19 files changed:
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java
lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityInfo.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java [deleted file]
lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyIdentityInfo.java [moved from lib/src/main/java/org/asamk/signal/manager/storage/protocol/IdentityInfo.java with 88% similarity]
lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonIdentityKeyStore.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSessionStore.java
lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSignalProtocolStore.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacySessionInfo.java [moved from lib/src/main/java/org/asamk/signal/manager/storage/protocol/SessionInfo.java with 70% similarity]
lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java [moved from lib/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSignalProtocolStore.java with 51% similarity]
lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalServiceAddressResolver.java [deleted file]
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java
lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java
lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java
src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java
src/main/java/org/asamk/signal/commands/TrustCommand.java
src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java

index c1189df30477fcf512678383c34c76e99fbadff6..a8eaa9abef96287a7cf0100f54f6bc4618a9f71b 100644 (file)
@@ -34,9 +34,10 @@ import org.asamk.signal.manager.storage.contacts.ContactInfo;
 import org.asamk.signal.manager.storage.groups.GroupInfo;
 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
 import org.asamk.signal.manager.storage.groups.GroupInfoV2;
+import org.asamk.signal.manager.storage.identities.IdentityInfo;
 import org.asamk.signal.manager.storage.messageCache.CachedMessage;
 import org.asamk.signal.manager.storage.profiles.SignalProfile;
-import org.asamk.signal.manager.storage.protocol.IdentityInfo;
+import org.asamk.signal.manager.storage.recipients.RecipientId;
 import org.asamk.signal.manager.storage.stickers.Sticker;
 import org.asamk.signal.manager.util.AttachmentUtils;
 import org.asamk.signal.manager.util.IOUtils;
@@ -234,8 +235,6 @@ public class Manager implements Closeable {
                 clientZkProfileOperations,
                 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
 
-        this.account.setResolver(this::resolveSignalServiceAddress);
-
         this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
                 account.getProfileStore()::getProfileKey,
                 this::getRecipientProfile,
@@ -1223,17 +1222,7 @@ public class Manager implements Closeable {
 
     private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException {
         var messageSender = createMessageSender();
-        try {
-            messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync());
-        } catch (UntrustedIdentityException e) {
-            if (e.getIdentityKey() != null) {
-                account.getSignalProtocolStore()
-                        .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
-                                e.getIdentityKey(),
-                                TrustLevel.UNTRUSTED);
-            }
-            throw e;
-        }
+        messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync());
     }
 
     private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
@@ -1303,22 +1292,8 @@ public class Manager implements Closeable {
                             unidentifiedAccessHelper.getAccessFor(recipients),
                             isRecipientUpdate,
                             message);
-                    for (var r : result) {
-                        if (r.getIdentityFailure() != null) {
-                            account.getSignalProtocolStore()
-                                    .saveIdentity(r.getAddress(),
-                                            r.getIdentityFailure().getIdentityKey(),
-                                            TrustLevel.UNTRUSTED);
-                        }
-                    }
                     return new Pair<>(timestamp, result);
                 } catch (UntrustedIdentityException e) {
-                    if (e.getIdentityKey() != null) {
-                        account.getSignalProtocolStore()
-                                .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
-                                        e.getIdentityKey(),
-                                        TrustLevel.UNTRUSTED);
-                    }
                     return new Pair<>(timestamp, List.of());
                 }
             } else {
@@ -1388,12 +1363,6 @@ public class Manager implements Closeable {
                     false,
                     System.currentTimeMillis() - startTime);
         } catch (UntrustedIdentityException e) {
-            if (e.getIdentityKey() != null) {
-                account.getSignalProtocolStore()
-                        .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
-                                e.getIdentityKey(),
-                                TrustLevel.UNTRUSTED);
-            }
             return SendMessageResult.identityFailure(recipient, e.getIdentityKey());
         }
     }
@@ -1406,12 +1375,6 @@ public class Manager implements Closeable {
         try {
             return messageSender.sendMessage(address, unidentifiedAccessHelper.getAccessFor(address), message);
         } catch (UntrustedIdentityException e) {
-            if (e.getIdentityKey() != null) {
-                account.getSignalProtocolStore()
-                        .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
-                                e.getIdentityKey(),
-                                TrustLevel.UNTRUSTED);
-            }
             return SendMessageResult.identityFailure(address, e.getIdentityKey());
         }
     }
@@ -1424,15 +1387,7 @@ public class Manager implements Closeable {
             return cipher.decrypt(envelope);
         } catch (ProtocolUntrustedIdentityException e) {
             if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
-                var identityException = (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
-                final var untrustedIdentity = identityException.getUntrustedIdentity();
-                if (untrustedIdentity != null) {
-                    account.getSignalProtocolStore()
-                            .saveIdentity(resolveSignalServiceAddress(identityException.getName()),
-                                    untrustedIdentity,
-                                    TrustLevel.UNTRUSTED);
-                }
-                throw identityException;
+                throw (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
             }
             throw new AssertionError(e);
         }
@@ -2004,8 +1959,8 @@ public class Manager implements Closeable {
                                 }
                                 if (c.getVerified().isPresent()) {
                                     final var verifiedMessage = c.getVerified().get();
-                                    account.getSignalProtocolStore()
-                                            .setIdentityTrustLevel(verifiedMessage.getDestination(),
+                                    account.getIdentityKeyStore()
+                                            .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
                                                     verifiedMessage.getIdentityKey(),
                                                     TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
                                 }
@@ -2040,8 +1995,8 @@ public class Manager implements Closeable {
                 }
                 if (syncMessage.getVerified().isPresent()) {
                     final var verifiedMessage = syncMessage.getVerified().get();
-                    account.getSignalProtocolStore()
-                            .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()),
+                    account.getIdentityKeyStore()
+                            .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
                                     verifiedMessage.getIdentityKey(),
                                     TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
                 }
@@ -2283,7 +2238,8 @@ public class Manager implements Closeable {
                 var out = new DeviceContactsOutputStream(fos);
                 for (var record : account.getContactStore().getContacts()) {
                     VerifiedMessage verifiedMessage = null;
-                    var currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress());
+                    var currentIdentity = account.getIdentityKeyStore()
+                            .getIdentity(resolveRecipientTrusted(record.getAddress()));
                     if (currentIdentity != null) {
                         verifiedMessage = new VerifiedMessage(record.getAddress(),
                                 currentIdentity.getIdentityKey(),
@@ -2395,11 +2351,12 @@ public class Manager implements Closeable {
     }
 
     public List<IdentityInfo> getIdentities() {
-        return account.getSignalProtocolStore().getIdentities();
+        return account.getIdentityKeyStore().getIdentities();
     }
 
     public List<IdentityInfo> getIdentities(String number) throws InvalidNumberException {
-        return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number));
+        final var identity = account.getIdentityKeyStore().getIdentity(canonicalizeAndResolveRecipient(number));
+        return identity == null ? List.of() : List.of(identity);
     }
 
     /**
@@ -2409,8 +2366,10 @@ public class Manager implements Closeable {
      * @param fingerprint Fingerprint
      */
     public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
-        var address = canonicalizeAndResolveSignalServiceAddress(name);
-        return trustIdentity(address, (identityKey) -> Arrays.equals(identityKey.serialize(), fingerprint));
+        var recipientId = canonicalizeAndResolveRecipient(name);
+        return trustIdentity(recipientId,
+                identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
+                TrustLevel.TRUSTED_VERIFIED);
     }
 
     /**
@@ -2420,72 +2379,43 @@ public class Manager implements Closeable {
      * @param safetyNumber Safety number
      */
     public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
-        var address = canonicalizeAndResolveSignalServiceAddress(name);
-        return trustIdentity(address, (identityKey) -> safetyNumber.equals(computeSafetyNumber(address, identityKey)));
+        var recipientId = canonicalizeAndResolveRecipient(name);
+        var address = account.getRecipientStore().resolveServiceAddress(recipientId);
+        return trustIdentity(recipientId,
+                identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)),
+                TrustLevel.TRUSTED_VERIFIED);
     }
 
-    private boolean trustIdentity(SignalServiceAddress address, Function<IdentityKey, Boolean> verifier) {
-        var ids = account.getSignalProtocolStore().getIdentities(address);
-        if (ids == null) {
-            return false;
-        }
-
-        IdentityInfo foundIdentity = null;
+    /**
+     * Trust all keys of this identity without verification
+     *
+     * @param name username of the identity
+     */
+    public boolean trustIdentityAllKeys(String name) throws InvalidNumberException {
+        var recipientId = canonicalizeAndResolveRecipient(name);
+        return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
+    }
 
-        for (var id : ids) {
-            if (verifier.apply(id.getIdentityKey())) {
-                foundIdentity = id;
-                break;
-            }
+    private boolean trustIdentity(
+            RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
+    ) {
+        var identity = account.getIdentityKeyStore().getIdentity(recipientId);
+        if (identity == null) {
+            return false;
         }
 
-        if (foundIdentity == null) {
+        if (!verifier.apply(identity.getIdentityKey())) {
             return false;
         }
 
-        account.getSignalProtocolStore()
-                .setIdentityTrustLevel(address, foundIdentity.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
+        account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
         try {
-            sendVerifiedMessage(address, foundIdentity.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
+            var address = account.getRecipientStore().resolveServiceAddress(recipientId);
+            sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
         } catch (IOException | UntrustedIdentityException e) {
             logger.warn("Failed to send verification sync message: {}", e.getMessage());
         }
 
-        // Successfully trusted the new identity, now remove all other identities for that number
-        for (var id : ids) {
-            if (id == foundIdentity) {
-                continue;
-            }
-            account.getSignalProtocolStore().removeIdentity(address, id.getIdentityKey());
-        }
-
-        account.save();
-        return true;
-    }
-
-    /**
-     * Trust all keys of this identity without verification
-     *
-     * @param name username of the identity
-     */
-    public boolean trustIdentityAllKeys(String name) {
-        var address = resolveSignalServiceAddress(name);
-        var ids = account.getSignalProtocolStore().getIdentities(address);
-        if (ids == null) {
-            return false;
-        }
-        for (var id : ids) {
-            if (id.getTrustLevel() == TrustLevel.UNTRUSTED) {
-                account.getSignalProtocolStore()
-                        .setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
-                try {
-                    sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
-                } catch (IOException | UntrustedIdentityException e) {
-                    logger.warn("Failed to send verification sync message: {}", e.getMessage());
-                }
-            }
-        }
-        account.save();
         return true;
     }
 
@@ -2499,6 +2429,7 @@ public class Manager implements Closeable {
                 theirIdentityKey);
     }
 
+    @Deprecated
     public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
         var canonicalizedNumber = UuidUtil.isUuid(identifier)
                 ? identifier
@@ -2506,12 +2437,14 @@ public class Manager implements Closeable {
         return resolveSignalServiceAddress(canonicalizedNumber);
     }
 
+    @Deprecated
     public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
         var address = Utils.getSignalServiceAddressFromIdentifier(identifier);
 
         return resolveSignalServiceAddress(address);
     }
 
+    @Deprecated
     public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) {
         if (address.matches(account.getSelfAddress())) {
             return account.getSelfAddress();
@@ -2520,6 +2453,27 @@ public class Manager implements Closeable {
         return account.getRecipientStore().resolveServiceAddress(address);
     }
 
+    public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
+        return account.getRecipientStore().resolveServiceAddress(recipientId);
+    }
+
+    public RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
+        var canonicalizedNumber = UuidUtil.isUuid(identifier)
+                ? identifier
+                : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
+        var address = Utils.getSignalServiceAddressFromIdentifier(canonicalizedNumber);
+
+        return resolveRecipient(address);
+    }
+
+    public RecipientId resolveRecipient(SignalServiceAddress address) {
+        return account.getRecipientStore().resolveRecipientUntrusted(address);
+    }
+
+    private RecipientId resolveRecipientTrusted(SignalServiceAddress address) {
+        return account.getRecipientStore().resolveRecipient(address);
+    }
+
     @Override
     public void close() throws IOException {
         close(true);
index a16ead37c3a26b5680cb1add71d50d43c054fe09..9c1eecce60d0573be00330dc5964e5e4c09006a3 100644 (file)
@@ -40,6 +40,7 @@ import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
+import java.util.Date;
 import java.util.Locale;
 
 public class RegistrationManager implements Closeable {
@@ -164,10 +165,10 @@ public class RegistrationManager implements Closeable {
         account.setUuid(UuidUtil.parseOrNull(response.getUuid()));
         account.setRegistrationLockPin(pin);
         account.getSessionStore().archiveAllSessions();
-        account.getSignalProtocolStore()
-                .saveIdentity(account.getSelfAddress(),
-                        account.getIdentityKeyPair().getPublicKey(),
-                        TrustLevel.TRUSTED_VERIFIED);
+        final var recipientId = account.getRecipientStore().resolveRecipient(account.getSelfAddress());
+        final var publicKey = account.getIdentityKeyPair().getPublicKey();
+        account.getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
+        account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
 
         Manager m = null;
         try {
index 298dd1ed9d4e19f05ded4112864647f837710170..b1b888ccfaad2c3b1e791074349d2a86c1dc8003 100644 (file)
@@ -14,12 +14,13 @@ import org.asamk.signal.manager.storage.contacts.ContactInfo;
 import org.asamk.signal.manager.storage.contacts.JsonContactsStore;
 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
 import org.asamk.signal.manager.storage.groups.JsonGroupStore;
+import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
 import org.asamk.signal.manager.storage.messageCache.MessageCache;
 import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
 import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
 import org.asamk.signal.manager.storage.profiles.ProfileStore;
-import org.asamk.signal.manager.storage.protocol.JsonSignalProtocolStore;
-import org.asamk.signal.manager.storage.protocol.SignalServiceAddressResolver;
+import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
+import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
 import org.asamk.signal.manager.storage.recipients.RecipientId;
 import org.asamk.signal.manager.storage.recipients.RecipientStore;
@@ -81,10 +82,11 @@ public class SignalAccount implements Closeable {
 
     private boolean registered = false;
 
-    private JsonSignalProtocolStore signalProtocolStore;
+    private SignalProtocolStore signalProtocolStore;
     private PreKeyStore preKeyStore;
     private SignedPreKeyStore signedPreKeyStore;
     private SessionStore sessionStore;
+    private IdentityKeyStore identityKeyStore;
     private JsonGroupStore groupStore;
     private JsonContactsStore contactStore;
     private RecipientStore recipientStore;
@@ -141,11 +143,14 @@ public class SignalAccount implements Closeable {
         account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
         account.sessionStore = new SessionStore(getSessionsPath(dataPath, username),
                 account.recipientStore::resolveRecipient);
-        account.signalProtocolStore = new JsonSignalProtocolStore(identityKey,
-                registrationId,
-                account.preKeyStore,
+        account.identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
+                account.recipientStore::resolveRecipient,
+                identityKey,
+                registrationId);
+        account.signalProtocolStore = new SignalProtocolStore(account.preKeyStore,
                 account.signedPreKeyStore,
-                account.sessionStore);
+                account.sessionStore,
+                account.identityKeyStore);
         account.profileStore = new ProfileStore();
         account.stickerStore = new StickerStore();
 
@@ -190,11 +195,14 @@ public class SignalAccount implements Closeable {
         account.signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
         account.sessionStore = new SessionStore(getSessionsPath(dataPath, username),
                 account.recipientStore::resolveRecipient);
-        account.signalProtocolStore = new JsonSignalProtocolStore(identityKey,
-                registrationId,
-                account.preKeyStore,
+        account.identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
+                account.recipientStore::resolveRecipient,
+                identityKey,
+                registrationId);
+        account.signalProtocolStore = new SignalProtocolStore(account.preKeyStore,
                 account.signedPreKeyStore,
-                account.sessionStore);
+                account.sessionStore,
+                account.identityKeyStore);
         account.profileStore = new ProfileStore();
         account.stickerStore = new StickerStore();
 
@@ -235,6 +243,7 @@ public class SignalAccount implements Closeable {
 
     private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
         sessionStore.mergeRecipients(recipientId, toBeMergedRecipientId);
+        identityKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId);
     }
 
     public static File getFileName(File dataPath, String username) {
@@ -261,6 +270,10 @@ public class SignalAccount implements Closeable {
         return new File(getUserPath(dataPath, username), "signed-pre-keys");
     }
 
+    private static File getIdentitiesPath(File dataPath, String username) {
+        return new File(getUserPath(dataPath, username), "identities");
+    }
+
     private static File getSessionsPath(File dataPath, String username) {
         return new File(getUserPath(dataPath, username), "sessions");
     }
@@ -299,6 +312,17 @@ public class SignalAccount implements Closeable {
         }
         username = Utils.getNotNullNode(rootNode, "username").asText();
         password = Utils.getNotNullNode(rootNode, "password").asText();
+        int registrationId = 0;
+        if (rootNode.hasNonNull("registrationId")) {
+            registrationId = rootNode.get("registrationId").asInt();
+        }
+        IdentityKeyPair identityKeyPair = null;
+        if (rootNode.hasNonNull("identityPrivateKey") && rootNode.hasNonNull("identityKey")) {
+            final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("identityKey").asText());
+            final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("identityPrivateKey").asText());
+            identityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
+        }
+
         if (rootNode.hasNonNull("registrationLockPin")) {
             registrationLockPin = rootNode.get("registrationLockPin").asText();
         }
@@ -338,13 +362,15 @@ public class SignalAccount implements Closeable {
             }
         }
 
-        signalProtocolStore = jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
-                JsonSignalProtocolStore.class);
+        var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
+                ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
+                LegacyJsonSignalProtocolStore.class)
+                : null;
 
         preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
-        if (signalProtocolStore.getLegacyPreKeyStore() != null) {
+        if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
             logger.debug("Migrating legacy pre key store.");
-            for (var entry : signalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
+            for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
                 try {
                     preKeyStore.storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
                 } catch (IOException e) {
@@ -352,12 +378,11 @@ public class SignalAccount implements Closeable {
                 }
             }
         }
-        signalProtocolStore.setPreKeyStore(preKeyStore);
 
         signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
-        if (signalProtocolStore.getLegacySignedPreKeyStore() != null) {
+        if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
             logger.debug("Migrating legacy signed pre key store.");
-            for (var entry : signalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
+            for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
                 try {
                     signedPreKeyStore.storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
                 } catch (IOException e) {
@@ -365,12 +390,11 @@ public class SignalAccount implements Closeable {
                 }
             }
         }
-        signalProtocolStore.setSignedPreKeyStore(signedPreKeyStore);
 
         sessionStore = new SessionStore(getSessionsPath(dataPath, username), recipientStore::resolveRecipient);
-        if (signalProtocolStore.getLegacySessionStore() != null) {
+        if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
             logger.debug("Migrating legacy session store.");
-            for (var session : signalProtocolStore.getLegacySessionStore().getSessions()) {
+            for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
                 try {
                     sessionStore.storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
                             session.deviceId), new SessionRecord(session.sessionRecord));
@@ -379,7 +403,27 @@ public class SignalAccount implements Closeable {
                 }
             }
         }
-        signalProtocolStore.setSessionStore(sessionStore);
+
+        if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
+            identityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
+            registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
+        }
+        identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
+                recipientStore::resolveRecipient,
+                identityKeyPair,
+                registrationId);
+        if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
+            logger.debug("Migrating identity session store.");
+            for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
+                RecipientId recipientId = recipientStore.resolveRecipient(identity.getAddress());
+                identityKeyStore.saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded());
+                identityKeyStore.setIdentityTrustLevel(recipientId,
+                        identity.getIdentityKey(),
+                        identity.getTrustLevel());
+            }
+        }
+
+        signalProtocolStore = new SignalProtocolStore(preKeyStore, signedPreKeyStore, sessionStore, identityKeyStore);
 
         registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
         var groupStoreNode = rootNode.get("groupStore");
@@ -431,10 +475,6 @@ public class SignalAccount implements Closeable {
                             .collect(Collectors.toSet());
                 }
             }
-
-            for (var identity : signalProtocolStore.getIdentities()) {
-                identity.setAddress(recipientStore.resolveServiceAddress(identity.getAddress()));
-            }
         }
 
         messageCache = new MessageCache(getMessageCachePath(dataPath, username));
@@ -475,6 +515,13 @@ public class SignalAccount implements Closeable {
                 .put("deviceId", deviceId)
                 .put("isMultiDevice", isMultiDevice)
                 .put("password", password)
+                .put("registrationId", identityKeyStore.getLocalRegistrationId())
+                .put("identityPrivateKey",
+                        Base64.getEncoder()
+                                .encodeToString(identityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()))
+                .put("identityKey",
+                        Base64.getEncoder()
+                                .encodeToString(identityKeyStore.getIdentityKeyPair().getPublicKey().serialize()))
                 .put("registrationLockPin", registrationLockPin)
                 .put("pinMasterKey",
                         pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
@@ -484,7 +531,6 @@ public class SignalAccount implements Closeable {
                 .put("nextSignedPreKeyId", nextSignedPreKeyId)
                 .put("profileKey", Base64.getEncoder().encodeToString(profileKey.serialize()))
                 .put("registered", registered)
-                .putPOJO("axolotlStore", signalProtocolStore)
                 .putPOJO("groupStore", groupStore)
                 .putPOJO("contactStore", contactStore)
                 .putPOJO("profileStore", profileStore)
@@ -517,10 +563,6 @@ public class SignalAccount implements Closeable {
         return new Pair<>(fileChannel, lock);
     }
 
-    public void setResolver(final SignalServiceAddressResolver resolver) {
-        signalProtocolStore.setResolver(resolver);
-    }
-
     public void addPreKeys(List<PreKeyRecord> records) {
         for (var record : records) {
             if (preKeyIdOffset != record.getId()) {
@@ -543,7 +585,7 @@ public class SignalAccount implements Closeable {
         save();
     }
 
-    public JsonSignalProtocolStore getSignalProtocolStore() {
+    public SignalProtocolStore getSignalProtocolStore() {
         return signalProtocolStore;
     }
 
@@ -551,6 +593,10 @@ public class SignalAccount implements Closeable {
         return sessionStore;
     }
 
+    public IdentityKeyStore getIdentityKeyStore() {
+        return identityKeyStore;
+    }
+
     public JsonGroupStore getGroupStore() {
         return groupStore;
     }
diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityInfo.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityInfo.java
new file mode 100644 (file)
index 0000000..31b2a85
--- /dev/null
@@ -0,0 +1,48 @@
+package org.asamk.signal.manager.storage.identities;
+
+import org.asamk.signal.manager.TrustLevel;
+import org.asamk.signal.manager.storage.recipients.RecipientId;
+import org.whispersystems.libsignal.IdentityKey;
+
+import java.util.Date;
+
+public class IdentityInfo {
+
+    private final RecipientId recipientId;
+    private final IdentityKey identityKey;
+    private final TrustLevel trustLevel;
+    private final Date added;
+
+    IdentityInfo(
+            final RecipientId recipientId, IdentityKey identityKey, TrustLevel trustLevel, Date added
+    ) {
+        this.recipientId = recipientId;
+        this.identityKey = identityKey;
+        this.trustLevel = trustLevel;
+        this.added = added;
+    }
+
+    public RecipientId getRecipientId() {
+        return recipientId;
+    }
+
+    public IdentityKey getIdentityKey() {
+        return this.identityKey;
+    }
+
+    public TrustLevel getTrustLevel() {
+        return this.trustLevel;
+    }
+
+    boolean isTrusted() {
+        return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED;
+    }
+
+    public Date getDateAdded() {
+        return this.added;
+    }
+
+    public byte[] getFingerprint() {
+        return identityKey.getPublicKey().serialize();
+    }
+}
diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java
new file mode 100644 (file)
index 0000000..d1cfced
--- /dev/null
@@ -0,0 +1,273 @@
+package org.asamk.signal.manager.storage.identities;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.asamk.signal.manager.TrustLevel;
+import org.asamk.signal.manager.storage.recipients.RecipientId;
+import org.asamk.signal.manager.storage.recipients.RecipientResolver;
+import org.asamk.signal.manager.util.IOUtils;
+import org.asamk.signal.manager.util.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.IdentityKeyPair;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.SignalProtocolAddress;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class IdentityKeyStore implements org.whispersystems.libsignal.state.IdentityKeyStore {
+
+    private final static Logger logger = LoggerFactory.getLogger(IdentityKeyStore.class);
+    private final ObjectMapper objectMapper = org.asamk.signal.manager.storage.Utils.createStorageObjectMapper();
+
+    private final Map<RecipientId, IdentityInfo> cachedIdentities = new HashMap<>();
+
+    private final File identitiesPath;
+
+    private final RecipientResolver resolver;
+    private final IdentityKeyPair identityKeyPair;
+    private final int localRegistrationId;
+
+    public IdentityKeyStore(
+            final File identitiesPath,
+            final RecipientResolver resolver,
+            final IdentityKeyPair identityKeyPair,
+            final int localRegistrationId
+    ) {
+        this.identitiesPath = identitiesPath;
+        this.resolver = resolver;
+        this.identityKeyPair = identityKeyPair;
+        this.localRegistrationId = localRegistrationId;
+    }
+
+    @Override
+    public IdentityKeyPair getIdentityKeyPair() {
+        return identityKeyPair;
+    }
+
+    @Override
+    public int getLocalRegistrationId() {
+        return localRegistrationId;
+    }
+
+    @Override
+    public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
+        final var recipientId = resolveRecipient(address.getName());
+
+        return saveIdentity(recipientId, identityKey, new Date());
+    }
+
+    public boolean saveIdentity(final RecipientId recipientId, final IdentityKey identityKey, Date added) {
+        synchronized (cachedIdentities) {
+            final var identityInfo = loadIdentityLocked(recipientId);
+            if (identityInfo != null && identityInfo.getIdentityKey().equals(identityKey)) {
+                // Identity already exists, not updating the trust level
+                return false;
+            }
+
+            final var trustLevel = identityInfo == null ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED;
+            final var newIdentityInfo = new IdentityInfo(recipientId, identityKey, trustLevel, added);
+            storeIdentityLocked(recipientId, newIdentityInfo);
+            return true;
+        }
+    }
+
+    public boolean setIdentityTrustLevel(
+            RecipientId recipientId, IdentityKey identityKey, TrustLevel trustLevel
+    ) {
+        synchronized (cachedIdentities) {
+            final var identityInfo = loadIdentityLocked(recipientId);
+            if (identityInfo == null || !identityInfo.getIdentityKey().equals(identityKey)) {
+                // Identity not found, not updating the trust level
+                return false;
+            }
+
+            final var newIdentityInfo = new IdentityInfo(recipientId,
+                    identityKey,
+                    trustLevel,
+                    identityInfo.getDateAdded());
+            storeIdentityLocked(recipientId, newIdentityInfo);
+            return true;
+        }
+    }
+
+    @Override
+    public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
+        var recipientId = resolveRecipient(address.getName());
+
+        synchronized (cachedIdentities) {
+            final var identityInfo = loadIdentityLocked(recipientId);
+            if (identityInfo == null) {
+                // Identity not found
+                return true;
+            }
+
+            // TODO implement possibility for different handling of incoming/outgoing trust decisions
+            if (!identityInfo.getIdentityKey().equals(identityKey)) {
+                // Identity found, but different
+                return false;
+            }
+
+            return identityInfo.isTrusted();
+        }
+    }
+
+    @Override
+    public IdentityKey getIdentity(SignalProtocolAddress address) {
+        var recipientId = resolveRecipient(address.getName());
+
+        synchronized (cachedIdentities) {
+            var identity = loadIdentityLocked(recipientId);
+            return identity == null ? null : identity.getIdentityKey();
+        }
+    }
+
+    public IdentityInfo getIdentity(RecipientId recipientId) {
+        synchronized (cachedIdentities) {
+            return loadIdentityLocked(recipientId);
+        }
+    }
+
+    final Pattern identityFileNamePattern = Pattern.compile("([0-9]+)");
+
+    public List<IdentityInfo> getIdentities() {
+        final var files = identitiesPath.listFiles();
+        if (files == null) {
+            return List.of();
+        }
+        return Arrays.stream(files)
+                .filter(f -> identityFileNamePattern.matcher(f.getName()).matches())
+                .map(f -> RecipientId.of(Integer.parseInt(f.getName())))
+                .map(this::loadIdentityLocked)
+                .collect(Collectors.toList());
+    }
+
+    public void mergeRecipients(final RecipientId recipientId, final RecipientId toBeMergedRecipientId) {
+        synchronized (cachedIdentities) {
+            deleteIdentityLocked(toBeMergedRecipientId);
+        }
+    }
+
+    /**
+     * @param identifier can be either a serialized uuid or a e164 phone number
+     */
+    private RecipientId resolveRecipient(String identifier) {
+        return resolver.resolveRecipient(Utils.getSignalServiceAddressFromIdentifier(identifier));
+    }
+
+    private File getIdentityFile(final RecipientId recipientId) {
+        try {
+            IOUtils.createPrivateDirectories(identitiesPath);
+        } catch (IOException e) {
+            throw new AssertionError("Failed to create identities path", e);
+        }
+        return new File(identitiesPath, String.valueOf(recipientId.getId()));
+    }
+
+    private IdentityInfo loadIdentityLocked(final RecipientId recipientId) {
+        {
+            final var session = cachedIdentities.get(recipientId);
+            if (session != null) {
+                return session;
+            }
+        }
+
+        final var file = getIdentityFile(recipientId);
+        if (!file.exists()) {
+            return null;
+        }
+        try (var inputStream = new FileInputStream(file)) {
+            var storage = objectMapper.readValue(inputStream, IdentityStorage.class);
+
+            var id = new IdentityKey(Base64.getDecoder().decode(storage.getIdentityKey()));
+            var trustLevel = TrustLevel.fromInt(storage.getTrustLevel());
+            var added = new Date(storage.getAddedTimestamp());
+
+            final var identityInfo = new IdentityInfo(recipientId, id, trustLevel, added);
+            cachedIdentities.put(recipientId, identityInfo);
+            return identityInfo;
+        } catch (IOException | InvalidKeyException e) {
+            logger.warn("Failed to load identity key: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    private void storeIdentityLocked(final RecipientId recipientId, final IdentityInfo identityInfo) {
+        cachedIdentities.put(recipientId, identityInfo);
+
+        var storage = new IdentityStorage(Base64.getEncoder().encodeToString(identityInfo.getIdentityKey().serialize()),
+                identityInfo.getTrustLevel().ordinal(),
+                identityInfo.getDateAdded().getTime());
+
+        final var file = getIdentityFile(recipientId);
+        // Write to memory first to prevent corrupting the file in case of serialization errors
+        try (var inMemoryOutput = new ByteArrayOutputStream()) {
+            objectMapper.writeValue(inMemoryOutput, storage);
+
+            var input = new ByteArrayInputStream(inMemoryOutput.toByteArray());
+            try (var outputStream = new FileOutputStream(file)) {
+                input.transferTo(outputStream);
+            }
+        } catch (Exception e) {
+            logger.error("Error saving identity file: {}", e.getMessage());
+        }
+    }
+
+    private void deleteIdentityLocked(final RecipientId recipientId) {
+        cachedIdentities.remove(recipientId);
+
+        final var file = getIdentityFile(recipientId);
+        if (!file.exists()) {
+            return;
+        }
+        try {
+            Files.delete(file.toPath());
+        } catch (IOException e) {
+            logger.error("Failed to delete identity file {}: {}", file, e.getMessage());
+        }
+    }
+
+    private static final class IdentityStorage {
+
+        private String identityKey;
+        private int trustLevel;
+        private long addedTimestamp;
+
+        // For deserialization
+        private IdentityStorage() {
+        }
+
+        private IdentityStorage(final String identityKey, final int trustLevel, final long addedTimestamp) {
+            this.identityKey = identityKey;
+            this.trustLevel = trustLevel;
+            this.addedTimestamp = addedTimestamp;
+        }
+
+        public String getIdentityKey() {
+            return identityKey;
+        }
+
+        public int getTrustLevel() {
+            return trustLevel;
+        }
+
+        public long getAddedTimestamp() {
+            return addedTimestamp;
+        }
+    }
+}
diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java
deleted file mode 100644 (file)
index 561138c..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-package org.asamk.signal.manager.storage.protocol;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
-
-import org.asamk.signal.manager.TrustLevel;
-import org.asamk.signal.manager.util.Utils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-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 java.io.IOException;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.Date;
-import java.util.List;
-
-public class JsonIdentityKeyStore implements IdentityKeyStore {
-
-    private final static Logger logger = LoggerFactory.getLogger(JsonIdentityKeyStore.class);
-
-    private final List<IdentityInfo> 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 Utils.getSignalServiceAddressFromIdentifier(identifier);
-        }
-    }
-
-    @Override
-    public IdentityKeyPair getIdentityKeyPair() {
-        return identityKeyPair;
-    }
-
-    @Override
-    public int getLocalRegistrationId() {
-        return localRegistrationId;
-    }
-
-    @Override
-    public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
-        return saveIdentity(resolveSignalServiceAddress(address.getName()),
-                identityKey,
-                TrustLevel.TRUSTED_UNVERIFIED,
-                null);
-    }
-
-    /**
-     * 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 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(
-            SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel, Date added
-    ) {
-        for (var 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 IdentityInfo(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 (var 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 IdentityInfo(serviceAddress, identityKey, trustLevel, new Date()));
-    }
-
-    public void removeIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey) {
-        identities.removeIf(id -> id.address.matches(serviceAddress) && id.identityKey.equals(identityKey));
-    }
-
-    @Override
-    public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
-        // TODO implement possibility for different handling of incoming/outgoing trust decisions
-        var serviceAddress = resolveSignalServiceAddress(address.getName());
-        var trustOnFirstUse = true;
-
-        for (var id : identities) {
-            if (!id.address.matches(serviceAddress)) {
-                continue;
-            }
-
-            if (id.identityKey.equals(identityKey)) {
-                return id.isTrusted();
-            } else {
-                trustOnFirstUse = false;
-            }
-        }
-
-        if (!trustOnFirstUse) {
-            saveIdentity(resolveSignalServiceAddress(address.getName()), identityKey, TrustLevel.UNTRUSTED, null);
-        }
-
-        return trustOnFirstUse;
-    }
-
-    @Override
-    public IdentityKey getIdentity(SignalProtocolAddress address) {
-        var serviceAddress = resolveSignalServiceAddress(address.getName());
-        var identity = getIdentity(serviceAddress);
-        return identity == null ? null : identity.getIdentityKey();
-    }
-
-    public IdentityInfo getIdentity(SignalServiceAddress serviceAddress) {
-        long maxDate = 0;
-        IdentityInfo maxIdentity = null;
-        for (var id : this.identities) {
-            if (!id.address.matches(serviceAddress)) {
-                continue;
-            }
-
-            final var time = id.getDateAdded().getTime();
-            if (maxIdentity == null || maxDate <= time) {
-                maxDate = time;
-                maxIdentity = id;
-            }
-        }
-        return maxIdentity;
-    }
-
-    public List<IdentityInfo> getIdentities() {
-        // TODO deep copy
-        return identities;
-    }
-
-    public List<IdentityInfo> getIdentities(SignalServiceAddress serviceAddress) {
-        var identities = new ArrayList<IdentityInfo>();
-        for (var identity : this.identities) {
-            if (identity.address.matches(serviceAddress)) {
-                identities.add(identity);
-            }
-        }
-        return identities;
-    }
-
-    public static class JsonIdentityKeyStoreDeserializer extends JsonDeserializer<JsonIdentityKeyStore> {
-
-        @Override
-        public JsonIdentityKeyStore deserialize(
-                JsonParser jsonParser, DeserializationContext deserializationContext
-        ) throws IOException {
-            JsonNode node = jsonParser.getCodec().readTree(jsonParser);
-
-            var localRegistrationId = node.get("registrationId").asInt();
-            var identityKeyPair = new IdentityKeyPair(Base64.getDecoder().decode(node.get("identityKey").asText()));
-
-            var keyStore = new JsonIdentityKeyStore(identityKeyPair, localRegistrationId);
-
-            var trustedKeysNode = node.get("trustedKeys");
-            if (trustedKeysNode.isArray()) {
-                for (var trustedKey : trustedKeysNode) {
-                    var trustedKeyName = trustedKey.hasNonNull("name") ? trustedKey.get("name").asText() : null;
-
-                    if (UuidUtil.isUuid(trustedKeyName)) {
-                        // Ignore identities that were incorrectly created with UUIDs as name
-                        continue;
-                    }
-
-                    var uuid = trustedKey.hasNonNull("uuid")
-                            ? UuidUtil.parseOrNull(trustedKey.get("uuid").asText())
-                            : null;
-                    final var serviceAddress = uuid == null
-                            ? Utils.getSignalServiceAddressFromIdentifier(trustedKeyName)
-                            : new SignalServiceAddress(uuid, trustedKeyName);
-                    try {
-                        var id = new IdentityKey(Base64.getDecoder().decode(trustedKey.get("identityKey").asText()), 0);
-                        var trustLevel = trustedKey.hasNonNull("trustLevel") ? TrustLevel.fromInt(trustedKey.get(
-                                "trustLevel").asInt()) : TrustLevel.TRUSTED_UNVERIFIED;
-                        var added = trustedKey.hasNonNull("addedTimestamp") ? new Date(trustedKey.get("addedTimestamp")
-                                .asLong()) : new Date();
-                        keyStore.saveIdentity(serviceAddress, id, trustLevel, added);
-                    } catch (InvalidKeyException e) {
-                        logger.warn("Error while decoding key for {}: {}", trustedKeyName, e.getMessage());
-                    }
-                }
-            }
-
-            return keyStore;
-        }
-    }
-
-    public static class JsonIdentityKeyStoreSerializer extends JsonSerializer<JsonIdentityKeyStore> {
-
-        @Override
-        public void serialize(
-                JsonIdentityKeyStore jsonIdentityKeyStore, JsonGenerator json, SerializerProvider serializerProvider
-        ) throws IOException {
-            json.writeStartObject();
-            json.writeNumberField("registrationId", jsonIdentityKeyStore.getLocalRegistrationId());
-            json.writeStringField("identityKey",
-                    Base64.getEncoder().encodeToString(jsonIdentityKeyStore.getIdentityKeyPair().serialize()));
-            json.writeStringField("identityPrivateKey",
-                    Base64.getEncoder()
-                            .encodeToString(jsonIdentityKeyStore.getIdentityKeyPair().getPrivateKey().serialize()));
-            json.writeStringField("identityPublicKey",
-                    Base64.getEncoder()
-                            .encodeToString(jsonIdentityKeyStore.getIdentityKeyPair().getPublicKey().serialize()));
-            json.writeArrayFieldStart("trustedKeys");
-            for (var 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.getEncoder().encodeToString(trustedKey.identityKey.serialize()));
-                json.writeNumberField("trustLevel", trustedKey.trustLevel.ordinal());
-                json.writeNumberField("addedTimestamp", trustedKey.added.getTime());
-                json.writeEndObject();
-            }
-            json.writeEndArray();
-            json.writeEndObject();
-        }
-    }
-}
similarity index 88%
rename from lib/src/main/java/org/asamk/signal/manager/storage/protocol/IdentityInfo.java
rename to lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyIdentityInfo.java
index 652bf524ef803aca8c48e538bc8e18f3d4fcc221..eb66b3e5cfad2c87b9c4c99bfa760504d14807f7 100644 (file)
@@ -6,14 +6,14 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
 import java.util.Date;
 
-public class IdentityInfo {
+public class LegacyIdentityInfo {
 
     SignalServiceAddress address;
     IdentityKey identityKey;
     TrustLevel trustLevel;
     Date added;
 
-    IdentityInfo(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) {
+    LegacyIdentityInfo(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) {
         this.address = address;
         this.identityKey = identityKey;
         this.trustLevel = trustLevel;
diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonIdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonIdentityKeyStore.java
new file mode 100644 (file)
index 0000000..781b6f9
--- /dev/null
@@ -0,0 +1,120 @@
+package org.asamk.signal.manager.storage.protocol;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import org.asamk.signal.manager.TrustLevel;
+import org.asamk.signal.manager.util.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.IdentityKeyPair;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class LegacyJsonIdentityKeyStore {
+
+    private final static Logger logger = LoggerFactory.getLogger(LegacyJsonIdentityKeyStore.class);
+
+    private final List<LegacyIdentityInfo> identities;
+    private final IdentityKeyPair identityKeyPair;
+    private final int localRegistrationId;
+
+    private LegacyJsonIdentityKeyStore(
+            final List<LegacyIdentityInfo> identities, IdentityKeyPair identityKeyPair, int localRegistrationId
+    ) {
+        this.identities = identities;
+        this.identityKeyPair = identityKeyPair;
+        this.localRegistrationId = localRegistrationId;
+    }
+
+    public List<LegacyIdentityInfo> getIdentities() {
+        return identities.stream()
+                .map(LegacyIdentityInfo::getAddress)
+                .collect(Collectors.toSet())
+                .stream()
+                .map(this::getIdentity)
+                .collect(Collectors.toList());
+    }
+
+    public IdentityKeyPair getIdentityKeyPair() {
+        return identityKeyPair;
+    }
+
+    public int getLocalRegistrationId() {
+        return localRegistrationId;
+    }
+
+    private LegacyIdentityInfo getIdentity(SignalServiceAddress serviceAddress) {
+        long maxDate = 0;
+        LegacyIdentityInfo maxIdentity = null;
+        for (var id : this.identities) {
+            if (!id.address.matches(serviceAddress)) {
+                continue;
+            }
+
+            final var time = id.getDateAdded().getTime();
+            if (maxIdentity == null || maxDate <= time) {
+                maxDate = time;
+                maxIdentity = id;
+            }
+        }
+        return maxIdentity;
+    }
+
+    public static class JsonIdentityKeyStoreDeserializer extends JsonDeserializer<LegacyJsonIdentityKeyStore> {
+
+        @Override
+        public LegacyJsonIdentityKeyStore deserialize(
+                JsonParser jsonParser, DeserializationContext deserializationContext
+        ) throws IOException {
+            JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+
+            var localRegistrationId = node.get("registrationId").asInt();
+            var identityKeyPair = new IdentityKeyPair(Base64.getDecoder().decode(node.get("identityKey").asText()));
+
+            var identities = new ArrayList<LegacyIdentityInfo>();
+
+            var trustedKeysNode = node.get("trustedKeys");
+            if (trustedKeysNode.isArray()) {
+                for (var trustedKey : trustedKeysNode) {
+                    var trustedKeyName = trustedKey.hasNonNull("name") ? trustedKey.get("name").asText() : null;
+
+                    if (UuidUtil.isUuid(trustedKeyName)) {
+                        // Ignore identities that were incorrectly created with UUIDs as name
+                        continue;
+                    }
+
+                    var uuid = trustedKey.hasNonNull("uuid")
+                            ? UuidUtil.parseOrNull(trustedKey.get("uuid").asText())
+                            : null;
+                    final var serviceAddress = uuid == null
+                            ? Utils.getSignalServiceAddressFromIdentifier(trustedKeyName)
+                            : new SignalServiceAddress(uuid, trustedKeyName);
+                    try {
+                        var id = new IdentityKey(Base64.getDecoder().decode(trustedKey.get("identityKey").asText()), 0);
+                        var trustLevel = trustedKey.hasNonNull("trustLevel") ? TrustLevel.fromInt(trustedKey.get(
+                                "trustLevel").asInt()) : TrustLevel.TRUSTED_UNVERIFIED;
+                        var added = trustedKey.hasNonNull("addedTimestamp") ? new Date(trustedKey.get("addedTimestamp")
+                                .asLong()) : new Date();
+                        identities.add(new LegacyIdentityInfo(serviceAddress, id, trustLevel, added));
+                    } catch (InvalidKeyException e) {
+                        logger.warn("Error while decoding key for {}: {}", trustedKeyName, e.getMessage());
+                    }
+                }
+            }
+
+            return new LegacyJsonIdentityKeyStore(identities, identityKeyPair, localRegistrationId);
+        }
+    }
+}
index 9a14ab60112f443ff1deffbe93c88dd88b6d1620..5f301aebaee00301d72903a064e08c82cbd415fe 100644 (file)
@@ -16,13 +16,13 @@ import java.util.List;
 
 public class LegacyJsonSessionStore {
 
-    private final List<SessionInfo> sessions;
+    private final List<LegacySessionInfo> sessions;
 
-    private LegacyJsonSessionStore(final List<SessionInfo> sessions) {
+    private LegacyJsonSessionStore(final List<LegacySessionInfo> sessions) {
         this.sessions = sessions;
     }
 
-    public List<SessionInfo> getSessions() {
+    public List<LegacySessionInfo> getSessions() {
         return sessions;
     }
 
@@ -34,7 +34,7 @@ public class LegacyJsonSessionStore {
         ) throws IOException {
             JsonNode node = jsonParser.getCodec().readTree(jsonParser);
 
-            var sessions = new ArrayList<SessionInfo>();
+            var sessions = new ArrayList<LegacySessionInfo>();
 
             if (node.isArray()) {
                 for (var session : node) {
@@ -50,7 +50,7 @@ public class LegacyJsonSessionStore {
                             : new SignalServiceAddress(uuid, sessionName);
                     final var deviceId = session.get("deviceId").asInt();
                     final var record = Base64.getDecoder().decode(session.get("record").asText());
-                    var sessionInfo = new SessionInfo(serviceAddress, deviceId, record);
+                    var sessionInfo = new LegacySessionInfo(serviceAddress, deviceId, record);
                     sessions.add(sessionInfo);
                 }
             }
diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSignalProtocolStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSignalProtocolStore.java
new file mode 100644 (file)
index 0000000..9cc4c6c
--- /dev/null
@@ -0,0 +1,42 @@
+package org.asamk.signal.manager.storage.protocol;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+public class LegacyJsonSignalProtocolStore {
+
+    @JsonProperty("preKeys")
+    @JsonDeserialize(using = LegacyJsonPreKeyStore.JsonPreKeyStoreDeserializer.class)
+    private LegacyJsonPreKeyStore legacyPreKeyStore;
+
+    @JsonProperty("sessionStore")
+    @JsonDeserialize(using = LegacyJsonSessionStore.JsonSessionStoreDeserializer.class)
+    private LegacyJsonSessionStore legacySessionStore;
+
+    @JsonProperty("signedPreKeyStore")
+    @JsonDeserialize(using = LegacyJsonSignedPreKeyStore.JsonSignedPreKeyStoreDeserializer.class)
+    private LegacyJsonSignedPreKeyStore legacySignedPreKeyStore;
+
+    @JsonProperty("identityKeyStore")
+    @JsonDeserialize(using = LegacyJsonIdentityKeyStore.JsonIdentityKeyStoreDeserializer.class)
+    private LegacyJsonIdentityKeyStore legacyIdentityKeyStore;
+
+    private LegacyJsonSignalProtocolStore() {
+    }
+
+    public LegacyJsonPreKeyStore getLegacyPreKeyStore() {
+        return legacyPreKeyStore;
+    }
+
+    public LegacyJsonSignedPreKeyStore getLegacySignedPreKeyStore() {
+        return legacySignedPreKeyStore;
+    }
+
+    public LegacyJsonSessionStore getLegacySessionStore() {
+        return legacySessionStore;
+    }
+
+    public LegacyJsonIdentityKeyStore getLegacyIdentityKeyStore() {
+        return legacyIdentityKeyStore;
+    }
+}
similarity index 70%
rename from lib/src/main/java/org/asamk/signal/manager/storage/protocol/SessionInfo.java
rename to lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacySessionInfo.java
index 802b896b37abbbe413ff23c7380e926ff5509e79..a19bbd8643fa7b90eb37eb0da9f5fa231e01793f 100644 (file)
@@ -2,7 +2,7 @@ package org.asamk.signal.manager.storage.protocol;
 
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
-public class SessionInfo {
+public class LegacySessionInfo {
 
     public SignalServiceAddress address;
 
@@ -10,7 +10,7 @@ public class SessionInfo {
 
     public byte[] sessionRecord;
 
-    public SessionInfo(final SignalServiceAddress address, final int deviceId, final byte[] sessionRecord) {
+    LegacySessionInfo(final SignalServiceAddress address, final int deviceId, final byte[] sessionRecord) {
         this.address = address;
         this.deviceId = deviceId;
         this.sessionRecord = sessionRecord;
similarity index 51%
rename from lib/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSignalProtocolStore.java
rename to lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java
index 913180841910ab357f2da1fc9795d37a11830745..1872e356ce73e2497f67aeb69c4d8219d9b5df96 100644 (file)
@@ -1,15 +1,10 @@
 package org.asamk.signal.manager.storage.protocol;
 
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
-import org.asamk.signal.manager.TrustLevel;
 import org.whispersystems.libsignal.IdentityKey;
 import org.whispersystems.libsignal.IdentityKeyPair;
 import org.whispersystems.libsignal.InvalidKeyIdException;
 import org.whispersystems.libsignal.SignalProtocolAddress;
+import org.whispersystems.libsignal.state.IdentityKeyStore;
 import org.whispersystems.libsignal.state.PreKeyRecord;
 import org.whispersystems.libsignal.state.PreKeyStore;
 import org.whispersystems.libsignal.state.SessionRecord;
@@ -17,76 +12,26 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord;
 import org.whispersystems.libsignal.state.SignedPreKeyStore;
 import org.whispersystems.signalservice.api.SignalServiceProtocolStore;
 import org.whispersystems.signalservice.api.SignalServiceSessionStore;
-import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
 import java.util.List;
 
-@JsonIgnoreProperties(value = {"sessionStore", "preKeys", "signedPreKeyStore"}, allowSetters = true)
-public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
-
-    @JsonProperty("preKeys")
-    @JsonDeserialize(using = LegacyJsonPreKeyStore.JsonPreKeyStoreDeserializer.class)
-    private LegacyJsonPreKeyStore legacyPreKeyStore;
-
-    @JsonProperty("sessionStore")
-    @JsonDeserialize(using = LegacyJsonSessionStore.JsonSessionStoreDeserializer.class)
-    private LegacyJsonSessionStore legacySessionStore;
-
-    @JsonProperty("signedPreKeyStore")
-    @JsonDeserialize(using = LegacyJsonSignedPreKeyStore.JsonSignedPreKeyStoreDeserializer.class)
-    private LegacyJsonSignedPreKeyStore legacySignedPreKeyStore;
-
-    @JsonProperty("identityKeyStore")
-    @JsonDeserialize(using = JsonIdentityKeyStore.JsonIdentityKeyStoreDeserializer.class)
-    @JsonSerialize(using = JsonIdentityKeyStore.JsonIdentityKeyStoreSerializer.class)
-    private JsonIdentityKeyStore identityKeyStore;
+public class SignalProtocolStore implements SignalServiceProtocolStore {
 
-    private PreKeyStore preKeyStore;
-    private SignedPreKeyStore signedPreKeyStore;
-    private SignalServiceSessionStore sessionStore;
+    private final PreKeyStore preKeyStore;
+    private final SignedPreKeyStore signedPreKeyStore;
+    private final SignalServiceSessionStore sessionStore;
+    private final IdentityKeyStore identityKeyStore;
 
-    public JsonSignalProtocolStore() {
-    }
-
-    public JsonSignalProtocolStore(
-            IdentityKeyPair identityKeyPair,
-            int registrationId,
-            PreKeyStore preKeyStore,
-            SignedPreKeyStore signedPreKeyStore,
-            SignalServiceSessionStore sessionStore
+    public SignalProtocolStore(
+            final PreKeyStore preKeyStore,
+            final SignedPreKeyStore signedPreKeyStore,
+            final SignalServiceSessionStore sessionStore,
+            final IdentityKeyStore identityKeyStore
     ) {
         this.preKeyStore = preKeyStore;
         this.signedPreKeyStore = signedPreKeyStore;
         this.sessionStore = sessionStore;
-        this.identityKeyStore = new JsonIdentityKeyStore(identityKeyPair, registrationId);
-    }
-
-    public void setResolver(final SignalServiceAddressResolver resolver) {
-        identityKeyStore.setResolver(resolver);
-    }
-
-    public void setPreKeyStore(final PreKeyStore preKeyStore) {
-        this.preKeyStore = preKeyStore;
-    }
-
-    public void setSignedPreKeyStore(final SignedPreKeyStore signedPreKeyStore) {
-        this.signedPreKeyStore = signedPreKeyStore;
-    }
-
-    public void setSessionStore(final SignalServiceSessionStore sessionStore) {
-        this.sessionStore = sessionStore;
-    }
-
-    public LegacyJsonPreKeyStore getLegacyPreKeyStore() {
-        return legacyPreKeyStore;
-    }
-
-    public LegacyJsonSignedPreKeyStore getLegacySignedPreKeyStore() {
-        return legacySignedPreKeyStore;
-    }
-
-    public LegacyJsonSessionStore getLegacySessionStore() {
-        return legacySessionStore;
+        this.identityKeyStore = identityKeyStore;
     }
 
     @Override
@@ -104,28 +49,6 @@ public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
         return identityKeyStore.saveIdentity(address, identityKey);
     }
 
-    public void saveIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel) {
-        identityKeyStore.saveIdentity(serviceAddress, identityKey, trustLevel, null);
-    }
-
-    public void setIdentityTrustLevel(
-            SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel
-    ) {
-        identityKeyStore.setIdentityTrustLevel(serviceAddress, identityKey, trustLevel);
-    }
-
-    public void removeIdentity(SignalServiceAddress serviceAddress, IdentityKey identityKey) {
-        identityKeyStore.removeIdentity(serviceAddress, identityKey);
-    }
-
-    public List<IdentityInfo> getIdentities() {
-        return identityKeyStore.getIdentities();
-    }
-
-    public List<IdentityInfo> getIdentities(SignalServiceAddress serviceAddress) {
-        return identityKeyStore.getIdentities(serviceAddress);
-    }
-
     @Override
     public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
         return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
@@ -136,10 +59,6 @@ public class JsonSignalProtocolStore implements SignalServiceProtocolStore {
         return identityKeyStore.getIdentity(address);
     }
 
-    public IdentityInfo getIdentity(SignalServiceAddress serviceAddress) {
-        return identityKeyStore.getIdentity(serviceAddress);
-    }
-
     @Override
     public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
         return preKeyStore.loadPreKey(preKeyId);
diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalServiceAddressResolver.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalServiceAddressResolver.java
deleted file mode 100644 (file)
index 86eea05..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.asamk.signal.manager.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);
-}
index 533808d885827d6f148519bf2d0dd5dbc724d210..7235b6626cadff64e6a7bf653ad37b9a2652f705 100644 (file)
@@ -229,14 +229,15 @@ public class RecipientStore {
     }
 
     private void save() {
+        var storage = new Storage(recipients.entrySet()
+                .stream()
+                .map(pair -> new Storage.Recipient(pair.getKey().getId(),
+                        pair.getValue().getNumber().orNull(),
+                        pair.getValue().getUuid().transform(UUID::toString).orNull()))
+                .collect(Collectors.toList()), lastId);
+
         // Write to memory first to prevent corrupting the file in case of serialization errors
         try (var inMemoryOutput = new ByteArrayOutputStream()) {
-            var storage = new Storage(recipients.entrySet()
-                    .stream()
-                    .map(pair -> new Storage.Recipient(pair.getKey().getId(),
-                            pair.getValue().getNumber().orNull(),
-                            pair.getValue().getUuid().transform(UUID::toString).orNull()))
-                    .collect(Collectors.toList()), lastId);
             objectMapper.writeValue(inMemoryOutput, storage);
 
             var input = new ByteArrayInputStream(inMemoryOutput.toByteArray());
index 0773af9abc3b6b5027dafeb78e78baa361084798..30df690045e99ebc2afff68346dce108c576725f 100644 (file)
@@ -199,7 +199,7 @@ public class SessionStore implements SignalServiceSessionStore {
                 .collect(Collectors.toList());
     }
 
-    private File getSessionPath(Key key) {
+    private File getSessionFile(Key key) {
         try {
             IOUtils.createPrivateDirectories(sessionsPath);
         } catch (IOException e) {
@@ -216,7 +216,7 @@ public class SessionStore implements SignalServiceSessionStore {
             }
         }
 
-        final var file = getSessionPath(key);
+        final var file = getSessionFile(key);
         if (!file.exists()) {
             return null;
         }
@@ -233,7 +233,7 @@ public class SessionStore implements SignalServiceSessionStore {
     private void storeSessionLocked(final Key key, final SessionRecord session) {
         cachedSessions.put(key, session);
 
-        final var file = getSessionPath(key);
+        final var file = getSessionFile(key);
         try {
             try (var outputStream = new FileOutputStream(file)) {
                 outputStream.write(session.serialize());
@@ -263,7 +263,7 @@ public class SessionStore implements SignalServiceSessionStore {
     private void deleteSessionLocked(final Key key) {
         cachedSessions.remove(key);
 
-        final var file = getSessionPath(key);
+        final var file = getSessionFile(key);
         if (!file.exists()) {
             return;
         }
index 5167331ab9441b03bf955e7f0aa6facbd970310b..03bf8d79ad75af6ca2a969180841c89c9d4b44ce 100644 (file)
@@ -6,6 +6,7 @@ import org.whispersystems.libsignal.IdentityKey;
 import org.whispersystems.libsignal.IdentityKeyPair;
 import org.whispersystems.libsignal.InvalidKeyException;
 import org.whispersystems.libsignal.ecc.Curve;
+import org.whispersystems.libsignal.ecc.ECPrivateKey;
 import org.whispersystems.libsignal.state.PreKeyRecord;
 import org.whispersystems.libsignal.state.SignedPreKeyRecord;
 import org.whispersystems.libsignal.util.Medium;
@@ -23,6 +24,17 @@ public class KeyUtils {
     private KeyUtils() {
     }
 
+    public static IdentityKeyPair getIdentityKeyPair(byte[] publicKeyBytes, byte[] privateKeyBytes) {
+        try {
+            IdentityKey publicKey = new IdentityKey(publicKeyBytes);
+            ECPrivateKey privateKey = Curve.decodePrivatePoint(privateKeyBytes);
+
+            return new IdentityKeyPair(publicKey, privateKey);
+        } catch (InvalidKeyException e) {
+            throw new AssertionError(e);
+        }
+    }
+
     public static IdentityKeyPair generateIdentityKeyPair() {
         var djbKeyPair = Curve.generateKeyPair();
         var djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
index dc2d92fb58db3ffa4ef34b5dda595b45f2f99d39..78bd65765084d2ea7ce1130896020d0f27a80326 100644 (file)
@@ -8,11 +8,12 @@ import org.asamk.signal.PlainTextWriterImpl;
 import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.storage.protocol.IdentityInfo;
+import org.asamk.signal.manager.storage.identities.IdentityInfo;
 import org.asamk.signal.util.Hex;
 import org.asamk.signal.util.Util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
 import java.util.List;
@@ -22,9 +23,10 @@ public class ListIdentitiesCommand implements LocalCommand {
     private final static Logger logger = LoggerFactory.getLogger(ListIdentitiesCommand.class);
 
     private static void printIdentityFingerprint(PlainTextWriter writer, Manager m, IdentityInfo theirId) {
-        var digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirId.getAddress(), theirId.getIdentityKey()));
+        final SignalServiceAddress address = m.resolveSignalServiceAddress(theirId.getRecipientId());
+        var digits = Util.formatSafetyNumber(m.computeSafetyNumber(address, theirId.getIdentityKey()));
         writer.println("{}: {} Added: {} Fingerprint: {} Safety Number: {}",
-                theirId.getAddress().getNumber().orNull(),
+                address.getNumber().orNull(),
                 theirId.getTrustLevel(),
                 theirId.getDateAdded(),
                 Hex.toString(theirId.getFingerprint()),
index 08fe6a41e30b4dd0bebbbb1c17cb91fc4722286b..00371b59e0378620b1daa21eed229d95201f032e 100644 (file)
@@ -29,7 +29,12 @@ public class TrustCommand implements LocalCommand {
     public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
         var number = ns.getString("number");
         if (ns.getBoolean("trust_all_known_keys")) {
-            var res = m.trustIdentityAllKeys(number);
+            boolean res;
+            try {
+                res = m.trustIdentityAllKeys(number);
+            } catch (InvalidNumberException e) {
+                throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
+            }
             if (!res) {
                 throw new UserErrorException("Failed to set the trust for this number, make sure the number is correct.");
             }
index 6b22029b2fc0434f05c0465f383116aabc5a6f6e..f50363cb3d2e5223689766ddd11ceac14dac7b1b 100644 (file)
@@ -8,6 +8,7 @@ import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
+import org.asamk.signal.manager.storage.identities.IdentityInfo;
 import org.asamk.signal.util.ErrorUtils;
 import org.freedesktop.dbus.exceptions.DBusExecutionException;
 import org.whispersystems.libsignal.util.guava.Optional;
@@ -144,7 +145,11 @@ public class DbusSignalImpl implements Signal {
 
     @Override
     public long sendMessageReaction(
-            final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final String recipient
+            final String emoji,
+            final boolean remove,
+            final String targetAuthor,
+            final long targetSentTimestamp,
+            final String recipient
     ) {
         var recipients = new ArrayList<String>(1);
         recipients.add(recipient);
@@ -153,7 +158,11 @@ public class DbusSignalImpl implements Signal {
 
     @Override
     public long sendMessageReaction(
-            final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final List<String> recipients
+            final String emoji,
+            final boolean remove,
+            final String targetAuthor,
+            final long targetSentTimestamp,
+            final List<String> recipients
     ) {
         try {
             final var results = m.sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
@@ -210,10 +219,18 @@ public class DbusSignalImpl implements Signal {
 
     @Override
     public long sendGroupMessageReaction(
-            final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final byte[] groupId
+            final String emoji,
+            final boolean remove,
+            final String targetAuthor,
+            final long targetSentTimestamp,
+            final byte[] groupId
     ) {
         try {
-            final var results = m.sendGroupMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, GroupId.unknownVersion(groupId));
+            final var results = m.sendGroupMessageReaction(emoji,
+                    remove,
+                    targetAuthor,
+                    targetSentTimestamp,
+                    GroupId.unknownVersion(groupId));
             checkSendMessageResults(results.first(), results.second());
             return results.first();
         } catch (IOException e) {
@@ -366,8 +383,11 @@ public class DbusSignalImpl implements Signal {
     // all numbers the system knows
     @Override
     public List<String> listNumbers() {
-        return Stream.concat(m.getIdentities().stream().map(i -> i.getAddress().getNumber().orNull()),
-                m.getContacts().stream().map(c -> c.number))
+        return Stream.concat(m.getIdentities()
+                .stream()
+                .map(IdentityInfo::getRecipientId)
+                .map(m::resolveSignalServiceAddress)
+                .map(a -> a.getNumber().orNull()), m.getContacts().stream().map(c -> c.number))
                 .filter(Objects::nonNull)
                 .distinct()
                 .collect(Collectors.toList());
@@ -385,7 +405,8 @@ public class DbusSignalImpl implements Signal {
         }
         // Try profiles if no contact name was found
         for (var identity : m.getIdentities()) {
-            final var address = identity.getAddress();
+            final var recipientId = identity.getRecipientId();
+            final var address = m.resolveSignalServiceAddress(recipientId);
             var number = address.getNumber().orNull();
             if (number != null) {
                 var profile = m.getRecipientProfile(address);