import org.freedesktop.dbus.DBusSigHandler;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.*;
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeoutException;
public class Main {
System.exit(3);
}
break;
+ case "link":
+ if (dBusConn != null) {
+ System.err.println("link is not yet implemented via dbus");
+ System.exit(1);
+ }
+
+ // When linking, username is null and we always have to create keys
+ m.createNewIdentity();
+
+ String deviceName = ns.getString("name");
+ if (deviceName == null) {
+ deviceName = "cli";
+ }
+ try {
+ System.out.println(m.getDeviceLinkUri());
+ m.finishDeviceLink(deviceName);
+ System.out.println("Associated with: " + m.getUsername());
+ } catch (TimeoutException e) {
+ System.err.println("Link request timed out, please try again.");
+ System.exit(3);
+ } catch (IOException e) {
+ System.err.println("Link request error: " + e.getMessage());
+ System.exit(3);
+ } catch (InvalidKeyException e) {
+ e.printStackTrace();
+ System.exit(3);
+ } catch (UserAlreadyExists e) {
+ System.err.println("The user " + e.getUsername() + " already exists\nDelete \"" + e.getFileName() + "\" before trying again.");
+ System.exit(3);
+ }
+ break;
case "send":
if (dBusConn == null && !m.isRegistered()) {
System.err.println("User is not registered.");
.description("valid subcommands")
.help("additional help");
+ Subparser parserLink = subparsers.addParser("link");
+ parserLink.addArgument("-n", "--name")
+ .help("Specify a name to describe this new device.");
+
Subparser parserRegister = subparsers.addParser("register");
parserRegister.addArgument("-v", "--voice")
.help("The verification should be done over voice, not sms.")
try {
Namespace ns = parser.parseArgs(args);
- if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
+ if ("link".equals(ns.getString("command"))) {
+ if (ns.getString("username") != null) {
+ parser.printUsage();
+ System.err.println("You cannot specify a username (phone number) when linking");
+ System.exit(2);
+ }
+ } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
if (ns.getString("username") == null) {
parser.printUsage();
System.err.println("You need to specify a username (phone number)");
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
private final ObjectMapper jsonProcessot = new ObjectMapper();
private String username;
+ int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
private String password;
private String signalingKey;
private int preKeyIdOffset;
jsonProcessot.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
+ public String getUsername() {
+ return username;
+ }
+
public String getFileName() {
new File(dataPath).mkdirs();
return dataPath + "/" + username;
}
public boolean userExists() {
+ if (username == null) {
+ return false;
+ }
File f = new File(getFileName());
return !(!f.exists() || f.isDirectory());
}
public void load() throws IOException, InvalidKeyException {
JsonNode rootNode = jsonProcessot.readTree(new File(getFileName()));
+ JsonNode node = rootNode.get("deviceId");
+ if (node != null) {
+ deviceId = node.asInt();
+ }
username = getNotNullNode(rootNode, "username").asText();
password = getNotNullNode(rootNode, "password").asText();
if (rootNode.has("signalingKey")) {
if (groupStore == null) {
groupStore = new JsonGroupStore();
}
- accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, USER_AGENT);
+ accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, deviceId, USER_AGENT);
try {
if (registered && accountManager.getPreKeysCount() < PREKEY_MINIMUM_COUNT) {
refreshPreKeys();
private void save() {
ObjectNode rootNode = jsonProcessot.createObjectNode();
rootNode.put("username", username)
+ .put("deviceId", deviceId)
.put("password", password)
.put("signalingKey", signalingKey)
.put("preKeyIdOffset", preKeyIdOffset)
save();
}
+ public URI getDeviceLinkUri() throws TimeoutException, IOException {
+ password = Util.getSecret(18);
+
+ accountManager = new SignalServiceAccountManager(URL, TRUST_STORE, username, password, USER_AGENT);
+ String uuid = accountManager.getNewDeviceUuid();
+
+ registered = false;
+ try {
+ return new URI("tsdevice:/?uuid=" + URLEncoder.encode(uuid, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(signalProtocolStore.getIdentityKeyPair().getPublicKey().serialize()), "utf-8"));
+ } catch (URISyntaxException e) {
+ // Shouldn't happen
+ return null;
+ }
+ }
+
+ public void finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
+ signalingKey = Util.getSecret(52);
+ SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration(signalProtocolStore.getIdentityKeyPair(), signalingKey, false, true, signalProtocolStore.getLocalRegistrationId(), deviceName);
+ deviceId = ret.getDeviceId();
+ username = ret.getNumber();
+ // TODO do this check before actually registering
+ if (userExists()) {
+ throw new UserAlreadyExists(username, getFileName());
+ }
+ signalProtocolStore = new JsonSignalProtocolStore(ret.getIdentity(), signalProtocolStore.getLocalRegistrationId());
+
+ registered = true;
+ refreshPreKeys();
+ }
+
private List<PreKeyRecord> generatePreKeys() {
List<PreKeyRecord> records = new LinkedList<>();
private void sendMessage(SignalServiceDataMessage message, Collection<String> recipients)
throws IOException, EncapsulatedExceptions, UntrustedIdentityException {
SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password,
- signalProtocolStore, USER_AGENT, Optional.<SignalServiceMessageSender.EventListener>absent());
+ deviceId, signalProtocolStore, USER_AGENT, Optional.<SignalServiceMessageSender.EventListener>absent());
Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
for (String recipient : recipients) {
}
public void receiveMessages(int timeoutSeconds, boolean returnOnTimeout, ReceiveMessageHandler handler) throws IOException {
- final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, signalingKey, USER_AGENT);
+ final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT);
SignalServiceMessagePipe messagePipe = null;
try {
}
private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException {
- final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, signalingKey, USER_AGENT);
+ final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT);
File tmpFile = File.createTempFile("ts_attach_" + pointer.getId(), ".tmp");
InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile);