import org.asamk.signal.storage.contacts.ContactInfo;
import org.asamk.signal.storage.groups.GroupInfo;
import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
+import org.asamk.signal.util.DateUtils;
import org.asamk.signal.util.Hex;
+import org.asamk.signal.util.IOUtils;
+import org.asamk.signal.util.Util;
import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.DBusSigHandler;
import org.freedesktop.dbus.exceptions.DBusException;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.Security;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public static final String SIGNAL_BUSNAME = "org.asamk.Signal";
public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal";
- private static final TimeZone tzUTC = TimeZone.getTimeZone("UTC");
-
public static void main(String[] args) {
// Workaround for BKS truststore
Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 1);
for (DeviceInfo d : devices) {
System.out.println("Device " + d.getId() + (d.getId() == m.getDeviceId() ? " (this device)" : "") + ":");
System.out.println(" Name: " + d.getName());
- System.out.println(" Created: " + formatTimestamp(d.getCreated()));
- System.out.println(" Last seen: " + formatTimestamp(d.getLastSeen()));
+ System.out.println(" Created: " + DateUtils.formatTimestamp(d.getCreated()));
+ System.out.println(" Last seen: " + DateUtils.formatTimestamp(d.getLastSeen()));
}
} catch (IOException e) {
e.printStackTrace();
String messageText = ns.getString("message");
if (messageText == null) {
try {
- messageText = readAll(System.in);
+ messageText = IOUtils.readAll(System.in, Charset.defaultCharset());
} catch (IOException e) {
System.err.println("Failed to read message from stdin: " + e.getMessage());
System.err.println("Aborting sending.");
@Override
public void handle(Signal.MessageReceived s) {
System.out.print(String.format("Envelope from: %s\nTimestamp: %s\nBody: %s\n",
- s.getSender(), formatTimestamp(s.getTimestamp()), s.getMessage()));
+ s.getSender(), DateUtils.formatTimestamp(s.getTimestamp()), s.getMessage()));
if (s.getGroupId().length > 0) {
System.out.println("Group info:");
System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId()));
@Override
public void handle(Signal.ReceiptReceived s) {
System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n",
- s.getSender(), formatTimestamp(s.getTimestamp())));
+ s.getSender(), DateUtils.formatTimestamp(s.getTimestamp())));
}
});
} catch (UnsatisfiedLinkError e) {
}
private static void printIdentityFingerprint(Manager m, String theirUsername, JsonIdentityKeyStore.Identity theirId) {
- String digits = formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey()));
+ String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey()));
System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirUsername,
theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toStringCondensed(theirId.getFingerprint()), digits));
}
}
}
- private static String formatSafetyNumber(String digits) {
- final int partCount = 12;
- int partSize = digits.length() / partCount;
- StringBuilder f = new StringBuilder(digits.length() + partCount);
- for (int i = 0; i < partCount; i++) {
- f.append(digits.substring(i * partSize, (i * partSize) + partSize)).append(" ");
- }
- return f.toString();
- }
-
private static void handleGroupNotFoundException(GroupNotFoundException e) {
System.err.println("Failed to send to group: " + e.getMessage());
System.err.println("Aborting sending.");
System.err.println("Failed to send message: " + e.getMessage());
}
- private static String readAll(InputStream in) throws IOException {
- StringWriter output = new StringWriter();
- byte[] buffer = new byte[4096];
- long count = 0;
- int n;
- while (-1 != (n = System.in.read(buffer))) {
- output.write(new String(buffer, 0, n, Charset.defaultCharset()));
- count += n;
- }
- return output.toString();
- }
-
private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
final Manager m;
if (source.getRelay().isPresent()) {
System.out.println("Relayed by: " + source.getRelay().get());
}
- System.out.println("Timestamp: " + formatTimestamp(envelope.getTimestamp()));
+ System.out.println("Timestamp: " + DateUtils.formatTimestamp(envelope.getTimestamp()));
if (envelope.isUnidentifiedSender()) {
System.out.println("Sent by unidentified/sealed sender");
}
System.out.println("Received sync read messages list");
for (ReadMessage rm : syncMessage.getRead().get()) {
ContactInfo fromContact = m.getContact(rm.getSender());
- System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender() + " Message timestamp: " + formatTimestamp(rm.getTimestamp()));
+ System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender() + " Message timestamp: " + DateUtils.formatTimestamp(rm.getTimestamp()));
}
}
if (syncMessage.getRequest().isPresent()) {
} else {
to = "Unknown";
}
- System.out.println("To: " + to + " , Message timestamp: " + formatTimestamp(sentTranscriptMessage.getTimestamp()));
+ System.out.println("To: " + to + " , Message timestamp: " + DateUtils.formatTimestamp(sentTranscriptMessage.getTimestamp()));
if (sentTranscriptMessage.getExpirationStartTimestamp() > 0) {
- System.out.println("Expiration started at: " + formatTimestamp(sentTranscriptMessage.getExpirationStartTimestamp()));
+ System.out.println("Expiration started at: " + DateUtils.formatTimestamp(sentTranscriptMessage.getExpirationStartTimestamp()));
}
SignalServiceDataMessage message = sentTranscriptMessage.getMessage();
handleSignalServiceDataMessage(message);
System.out.println("Received sync message with verified identities:");
final VerifiedMessage verifiedMessage = syncMessage.getVerified().get();
System.out.println(" - " + verifiedMessage.getDestination() + ": " + verifiedMessage.getVerified());
- String safetyNumber = formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey()));
+ String safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey()));
System.out.println(" " + safetyNumber);
}
if (syncMessage.getConfiguration().isPresent()) {
if (content.getReceiptMessage().isPresent()) {
System.out.println("Received a receipt message");
SignalServiceReceiptMessage receiptMessage = content.getReceiptMessage().get();
- System.out.println(" - When: " + formatTimestamp(receiptMessage.getWhen()));
+ System.out.println(" - When: " + DateUtils.formatTimestamp(receiptMessage.getWhen()));
if (receiptMessage.isDeliveryReceipt()) {
System.out.println(" - Is delivery receipt");
}
}
System.out.println(" - Timestamps:");
for (long timestamp : receiptMessage.getTimestamps()) {
- System.out.println(" " + formatTimestamp(timestamp));
+ System.out.println(" " + DateUtils.formatTimestamp(timestamp));
}
}
if (content.getTypingMessage().isPresent()) {
System.out.println("Received a typing message");
SignalServiceTypingMessage typingMessage = content.getTypingMessage().get();
System.out.println(" - Action: " + typingMessage.getAction());
- System.out.println(" - Timestamp: " + formatTimestamp(typingMessage.getTimestamp()));
+ System.out.println(" - Timestamp: " + DateUtils.formatTimestamp(typingMessage.getTimestamp()));
if (typingMessage.getGroupId().isPresent()) {
GroupInfo group = m.getGroup(typingMessage.getGroupId().get());
if (group != null) {
}
private void handleSignalServiceDataMessage(SignalServiceDataMessage message) {
- System.out.println("Message timestamp: " + formatTimestamp(message.getTimestamp()));
+ System.out.println("Message timestamp: " + DateUtils.formatTimestamp(message.getTimestamp()));
if (message.getBody().isPresent()) {
System.out.println("Body: " + message.getBody().get());
}
}
}
-
- private static String formatTimestamp(long timestamp) {
- Date date = new Date(timestamp);
- final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
- df.setTimeZone(tzUTC);
- return timestamp + " (" + df.format(date) + ")";
- }
}
import org.asamk.signal.storage.protocol.JsonSignalProtocolStore;
import org.asamk.signal.storage.threads.JsonThreadStore;
import org.asamk.signal.storage.threads.ThreadInfo;
+import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.KeyUtils;
import org.asamk.signal.util.Util;
import org.signal.libsignal.metadata.*;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
-import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import static java.nio.file.attribute.PosixFilePermission.*;
-
class Manager implements Signal {
private final static String URL = "https://textsecure-service.whispersystems.org";
private final static String CDN_URL = "https://cdn.signal.org";
private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException {
String cachePath = getMessageCachePath(sender);
- createPrivateDirectories(cachePath);
+ IOUtils.createPrivateDirectories(cachePath);
return new File(cachePath + "/" + now + "_" + timestamp);
}
- private static void createPrivateDirectories(String path) throws IOException {
- final Path file = new File(path).toPath();
- try {
- Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
- Files.createDirectories(file, PosixFilePermissions.asFileAttribute(perms));
- } catch (UnsupportedOperationException e) {
- Files.createDirectories(file);
- }
- }
-
- private static void createPrivateFile(String path) throws IOException {
- final Path file = new File(path).toPath();
- try {
- Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE);
- Files.createFile(file, PosixFilePermissions.asFileAttribute(perms));
- } catch (UnsupportedOperationException e) {
- Files.createFile(file);
- }
- }
-
public boolean userExists() {
if (username == null) {
return false;
if (fileChannel != null)
return;
- createPrivateDirectories(dataPath);
+ IOUtils.createPrivateDirectories(dataPath);
if (!new File(getFileName()).exists()) {
- createPrivateFile(getFileName());
+ IOUtils.createPrivateFile(getFileName());
}
fileChannel = new RandomAccessFile(new File(getFileName()), "rw").getChannel();
lock = fileChannel.tryLock();
File attachmentFile = getAttachmentFile(g.getAvatarId());
if (!avatarFile.exists() && attachmentFile.exists()) {
try {
- createPrivateDirectories(avatarsPath);
+ IOUtils.createPrivateDirectories(avatarsPath);
Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
// Ignore
accountManager.removeDevice(deviceId);
}
- public static Map<String, String> getQueryMap(String query) {
- String[] params = query.split("&");
- Map<String, String> map = new HashMap<>();
- for (String param : params) {
- String name = null;
- final String[] paramParts = param.split("=");
- try {
- name = URLDecoder.decode(paramParts[0], "utf-8");
- } catch (UnsupportedEncodingException e) {
- // Impossible
- }
- String value = null;
- try {
- value = URLDecoder.decode(paramParts[1], "utf-8");
- } catch (UnsupportedEncodingException e) {
- // Impossible
- }
- map.put(name, value);
- }
- return map;
- }
-
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
- Map<String, String> query = getQueryMap(linkUri.getRawQuery());
+ Map<String, String> query = Util.getQueryMap(linkUri.getRawQuery());
String deviceIdentifier = query.get("uuid");
String publicKeyEncoded = query.get("pub_key");
sendMessageLegacy(messageBuilder, g.members);
}
- private static String join(CharSequence separator, Iterable<? extends CharSequence> list) {
- StringBuilder buf = new StringBuilder();
- for (CharSequence str : list) {
- if (buf.length() > 0) {
- buf.append(separator);
- }
- buf.append(str);
- }
-
- return buf.toString();
- }
-
public byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<String> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
GroupInfo g;
if (groupId == null) {
for (ContactTokenDetails contact : contacts) {
newMembers.remove(contact.getNumber());
}
- System.err.println("Failed to add members " + join(", ", newMembers) + " to group: Not registered on Signal");
+ System.err.println("Failed to add members " + Util.join(", ", newMembers) + " to group: Not registered on Signal");
System.err.println("Aborting…");
System.exit(1);
}
}
if (avatarFile != null) {
- createPrivateDirectories(avatarsPath);
+ IOUtils.createPrivateDirectories(avatarsPath);
File aFile = getGroupAvatarFile(g.groupId);
Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
return UnidentifiedAccess.deriveAccessKeyFrom(profileKey);
}
- private static byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) {
+ private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) {
// TODO implement
return null;
}
if (syncMessage.getGroups().isPresent()) {
File tmpFile = null;
try {
- tmpFile = Util.createTempFile();
+ tmpFile = IOUtils.createTempFile();
try (InputStream attachmentAsStream = retrieveAttachmentAsStream(syncMessage.getGroups().get().asPointer(), tmpFile)) {
DeviceGroupsInputStream s = new DeviceGroupsInputStream(attachmentAsStream);
DeviceGroup g;
if (syncMessage.getContacts().isPresent()) {
File tmpFile = null;
try {
- tmpFile = Util.createTempFile();
+ tmpFile = IOUtils.createTempFile();
final ContactsMessage contactsMessage = syncMessage.getContacts().get();
try (InputStream attachmentAsStream = retrieveAttachmentAsStream(contactsMessage.getContactsStream().asPointer(), tmpFile)) {
DeviceContactsInputStream s = new DeviceContactsInputStream(attachmentAsStream);
}
private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException {
- createPrivateDirectories(avatarsPath);
+ IOUtils.createPrivateDirectories(avatarsPath);
if (attachment.isPointer()) {
SignalServiceAttachmentPointer pointer = attachment.asPointer();
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
}
private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException {
- createPrivateDirectories(avatarsPath);
+ IOUtils.createPrivateDirectories(avatarsPath);
if (attachment.isPointer()) {
SignalServiceAttachmentPointer pointer = attachment.asPointer();
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
}
private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException {
- createPrivateDirectories(attachmentsPath);
+ IOUtils.createPrivateDirectories(attachmentsPath);
return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true);
}
final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration, username, password, deviceId, signalingKey, USER_AGENT, null, timer);
- File tmpFile = Util.createTempFile();
+ File tmpFile = IOUtils.createTempFile();
try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, MAX_ATTACHMENT_SIZE)) {
try (OutputStream output = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
}
private void sendGroups() throws IOException, UntrustedIdentityException {
- File groupsFile = Util.createTempFile();
+ File groupsFile = IOUtils.createTempFile();
try {
try (OutputStream fos = new FileOutputStream(groupsFile)) {
}
private void sendContacts() throws IOException, UntrustedIdentityException {
- File contactsFile = Util.createTempFile();
+ File contactsFile = IOUtils.createTempFile();
try {
try (OutputStream fos = new FileOutputStream(contactsFile)) {
--- /dev/null
+package org.asamk.signal.util;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateUtils {
+
+ private static final TimeZone tzUTC = TimeZone.getTimeZone("UTC");
+
+ private DateUtils() {
+ }
+
+ public static String formatTimestamp(long timestamp) {
+ Date date = new Date(timestamp);
+ final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
+ df.setTimeZone(tzUTC);
+ return timestamp + " (" + df.format(date) + ")";
+ }
+}
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
+ private Hex() {
+ }
+
public static String toStringCondensed(byte[] bytes) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
--- /dev/null
+package org.asamk.signal.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
+import java.util.Set;
+
+import static java.nio.file.attribute.PosixFilePermission.*;
+
+public class IOUtils {
+
+ private IOUtils() {
+ }
+
+ public static File createTempFile() throws IOException {
+ return File.createTempFile("signal_tmp_", ".tmp");
+ }
+
+ public static String readAll(InputStream in, Charset charset) throws IOException {
+ StringWriter output = new StringWriter();
+ byte[] buffer = new byte[4096];
+ int n;
+ while (-1 != (n = in.read(buffer))) {
+ output.write(new String(buffer, 0, n, charset));
+ }
+ return output.toString();
+ }
+
+ public static void createPrivateDirectories(String path) throws IOException {
+ final Path file = new File(path).toPath();
+ try {
+ Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
+ Files.createDirectories(file, PosixFilePermissions.asFileAttribute(perms));
+ } catch (UnsupportedOperationException e) {
+ Files.createDirectories(file);
+ }
+ }
+
+ public static void createPrivateFile(String path) throws IOException {
+ final Path file = new File(path).toPath();
+ try {
+ Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE);
+ Files.createFile(file, PosixFilePermissions.asFileAttribute(perms));
+ } catch (UnsupportedOperationException e) {
+ Files.createFile(file);
+ }
+ }
+}
package org.asamk.signal.util;
-import java.io.File;
-import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
public class Util {
- public static File createTempFile() throws IOException {
- return File.createTempFile("signal_tmp_", ".tmp");
+
+ private Util() {
+ }
+
+ public static String formatSafetyNumber(String digits) {
+ final int partCount = 12;
+ int partSize = digits.length() / partCount;
+ StringBuilder f = new StringBuilder(digits.length() + partCount);
+ for (int i = 0; i < partCount; i++) {
+ f.append(digits, i * partSize, (i * partSize) + partSize).append(" ");
+ }
+ return f.toString();
+ }
+
+ public static Map<String, String> getQueryMap(String query) {
+ String[] params = query.split("&");
+ Map<String, String> map = new HashMap<>();
+ for (String param : params) {
+ String name = null;
+ final String[] paramParts = param.split("=");
+ try {
+ name = URLDecoder.decode(paramParts[0], "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ // Impossible
+ }
+ String value = null;
+ try {
+ value = URLDecoder.decode(paramParts[1], "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ // Impossible
+ }
+ map.put(name, value);
+ }
+ return map;
+ }
+
+ public static String join(CharSequence separator, Iterable<? extends CharSequence> list) {
+ StringBuilder buf = new StringBuilder();
+ for (CharSequence str : list) {
+ if (buf.length() > 0) {
+ buf.append(separator);
+ }
+ buf.append(str);
+ }
+
+ return buf.toString();
}
}