mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 02:20:39 +00:00
parent
6dd1a21606
commit
6c3106db5d
10 changed files with 132 additions and 35 deletions
|
@ -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.
|
||||
|
||||
### 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)
|
||||
|
|
|
@ -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.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;
|
||||
|
@ -270,7 +271,11 @@ public class Manager implements Closeable {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -278,7 +283,7 @@ public class Manager implements Closeable {
|
|||
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();
|
||||
|
|
|
@ -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.storage.identities.TrustNewIdentity;
|
||||
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,
|
||||
profileKey);
|
||||
profileKey,
|
||||
TrustNewIdentity.ON_FIRST_USE);
|
||||
|
||||
Manager m = null;
|
||||
try {
|
||||
|
@ -161,7 +163,7 @@ public class ProvisioningManager {
|
|||
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;
|
||||
|
|
|
@ -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.storage.identities.TrustNewIdentity;
|
||||
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,
|
||||
profileKey);
|
||||
profileKey,
|
||||
TrustNewIdentity.ON_FIRST_USE);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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.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;
|
||||
|
@ -106,12 +107,14 @@ public class SignalAccount implements Closeable {
|
|||
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());
|
||||
account.load(dataPath);
|
||||
account.load(dataPath, trustNewIdentity);
|
||||
account.migrateLegacyConfigs();
|
||||
|
||||
if (!username.equals(account.getUsername())) {
|
||||
|
@ -128,7 +131,12 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
|
||||
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);
|
||||
|
@ -142,7 +150,7 @@ public class SignalAccount implements Closeable {
|
|||
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);
|
||||
|
@ -157,7 +165,10 @@ public class SignalAccount implements Closeable {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -167,7 +178,8 @@ public class SignalAccount implements Closeable {
|
|||
identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
|
||||
recipientStore::resolveRecipient,
|
||||
identityKey,
|
||||
registrationId);
|
||||
registrationId,
|
||||
trustNewIdentity);
|
||||
signalProtocolStore = new SignalProtocolStore(preKeyStore,
|
||||
signedPreKeyStore,
|
||||
sessionStore,
|
||||
|
@ -186,7 +198,8 @@ public class SignalAccount implements Closeable {
|
|||
int deviceId,
|
||||
IdentityKeyPair identityKey,
|
||||
int registrationId,
|
||||
ProfileKey profileKey
|
||||
ProfileKey profileKey,
|
||||
final TrustNewIdentity trustNewIdentity
|
||||
) throws IOException {
|
||||
IOUtils.createPrivateDirectories(dataPath);
|
||||
var fileName = getFileName(dataPath, username);
|
||||
|
@ -199,10 +212,11 @@ public class SignalAccount implements Closeable {
|
|||
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();
|
||||
|
@ -227,7 +241,8 @@ public class SignalAccount implements Closeable {
|
|||
int deviceId,
|
||||
IdentityKeyPair identityKey,
|
||||
int registrationId,
|
||||
ProfileKey profileKey
|
||||
ProfileKey profileKey,
|
||||
final TrustNewIdentity trustNewIdentity
|
||||
) 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.initStores(dataPath, identityKey, registrationId);
|
||||
account.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
|
||||
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());
|
||||
}
|
||||
|
||||
private void load(File dataPath) throws IOException {
|
||||
private void load(
|
||||
File dataPath, final TrustNewIdentity trustNewIdentity
|
||||
) throws IOException {
|
||||
JsonNode rootNode;
|
||||
synchronized (fileChannel) {
|
||||
fileChannel.position(0);
|
||||
|
@ -428,7 +445,7 @@ public class SignalAccount implements Closeable {
|
|||
migratedLegacyConfig = true;
|
||||
}
|
||||
|
||||
initStores(dataPath, identityKeyPair, registrationId);
|
||||
initStores(dataPath, identityKeyPair, registrationId, trustNewIdentity);
|
||||
|
||||
migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
|
||||
|
||||
|
|
|
@ -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 TrustNewIdentity trustNewIdentity;
|
||||
|
||||
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.trustNewIdentity = trustNewIdentity;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -80,7 +83,10 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
|
|||
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;
|
||||
|
@ -108,13 +114,17 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
|
|||
|
||||
@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
|
||||
return true;
|
||||
return trustNewIdentity == TrustNewIdentity.ON_FIRST_USE;
|
||||
}
|
||||
|
||||
// TODO implement possibility for different handling of incoming/outgoing trust decisions
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package org.asamk.signal.manager.storage.identities;
|
||||
|
||||
public enum TrustNewIdentity {
|
||||
ALWAYS,
|
||||
ON_FIRST_USE,
|
||||
NEVER
|
||||
}
|
|
@ -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"
|
||||
|
||||
*--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
|
||||
|
|
|
@ -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.storage.identities.TrustNewIdentity;
|
||||
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);
|
||||
|
||||
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) -> {
|
||||
|
@ -125,11 +131,6 @@ public class App {
|
|||
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");
|
||||
|
@ -139,6 +140,16 @@ public class App {
|
|||
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");
|
||||
|
@ -156,7 +167,8 @@ public class App {
|
|||
dataPath,
|
||||
serviceEnvironment,
|
||||
usernames,
|
||||
outputWriter);
|
||||
outputWriter,
|
||||
trustNewIdentity);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -181,7 +193,12 @@ public class App {
|
|||
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(
|
||||
|
@ -222,9 +239,10 @@ public class App {
|
|||
final String username,
|
||||
final File dataPath,
|
||||
final ServiceEnvironment serviceEnvironment,
|
||||
final OutputWriter outputWriter
|
||||
final OutputWriter outputWriter,
|
||||
final TrustNewIdentity trustNewIdentity
|
||||
) 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);
|
||||
|
@ -236,12 +254,13 @@ public class App {
|
|||
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 {
|
||||
managers.add(loadManager(u, dataPath, serviceEnvironment));
|
||||
managers.add(loadManager(u, dataPath, serviceEnvironment, trustNewIdentity));
|
||||
} catch (CommandException e) {
|
||||
logger.warn("Ignoring {}: {}", u, e.getMessage());
|
||||
}
|
||||
|
@ -269,11 +288,14 @@ public class App {
|
|||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
|
|
22
src/main/java/org/asamk/signal/TrustNewIdentityCli.java
Normal file
22
src/main/java/org/asamk/signal/TrustNewIdentityCli.java
Normal file
|
@ -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";
|
||||
}
|
||||
},
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue