]> nmode's Git Repositories - signal-cli/commitdiff
Add new --trust-new-identities global parameter
authorAsamK <asamk@gmx.de>
Mon, 23 Aug 2021 13:50:03 +0000 (15:50 +0200)
committerAsamK <asamk@gmx.de>
Mon, 23 Aug 2021 13:58:05 +0000 (15:58 +0200)
Closes #360

CHANGELOG.md
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.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/IdentityKeyStore.java
lib/src/main/java/org/asamk/signal/manager/storage/identities/TrustNewIdentity.java [new file with mode: 0644]
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/App.java
src/main/java/org/asamk/signal/TrustNewIdentityCli.java [new file with mode: 0644]

index 8af9e358f1942f2ea0cddea932c76875e31cf534..e2ba6843061378dd7f2d71d61333b493e74566af 100644 (file)
@@ -8,6 +8,9 @@
 - Removed deprecated fallback data paths, only `$XDG_DATA_HOME/signal-cli` is used now
   For those still using the old paths (`$HOME/.config/signal`, `$HOME/.config/textsecure`) you need to move those to the new location.
 
 - Removed deprecated fallback data paths, only `$XDG_DATA_HOME/signal-cli` is used now
   For those still using the old paths (`$HOME/.config/signal`, `$HOME/.config/textsecure`) you need to move those to the new location.
 
+### Added
+- New global parameter `--trust-new-identities=always` to allow trusting any new identity key without verification
+
 ## [0.8.5] - 2021-08-07
 ### Added
 - Source name is included in JSON receive output (Thanks @technillogue)
 ## [0.8.5] - 2021-08-07
 ### Added
 - Source name is included in JSON receive output (Thanks @technillogue)
index 80c5fbbb3a7758886e04d980f58ca7966f54dee3..60ee407138a746053f575c20ed0a5001c1e99d3e 100644 (file)
@@ -43,6 +43,7 @@ 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.groups.GroupInfoV1;
 import org.asamk.signal.manager.storage.groups.GroupInfoV2;
 import org.asamk.signal.manager.storage.identities.IdentityInfo;
+import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
 import org.asamk.signal.manager.storage.messageCache.CachedMessage;
 import org.asamk.signal.manager.storage.recipients.Contact;
 import org.asamk.signal.manager.storage.recipients.Profile;
 import org.asamk.signal.manager.storage.messageCache.CachedMessage;
 import org.asamk.signal.manager.storage.recipients.Contact;
 import org.asamk.signal.manager.storage.recipients.Profile;
@@ -270,7 +271,11 @@ public class Manager implements Closeable {
     }
 
     public static Manager init(
     }
 
     public static Manager init(
-            String username, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
+            String username,
+            File settingsPath,
+            ServiceEnvironment serviceEnvironment,
+            String userAgent,
+            final TrustNewIdentity trustNewIdentity
     ) throws IOException, NotRegisteredException {
         var pathConfig = PathConfig.createDefault(settingsPath);
 
     ) throws IOException, NotRegisteredException {
         var pathConfig = PathConfig.createDefault(settingsPath);
 
@@ -278,7 +283,7 @@ public class Manager implements Closeable {
             throw new NotRegisteredException();
         }
 
             throw new NotRegisteredException();
         }
 
-        var account = SignalAccount.load(pathConfig.getDataPath(), username, true);
+        var account = SignalAccount.load(pathConfig.getDataPath(), username, true, trustNewIdentity);
 
         if (!account.isRegistered()) {
             throw new NotRegisteredException();
 
         if (!account.isRegistered()) {
             throw new NotRegisteredException();
index 7801e9994d008a25477eb7c6e38a385a0f6b516f..80c214f7123347b7e02fdea1b60c715f5145d9ce 100644 (file)
@@ -20,6 +20,7 @@ import org.asamk.signal.manager.config.ServiceConfig;
 import org.asamk.signal.manager.config.ServiceEnvironment;
 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.config.ServiceEnvironment;
 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -121,7 +122,8 @@ public class ProvisioningManager {
                     deviceId,
                     ret.getIdentity(),
                     registrationId,
                     deviceId,
                     ret.getIdentity(),
                     registrationId,
-                    profileKey);
+                    profileKey,
+                    TrustNewIdentity.ON_FIRST_USE);
 
             Manager m = null;
             try {
 
             Manager m = null;
             try {
@@ -161,7 +163,7 @@ public class ProvisioningManager {
     private boolean canRelinkExistingAccount(final String number) throws IOException {
         final SignalAccount signalAccount;
         try {
     private boolean canRelinkExistingAccount(final String number) throws IOException {
         final SignalAccount signalAccount;
         try {
-            signalAccount = SignalAccount.load(pathConfig.getDataPath(), number, false);
+            signalAccount = SignalAccount.load(pathConfig.getDataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
         } catch (IOException e) {
             logger.debug("Account in use or failed to load.", e);
             return false;
         } catch (IOException e) {
             logger.debug("Account in use or failed to load.", e);
             return false;
index 653f9cb403271cb73ace2c46246c2104893a11fd..95d43fd67a28b9d6b3c6ce2be58d67c0e31c937d 100644 (file)
@@ -21,6 +21,7 @@ import org.asamk.signal.manager.config.ServiceEnvironment;
 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.helper.PinHelper;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.helper.PinHelper;
 import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -102,12 +103,13 @@ public class RegistrationManager implements Closeable {
                     username,
                     identityKey,
                     registrationId,
                     username,
                     identityKey,
                     registrationId,
-                    profileKey);
+                    profileKey,
+                    TrustNewIdentity.ON_FIRST_USE);
 
             return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
         }
 
 
             return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
         }
 
-        var account = SignalAccount.load(pathConfig.getDataPath(), username, true);
+        var account = SignalAccount.load(pathConfig.getDataPath(), username, true, TrustNewIdentity.ON_FIRST_USE);
 
         return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
     }
 
         return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
     }
index 9b61fbb773a975b20a593ab8c857473a2646e99f..477e02dc0262a657ea4ff12705293b06160267f5 100644 (file)
@@ -10,6 +10,7 @@ import org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore;
 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
 import org.asamk.signal.manager.storage.groups.GroupStore;
 import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
 import org.asamk.signal.manager.storage.groups.GroupStore;
 import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
+import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
 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.messageCache.MessageCache;
 import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
 import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
@@ -106,12 +107,14 @@ public class SignalAccount implements Closeable {
         this.lock = lock;
     }
 
         this.lock = lock;
     }
 
-    public static SignalAccount load(File dataPath, String username, boolean waitForLock) throws IOException {
+    public static SignalAccount load(
+            File dataPath, String username, boolean waitForLock, final TrustNewIdentity trustNewIdentity
+    ) throws IOException {
         final var fileName = getFileName(dataPath, username);
         final var pair = openFileChannel(fileName, waitForLock);
         try {
             var account = new SignalAccount(pair.first(), pair.second());
         final var fileName = getFileName(dataPath, username);
         final var pair = openFileChannel(fileName, waitForLock);
         try {
             var account = new SignalAccount(pair.first(), pair.second());
-            account.load(dataPath);
+            account.load(dataPath, trustNewIdentity);
             account.migrateLegacyConfigs();
 
             if (!username.equals(account.getUsername())) {
             account.migrateLegacyConfigs();
 
             if (!username.equals(account.getUsername())) {
@@ -128,7 +131,12 @@ public class SignalAccount implements Closeable {
     }
 
     public static SignalAccount create(
     }
 
     public static SignalAccount create(
-            File dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey
+            File dataPath,
+            String username,
+            IdentityKeyPair identityKey,
+            int registrationId,
+            ProfileKey profileKey,
+            final TrustNewIdentity trustNewIdentity
     ) throws IOException {
         IOUtils.createPrivateDirectories(dataPath);
         var fileName = getFileName(dataPath, username);
     ) throws IOException {
         IOUtils.createPrivateDirectories(dataPath);
         var fileName = getFileName(dataPath, username);
@@ -142,7 +150,7 @@ public class SignalAccount implements Closeable {
         account.username = username;
         account.profileKey = profileKey;
 
         account.username = username;
         account.profileKey = profileKey;
 
-        account.initStores(dataPath, identityKey, registrationId);
+        account.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
         account.groupStore = new GroupStore(getGroupCachePath(dataPath, username),
                 account.recipientStore::resolveRecipient,
                 account::saveGroupStore);
         account.groupStore = new GroupStore(getGroupCachePath(dataPath, username),
                 account.recipientStore::resolveRecipient,
                 account::saveGroupStore);
@@ -157,7 +165,10 @@ public class SignalAccount implements Closeable {
     }
 
     private void initStores(
     }
 
     private void initStores(
-            final File dataPath, final IdentityKeyPair identityKey, final int registrationId
+            final File dataPath,
+            final IdentityKeyPair identityKey,
+            final int registrationId,
+            final TrustNewIdentity trustNewIdentity
     ) throws IOException {
         recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username), this::mergeRecipients);
 
     ) throws IOException {
         recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username), this::mergeRecipients);
 
@@ -167,7 +178,8 @@ public class SignalAccount implements Closeable {
         identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
                 recipientStore::resolveRecipient,
                 identityKey,
         identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
                 recipientStore::resolveRecipient,
                 identityKey,
-                registrationId);
+                registrationId,
+                trustNewIdentity);
         signalProtocolStore = new SignalProtocolStore(preKeyStore,
                 signedPreKeyStore,
                 sessionStore,
         signalProtocolStore = new SignalProtocolStore(preKeyStore,
                 signedPreKeyStore,
                 sessionStore,
@@ -186,7 +198,8 @@ public class SignalAccount implements Closeable {
             int deviceId,
             IdentityKeyPair identityKey,
             int registrationId,
             int deviceId,
             IdentityKeyPair identityKey,
             int registrationId,
-            ProfileKey profileKey
+            ProfileKey profileKey,
+            final TrustNewIdentity trustNewIdentity
     ) throws IOException {
         IOUtils.createPrivateDirectories(dataPath);
         var fileName = getFileName(dataPath, username);
     ) throws IOException {
         IOUtils.createPrivateDirectories(dataPath);
         var fileName = getFileName(dataPath, username);
@@ -199,10 +212,11 @@ public class SignalAccount implements Closeable {
                     deviceId,
                     identityKey,
                     registrationId,
                     deviceId,
                     identityKey,
                     registrationId,
-                    profileKey);
+                    profileKey,
+                    trustNewIdentity);
         }
 
         }
 
-        final var account = load(dataPath, username, true);
+        final var account = load(dataPath, username, true, trustNewIdentity);
         account.setProvisioningData(username, uuid, password, encryptedDeviceName, deviceId, profileKey);
         account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
         account.sessionStore.archiveAllSessions();
         account.setProvisioningData(username, uuid, password, encryptedDeviceName, deviceId, profileKey);
         account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
         account.sessionStore.archiveAllSessions();
@@ -227,7 +241,8 @@ public class SignalAccount implements Closeable {
             int deviceId,
             IdentityKeyPair identityKey,
             int registrationId,
             int deviceId,
             IdentityKeyPair identityKey,
             int registrationId,
-            ProfileKey profileKey
+            ProfileKey profileKey,
+            final TrustNewIdentity trustNewIdentity
     ) throws IOException {
         var fileName = getFileName(dataPath, username);
         IOUtils.createPrivateFile(fileName);
     ) throws IOException {
         var fileName = getFileName(dataPath, username);
         IOUtils.createPrivateFile(fileName);
@@ -237,7 +252,7 @@ public class SignalAccount implements Closeable {
 
         account.setProvisioningData(username, uuid, password, encryptedDeviceName, deviceId, profileKey);
 
 
         account.setProvisioningData(username, uuid, password, encryptedDeviceName, deviceId, profileKey);
 
-        account.initStores(dataPath, identityKey, registrationId);
+        account.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
         account.groupStore = new GroupStore(getGroupCachePath(dataPath, username),
                 account.recipientStore::resolveRecipient,
                 account::saveGroupStore);
         account.groupStore = new GroupStore(getGroupCachePath(dataPath, username),
                 account.recipientStore::resolveRecipient,
                 account::saveGroupStore);
@@ -339,7 +354,9 @@ public class SignalAccount implements Closeable {
         return !(!f.exists() || f.isDirectory());
     }
 
         return !(!f.exists() || f.isDirectory());
     }
 
-    private void load(File dataPath) throws IOException {
+    private void load(
+            File dataPath, final TrustNewIdentity trustNewIdentity
+    ) throws IOException {
         JsonNode rootNode;
         synchronized (fileChannel) {
             fileChannel.position(0);
         JsonNode rootNode;
         synchronized (fileChannel) {
             fileChannel.position(0);
@@ -428,7 +445,7 @@ public class SignalAccount implements Closeable {
             migratedLegacyConfig = true;
         }
 
             migratedLegacyConfig = true;
         }
 
-        initStores(dataPath, identityKeyPair, registrationId);
+        initStores(dataPath, identityKeyPair, registrationId, trustNewIdentity);
 
         migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
 
 
         migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
 
index d1cfceda441b31239d94dd751eab518be8fe44d0..0cbdf347e218ae952ef7452c9bf205b640ec4f48 100644 (file)
@@ -42,17 +42,20 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
     private final RecipientResolver resolver;
     private final IdentityKeyPair identityKeyPair;
     private final int localRegistrationId;
     private final RecipientResolver resolver;
     private final IdentityKeyPair identityKeyPair;
     private final int localRegistrationId;
+    private final TrustNewIdentity trustNewIdentity;
 
     public IdentityKeyStore(
             final File identitiesPath,
             final RecipientResolver resolver,
             final IdentityKeyPair identityKeyPair,
 
     public IdentityKeyStore(
             final File identitiesPath,
             final RecipientResolver resolver,
             final IdentityKeyPair identityKeyPair,
-            final int localRegistrationId
+            final int localRegistrationId,
+            final TrustNewIdentity trustNewIdentity
     ) {
         this.identitiesPath = identitiesPath;
         this.resolver = resolver;
         this.identityKeyPair = identityKeyPair;
         this.localRegistrationId = localRegistrationId;
     ) {
         this.identitiesPath = identitiesPath;
         this.resolver = resolver;
         this.identityKeyPair = identityKeyPair;
         this.localRegistrationId = localRegistrationId;
+        this.trustNewIdentity = trustNewIdentity;
     }
 
     @Override
     }
 
     @Override
@@ -80,7 +83,10 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
                 return false;
             }
 
                 return false;
             }
 
-            final var trustLevel = identityInfo == null ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED;
+            final var trustLevel = trustNewIdentity == TrustNewIdentity.ALWAYS || (
+                    trustNewIdentity == TrustNewIdentity.ON_FIRST_USE && identityInfo == null
+            ) ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED;
+            logger.debug("Storing new identity for recipient {} with trust {}", recipientId, trustLevel);
             final var newIdentityInfo = new IdentityInfo(recipientId, identityKey, trustLevel, added);
             storeIdentityLocked(recipientId, newIdentityInfo);
             return true;
             final var newIdentityInfo = new IdentityInfo(recipientId, identityKey, trustLevel, added);
             storeIdentityLocked(recipientId, newIdentityInfo);
             return true;
@@ -108,13 +114,17 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
 
     @Override
     public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
 
     @Override
     public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
+        if (trustNewIdentity == TrustNewIdentity.ALWAYS) {
+            return true;
+        }
+
         var recipientId = resolveRecipient(address.getName());
 
         synchronized (cachedIdentities) {
             final var identityInfo = loadIdentityLocked(recipientId);
             if (identityInfo == null) {
                 // Identity not found
         var recipientId = resolveRecipient(address.getName());
 
         synchronized (cachedIdentities) {
             final var identityInfo = loadIdentityLocked(recipientId);
             if (identityInfo == null) {
                 // Identity not found
-                return true;
+                return trustNewIdentity == TrustNewIdentity.ON_FIRST_USE;
             }
 
             // TODO implement possibility for different handling of incoming/outgoing trust decisions
             }
 
             // TODO implement possibility for different handling of incoming/outgoing trust decisions
diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/TrustNewIdentity.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/TrustNewIdentity.java
new file mode 100644 (file)
index 0000000..b2db73a
--- /dev/null
@@ -0,0 +1,7 @@
+package org.asamk.signal.manager.storage.identities;
+
+public enum TrustNewIdentity {
+    ALWAYS,
+    ON_FIRST_USE,
+    NEVER
+}
index de554c022661333d3189b5acece8180fc0bdbde9..b8251eb1053cf04b26b48ebdfccdc45f80ede28e 100644 (file)
@@ -58,6 +58,13 @@ Make request via system dbus.
 *-o* OUTPUT-MODE, *--output* OUTPUT-MODE::
 Specify if you want commands to output in either "plain-text" mode or in "json". Defaults to "plain-text"
 
 *-o* OUTPUT-MODE, *--output* OUTPUT-MODE::
 Specify if you want commands to output in either "plain-text" mode or in "json". Defaults to "plain-text"
 
+*--trust-new-identities* TRUST-MODE::
+Choose when to trust new identities:
+- `on-first-use` (default): Trust the first seen identity key from new users,
+  changed keys must be verified manually
+- `always`: Trust any new identity key without verification
+- `never`: Don't trust any unknown identity key, every key must be verified manually
+
 == Commands
 
 === register
 == Commands
 
 === register
index 6b31e2bccfc5bedda76697faef402c081c5079b5..1ff1a9094447739848e75f374b769bcff83b871e 100644 (file)
@@ -24,6 +24,7 @@ import org.asamk.signal.manager.ProvisioningManager;
 import org.asamk.signal.manager.RegistrationManager;
 import org.asamk.signal.manager.config.ServiceConfig;
 import org.asamk.signal.manager.config.ServiceEnvironment;
 import org.asamk.signal.manager.RegistrationManager;
 import org.asamk.signal.manager.config.ServiceConfig;
 import org.asamk.signal.manager.config.ServiceEnvironment;
+import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
 import org.asamk.signal.util.IOUtils;
 import org.freedesktop.dbus.connections.impl.DBusConnection;
 import org.freedesktop.dbus.exceptions.DBusException;
 import org.asamk.signal.util.IOUtils;
 import org.freedesktop.dbus.connections.impl.DBusConnection;
 import org.freedesktop.dbus.exceptions.DBusException;
@@ -74,6 +75,11 @@ public class App {
                 .type(Arguments.enumStringType(ServiceEnvironmentCli.class))
                 .setDefault(ServiceEnvironmentCli.LIVE);
 
                 .type(Arguments.enumStringType(ServiceEnvironmentCli.class))
                 .setDefault(ServiceEnvironmentCli.LIVE);
 
+        parser.addArgument("--trust-new-identities")
+                .help("Choose when to trust new identities.")
+                .type(Arguments.enumStringType(TrustNewIdentityCli.class))
+                .setDefault(TrustNewIdentityCli.ON_FIRST_USE);
+
         var subparsers = parser.addSubparsers().title("subcommands").dest("command");
 
         Commands.getCommandSubparserAttachers().forEach((key, value) -> {
         var subparsers = parser.addSubparsers().title("subcommands").dest("command");
 
         Commands.getCommandSubparserAttachers().forEach((key, value) -> {
@@ -125,11 +131,6 @@ public class App {
             dataPath = getDefaultDataPath();
         }
 
             dataPath = getDefaultDataPath();
         }
 
-        final var serviceEnvironmentCli = ns.<ServiceEnvironmentCli>get("service-environment");
-        final var serviceEnvironment = serviceEnvironmentCli == ServiceEnvironmentCli.LIVE
-                ? ServiceEnvironment.LIVE
-                : ServiceEnvironment.SANDBOX;
-
         if (!ServiceConfig.getCapabilities().isGv2()) {
             logger.warn("WARNING: Support for new group V2 is disabled,"
                     + " because the required native library dependency is missing: libzkgroup");
         if (!ServiceConfig.getCapabilities().isGv2()) {
             logger.warn("WARNING: Support for new group V2 is disabled,"
                     + " because the required native library dependency is missing: libzkgroup");
@@ -139,6 +140,16 @@ public class App {
             throw new UserErrorException("Missing required native library dependency: libsignal-client");
         }
 
             throw new UserErrorException("Missing required native library dependency: libsignal-client");
         }
 
+        final var serviceEnvironmentCli = ns.<ServiceEnvironmentCli>get("service-environment");
+        final var serviceEnvironment = serviceEnvironmentCli == ServiceEnvironmentCli.LIVE
+                ? ServiceEnvironment.LIVE
+                : ServiceEnvironment.SANDBOX;
+
+        final var trustNewIdentityCli = ns.<TrustNewIdentityCli>get("trust-new-identities");
+        final var trustNewIdentity = trustNewIdentityCli == TrustNewIdentityCli.ON_FIRST_USE
+                ? TrustNewIdentity.ON_FIRST_USE
+                : trustNewIdentityCli == TrustNewIdentityCli.ALWAYS ? TrustNewIdentity.ALWAYS : TrustNewIdentity.NEVER;
+
         if (command instanceof ProvisioningCommand) {
             if (username != null) {
                 throw new UserErrorException("You cannot specify a username (phone number) when linking");
         if (command instanceof ProvisioningCommand) {
             if (username != null) {
                 throw new UserErrorException("You cannot specify a username (phone number) when linking");
@@ -156,7 +167,8 @@ public class App {
                         dataPath,
                         serviceEnvironment,
                         usernames,
                         dataPath,
                         serviceEnvironment,
                         usernames,
-                        outputWriter);
+                        outputWriter,
+                        trustNewIdentity);
                 return;
             }
 
                 return;
             }
 
@@ -181,7 +193,12 @@ public class App {
             throw new UserErrorException("Command only works via dbus");
         }
 
             throw new UserErrorException("Command only works via dbus");
         }
 
-        handleLocalCommand((LocalCommand) command, username, dataPath, serviceEnvironment, outputWriter);
+        handleLocalCommand((LocalCommand) command,
+                username,
+                dataPath,
+                serviceEnvironment,
+                outputWriter,
+                trustNewIdentity);
     }
 
     private void handleProvisioningCommand(
     }
 
     private void handleProvisioningCommand(
@@ -222,9 +239,10 @@ public class App {
             final String username,
             final File dataPath,
             final ServiceEnvironment serviceEnvironment,
             final String username,
             final File dataPath,
             final ServiceEnvironment serviceEnvironment,
-            final OutputWriter outputWriter
+            final OutputWriter outputWriter,
+            final TrustNewIdentity trustNewIdentity
     ) throws CommandException {
     ) throws CommandException {
-        try (var m = loadManager(username, dataPath, serviceEnvironment)) {
+        try (var m = loadManager(username, dataPath, serviceEnvironment, trustNewIdentity)) {
             command.handleCommand(ns, m, outputWriter);
         } catch (IOException e) {
             logger.warn("Cleanup failed", e);
             command.handleCommand(ns, m, outputWriter);
         } catch (IOException e) {
             logger.warn("Cleanup failed", e);
@@ -236,12 +254,13 @@ public class App {
             final File dataPath,
             final ServiceEnvironment serviceEnvironment,
             final List<String> usernames,
             final File dataPath,
             final ServiceEnvironment serviceEnvironment,
             final List<String> usernames,
-            final OutputWriter outputWriter
+            final OutputWriter outputWriter,
+            final TrustNewIdentity trustNewIdentity
     ) throws CommandException {
         final var managers = new ArrayList<Manager>();
         for (String u : usernames) {
             try {
     ) throws CommandException {
         final var managers = new ArrayList<Manager>();
         for (String u : usernames) {
             try {
-                managers.add(loadManager(u, dataPath, serviceEnvironment));
+                managers.add(loadManager(u, dataPath, serviceEnvironment, trustNewIdentity));
             } catch (CommandException e) {
                 logger.warn("Ignoring {}: {}", u, e.getMessage());
             }
             } catch (CommandException e) {
                 logger.warn("Ignoring {}: {}", u, e.getMessage());
             }
@@ -269,11 +288,14 @@ public class App {
     }
 
     private Manager loadManager(
     }
 
     private Manager loadManager(
-            final String username, final File dataPath, final ServiceEnvironment serviceEnvironment
+            final String username,
+            final File dataPath,
+            final ServiceEnvironment serviceEnvironment,
+            final TrustNewIdentity trustNewIdentity
     ) throws CommandException {
         Manager manager;
         try {
     ) throws CommandException {
         Manager manager;
         try {
-            manager = Manager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
+            manager = Manager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT, trustNewIdentity);
         } catch (NotRegisteredException e) {
             throw new UserErrorException("User " + username + " is not registered.");
         } catch (Throwable e) {
         } catch (NotRegisteredException e) {
             throw new UserErrorException("User " + username + " is not registered.");
         } catch (Throwable e) {
diff --git a/src/main/java/org/asamk/signal/TrustNewIdentityCli.java b/src/main/java/org/asamk/signal/TrustNewIdentityCli.java
new file mode 100644 (file)
index 0000000..5cc36bb
--- /dev/null
@@ -0,0 +1,22 @@
+package org.asamk.signal;
+
+public enum TrustNewIdentityCli {
+    ALWAYS {
+        @Override
+        public String toString() {
+            return "always";
+        }
+    },
+    ON_FIRST_USE {
+        @Override
+        public String toString() {
+            return "on-first-use";
+        }
+    },
+    NEVER {
+        @Override
+        public String toString() {
+            return "never";
+        }
+    },
+}