Extract utils methods

This commit is contained in:
AsamK 2018-11-18 19:51:21 +01:00
parent 35c72f692f
commit 184354ffb7
4 changed files with 272 additions and 224 deletions

View file

@ -70,8 +70,8 @@ import java.util.concurrent.TimeoutException;
public class Main { public class Main {
public static final String SIGNAL_BUSNAME = "org.asamk.Signal"; private static final String SIGNAL_BUSNAME = "org.asamk.Signal";
public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal"; private static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal";
public static void main(String[] args) { public static void main(String[] args) {
// Workaround for BKS truststore // Workaround for BKS truststore
@ -286,15 +286,12 @@ public class Main {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
return 3; return 3;
} catch (InvalidKeyException e) { } catch (InvalidKeyException | URISyntaxException e) {
e.printStackTrace(); e.printStackTrace();
return 2; return 2;
} catch (AssertionError e) { } catch (AssertionError e) {
handleAssertionError(e); handleAssertionError(e);
return 1; return 1;
} catch (URISyntaxException e) {
e.printStackTrace();
return 2;
} }
break; break;
case "listDevices": case "listDevices":
@ -528,9 +525,9 @@ public class Main {
if (groupName == null) { if (groupName == null) {
groupName = ""; groupName = "";
} }
List<String> groupMembers = ns.<String>getList("member"); List<String> groupMembers = ns.getList("member");
if (groupMembers == null) { if (groupMembers == null) {
groupMembers = new ArrayList<String>(); groupMembers = new ArrayList<>();
} }
String groupAvatar = ns.getString("avatar"); String groupAvatar = ns.getString("avatar");
if (groupAvatar == null) { if (groupAvatar == null) {
@ -943,7 +940,7 @@ public class Main {
final Manager m; final Manager m;
public ReceiveMessageHandler(Manager m) { ReceiveMessageHandler(Manager m) {
this.m = m; this.m = m;
} }
@ -1207,7 +1204,7 @@ public class Main {
final DBusConnection conn; final DBusConnection conn;
public DbusReceiveMessageHandler(Manager m, DBusConnection conn) { DbusReceiveMessageHandler(Manager m, DBusConnection conn) {
super(m); super(m);
this.conn = conn; this.conn = conn;
} }
@ -1225,7 +1222,7 @@ public class Main {
final Manager m; final Manager m;
final ObjectMapper jsonProcessor; final ObjectMapper jsonProcessor;
public JsonReceiveMessageHandler(Manager m) { JsonReceiveMessageHandler(Manager m) {
this.m = m; this.m = m;
this.jsonProcessor = new ObjectMapper(); this.jsonProcessor = new ObjectMapper();
jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // disable autodetect jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // disable autodetect
@ -1256,7 +1253,7 @@ public class Main {
final DBusConnection conn; final DBusConnection conn;
public JsonDbusReceiveMessageHandler(Manager m, DBusConnection conn) { JsonDbusReceiveMessageHandler(Manager m, DBusConnection conn) {
super(m); super(m);
this.conn = conn; this.conn = conn;
} }

View file

@ -16,7 +16,6 @@
*/ */
package org.asamk.signal.manager; package org.asamk.signal.manager;
import org.apache.http.util.TextUtils;
import org.asamk.Signal; import org.asamk.Signal;
import org.asamk.signal.*; import org.asamk.signal.*;
import org.asamk.signal.storage.SignalAccount; import org.asamk.signal.storage.SignalAccount;
@ -28,13 +27,10 @@ import org.asamk.signal.storage.threads.ThreadInfo;
import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.Util; import org.asamk.signal.util.Util;
import org.signal.libsignal.metadata.*; import org.signal.libsignal.metadata.*;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.whispersystems.libsignal.*; import org.whispersystems.libsignal.*;
import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECKeyPair; import org.whispersystems.libsignal.ecc.ECKeyPair;
import org.whispersystems.libsignal.ecc.ECPublicKey; import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.fingerprint.Fingerprint;
import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.util.KeyHelper; import org.whispersystems.libsignal.util.KeyHelper;
@ -57,7 +53,6 @@ import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptio
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
@ -65,8 +60,6 @@ import org.whispersystems.signalservice.internal.util.Base64;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
@ -99,43 +92,6 @@ public class Manager implements Signal {
} }
private static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
List<SignalServiceAttachment> SignalServiceAttachments = null;
if (attachments != null) {
SignalServiceAttachments = new ArrayList<>(attachments.size());
for (String attachment : attachments) {
try {
SignalServiceAttachments.add(createAttachment(new File(attachment)));
} catch (IOException e) {
throw new AttachmentInvalidException(attachment, e);
}
}
}
return SignalServiceAttachments;
}
private static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
InputStream attachmentStream = new FileInputStream(attachmentFile);
final long attachmentSize = attachmentFile.length();
String mime = Files.probeContentType(attachmentFile.toPath());
if (mime == null) {
mime = "application/octet-stream";
}
// TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
Optional<byte[]> preview = Optional.absent();
Optional<String> caption = Optional.absent();
return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, null);
}
private static CertificateValidator getCertificateValidator() {
try {
ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
return new CertificateValidator(unidentifiedSenderTrustRoot);
} catch (InvalidKeyException | IOException e) {
throw new AssertionError(e);
}
}
public String getUsername() { public String getUsername() {
return username; return username;
} }
@ -163,7 +119,7 @@ public class Manager implements Signal {
} }
public boolean userHasKeys() { public boolean userHasKeys() {
return account.getSignalProtocolStore() != null; return account != null && account.getSignalProtocolStore() != null;
} }
public void init() throws IOException { public void init() throws IOException {
@ -253,7 +209,7 @@ public class Manager implements Signal {
accountManager.setGcmId(Optional.<String>absent()); accountManager.setGcmId(Optional.<String>absent());
} }
public URI getDeviceLinkUri() throws TimeoutException, IOException { public String getDeviceLinkUri() throws TimeoutException, IOException {
if (account == null) { if (account == null) {
createNewIdentity(); createNewIdentity();
} }
@ -261,12 +217,7 @@ public class Manager implements Signal {
accountManager = new SignalServiceAccountManager(BaseConfig.serviceConfiguration, username, account.getPassword(), BaseConfig.USER_AGENT, timer); accountManager = new SignalServiceAccountManager(BaseConfig.serviceConfiguration, username, account.getPassword(), BaseConfig.USER_AGENT, timer);
String uuid = accountManager.getNewDeviceUuid(); String uuid = accountManager.getNewDeviceUuid();
try { return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(uuid, getIdentity().getPublicKey()));
return new URI("tsdevice:/?uuid=" + URLEncoder.encode(uuid, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(getIdentity().serialize()), "utf-8"));
} catch (URISyntaxException e) {
// Shouldn't happen
return null;
}
} }
public void finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists { public void finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
@ -290,6 +241,8 @@ public class Manager implements Signal {
requestSyncGroups(); requestSyncGroups();
requestSyncContacts(); requestSyncContacts();
requestSyncBlocked();
requestSyncConfiguration();
account.save(); account.save();
} }
@ -305,17 +258,9 @@ public class Manager implements Signal {
} }
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException { public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
Map<String, String> query = Util.getQueryMap(linkUri.getRawQuery()); Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri);
String deviceIdentifier = query.get("uuid");
String publicKeyEncoded = query.get("pub_key");
if (TextUtils.isEmpty(deviceIdentifier) || TextUtils.isEmpty(publicKeyEncoded)) { addDevice(info.deviceIdentifier, info.deviceKey);
throw new RuntimeException("Invalid device link uri");
}
ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
addDevice(deviceIdentifier, deviceKey);
} }
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException { private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
@ -362,6 +307,7 @@ public class Manager implements Signal {
public void verifyAccount(String verificationCode, String pin) throws IOException { public void verifyAccount(String verificationCode, String pin) throws IOException {
verificationCode = verificationCode.replace("-", ""); verificationCode = verificationCode.replace("-", "");
account.setSignalingKey(KeyUtils.createSignalingKey()); account.setSignalingKey(KeyUtils.createSignalingKey());
// TODO make unrestricted unidentified access configurable
accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, getSelfUnidentifiedAccessKey(), false); accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, getSelfUnidentifiedAccessKey(), false);
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
@ -379,6 +325,7 @@ public class Manager implements Signal {
} else { } else {
account.setRegistrationLockPin(null); account.setRegistrationLockPin(null);
} }
account.save();
} }
private void refreshPreKeys() throws IOException { private void refreshPreKeys() throws IOException {
@ -395,7 +342,7 @@ public class Manager implements Signal {
return Optional.absent(); return Optional.absent();
} }
return Optional.of(createAttachment(file)); return Optional.of(Utils.createAttachment(file));
} }
private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException { private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException {
@ -404,7 +351,7 @@ public class Manager implements Signal {
return Optional.absent(); return Optional.absent();
} }
return Optional.of(createAttachment(file)); return Optional.of(Utils.createAttachment(file));
} }
private GroupInfo getGroupForSending(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException { private GroupInfo getGroupForSending(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException {
@ -430,7 +377,7 @@ public class Manager implements Signal {
throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException { throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
if (attachments != null) { if (attachments != null) {
messageBuilder.withAttachments(getSignalServiceAttachments(attachments)); messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
} }
if (groupId != null) { if (groupId != null) {
SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER) SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER)
@ -484,7 +431,7 @@ public class Manager implements Signal {
Set<String> newMembers = new HashSet<>(); Set<String> newMembers = new HashSet<>();
for (String member : members) { for (String member : members) {
try { try {
member = canonicalizeNumber(member); member = Utils.canonicalizeNumber(member, username);
} catch (InvalidNumberException e) { } catch (InvalidNumberException e) {
System.err.println("Failed to add member \"" + member + "\" to group: " + e.getMessage()); System.err.println("Failed to add member \"" + member + "\" to group: " + e.getMessage());
System.err.println("Aborting…"); System.err.println("Aborting…");
@ -552,7 +499,7 @@ public class Manager implements Signal {
File aFile = getGroupAvatarFile(g.groupId); File aFile = getGroupAvatarFile(g.groupId);
if (aFile.exists()) { if (aFile.exists()) {
try { try {
group.withAvatar(createAttachment(aFile)); group.withAvatar(Utils.createAttachment(aFile));
} catch (IOException e) { } catch (IOException e) {
throw new AttachmentInvalidException(aFile.toString(), e); throw new AttachmentInvalidException(aFile.toString(), e);
} }
@ -593,7 +540,7 @@ public class Manager implements Signal {
throws IOException, EncapsulatedExceptions, AttachmentInvalidException { throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
if (attachments != null) { if (attachments != null) {
messageBuilder.withAttachments(getSignalServiceAttachments(attachments)); messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
} }
sendMessageLegacy(messageBuilder, recipients); sendMessageLegacy(messageBuilder, recipients);
} }
@ -796,8 +743,11 @@ public class Manager implements Signal {
private List<SendMessageResult> sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection<String> recipients) private List<SendMessageResult> sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection<String> recipients)
throws IOException { throws IOException {
Set<SignalServiceAddress> recipientsTS = getSignalServiceAddresses(recipients); Set<SignalServiceAddress> recipientsTS = Utils.getSignalServiceAddresses(recipients, username);
if (recipientsTS == null) return Collections.emptyList(); if (recipientsTS == null) {
account.save();
return Collections.emptyList();
}
SignalServiceDataMessage message = null; SignalServiceDataMessage message = null;
try { try {
@ -849,23 +799,8 @@ public class Manager implements Signal {
} }
} }
private Set<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients) {
Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
for (String recipient : recipients) {
try {
recipientsTS.add(getPushAddress(recipient));
} catch (InvalidNumberException e) {
System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
System.err.println("Aborting sending.");
account.save();
return null;
}
}
return recipientsTS;
}
private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, ProtocolUntrustedIdentityException, SelfSendException { private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, ProtocolUntrustedIdentityException, SelfSendException {
SignalServiceCipher cipher = new SignalServiceCipher(new SignalServiceAddress(username), account.getSignalProtocolStore(), getCertificateValidator()); SignalServiceCipher cipher = new SignalServiceCipher(new SignalServiceAddress(username), account.getSignalProtocolStore(), Utils.getCertificateValidator());
try { try {
return cipher.decrypt(envelope); return cipher.decrypt(envelope);
} catch (ProtocolUntrustedIdentityException e) { } catch (ProtocolUntrustedIdentityException e) {
@ -978,6 +913,9 @@ public class Manager implements Signal {
} }
} }
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
if (source.equals(username)) {
this.account.setProfileKey(message.getProfileKey().get());
}
ContactInfo contact = account.getContactStore().getContact(source); ContactInfo contact = account.getContactStore().getContact(source);
if (contact == null) { if (contact == null) {
contact = new ContactInfo(); contact = new ContactInfo();
@ -1003,7 +941,7 @@ public class Manager implements Signal {
} }
SignalServiceEnvelope envelope; SignalServiceEnvelope envelope;
try { try {
envelope = loadEnvelope(fileEntry); envelope = Utils.loadEnvelope(fileEntry);
if (envelope == null) { if (envelope == null) {
continue; continue;
} }
@ -1054,7 +992,7 @@ public class Manager implements Signal {
// store message on disk, before acknowledging receipt to the server // store message on disk, before acknowledging receipt to the server
try { try {
File cacheFile = getMessageCacheFile(envelope.getSource(), now, envelope.getTimestamp()); File cacheFile = getMessageCacheFile(envelope.getSource(), now, envelope.getTimestamp());
storeEnvelope(envelope, cacheFile); Utils.storeEnvelope(envelope, cacheFile);
} catch (IOException e) { } catch (IOException e) {
System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage()); System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage());
} }
@ -1242,73 +1180,6 @@ public class Manager implements Signal {
} }
} }
private SignalServiceEnvelope loadEnvelope(File file) throws IOException {
try (FileInputStream f = new FileInputStream(file)) {
DataInputStream in = new DataInputStream(f);
int version = in.readInt();
if (version > 2) {
return null;
}
int type = in.readInt();
String source = in.readUTF();
int sourceDevice = in.readInt();
if (version == 1) {
// read legacy relay field
in.readUTF();
}
long timestamp = in.readLong();
byte[] content = null;
int contentLen = in.readInt();
if (contentLen > 0) {
content = new byte[contentLen];
in.readFully(content);
}
byte[] legacyMessage = null;
int legacyMessageLen = in.readInt();
if (legacyMessageLen > 0) {
legacyMessage = new byte[legacyMessageLen];
in.readFully(legacyMessage);
}
long serverTimestamp = 0;
String uuid = null;
if (version == 2) {
serverTimestamp = in.readLong();
uuid = in.readUTF();
if ("".equals(uuid)) {
uuid = null;
}
}
return new SignalServiceEnvelope(type, source, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
}
}
private void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
try (FileOutputStream f = new FileOutputStream(file)) {
try (DataOutputStream out = new DataOutputStream(f)) {
out.writeInt(2); // version
out.writeInt(envelope.getType());
out.writeUTF(envelope.getSource());
out.writeInt(envelope.getSourceDevice());
out.writeLong(envelope.getTimestamp());
if (envelope.hasContent()) {
out.writeInt(envelope.getContent().length);
out.write(envelope.getContent());
} else {
out.writeInt(0);
}
if (envelope.hasLegacyMessage()) {
out.writeInt(envelope.getLegacyMessage().length);
out.write(envelope.getLegacyMessage());
} else {
out.writeInt(0);
}
out.writeLong(envelope.getServerTimestamp());
String uuid = envelope.getUuid();
out.writeUTF(uuid == null ? "" : uuid);
}
}
}
private File getContactAvatarFile(String number) { private File getContactAvatarFile(String number) {
return new File(avatarsPath, "contact-" + number); return new File(avatarsPath, "contact-" + number);
} }
@ -1320,7 +1191,7 @@ public class Manager implements Signal {
return retrieveAttachment(pointer, getContactAvatarFile(number), false); return retrieveAttachment(pointer, getContactAvatarFile(number), false);
} else { } else {
SignalServiceAttachmentStream stream = attachment.asStream(); SignalServiceAttachmentStream stream = attachment.asStream();
return retrieveAttachment(stream, getContactAvatarFile(number)); return Utils.retrieveAttachment(stream, getContactAvatarFile(number));
} }
} }
@ -1335,7 +1206,7 @@ public class Manager implements Signal {
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false); return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
} else { } else {
SignalServiceAttachmentStream stream = attachment.asStream(); SignalServiceAttachmentStream stream = attachment.asStream();
return retrieveAttachment(stream, getGroupAvatarFile(groupId)); return Utils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
} }
} }
@ -1348,23 +1219,6 @@ public class Manager implements Signal {
return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true); return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true);
} }
private File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
InputStream input = stream.getInputStream();
try (OutputStream output = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int read;
while ((read = input.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
return outputFile;
}
private File retrieveAttachment(SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview) throws IOException, InvalidMessageException { private File retrieveAttachment(SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview) throws IOException, InvalidMessageException {
if (storePreview && pointer.getPreview().isPresent()) { if (storePreview && pointer.getPreview().isPresent()) {
File previewFile = new File(outputFile + ".preview"); File previewFile = new File(outputFile + ".preview");
@ -1407,16 +1261,6 @@ public class Manager implements Signal {
return messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE); return messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE);
} }
private String canonicalizeNumber(String number) throws InvalidNumberException {
String localNumber = username;
return PhoneNumberFormatter.formatNumber(number, localNumber);
}
private SignalServiceAddress getPushAddress(String number) throws InvalidNumberException {
String e164number = canonicalizeNumber(number);
return new SignalServiceAddress(e164number);
}
@Override @Override
public boolean isRemote() { public boolean isRemote() {
return false; return false;
@ -1618,8 +1462,7 @@ public class Manager implements Signal {
} }
public String computeSafetyNumber(String theirUsername, IdentityKey theirIdentityKey) { public String computeSafetyNumber(String theirUsername, IdentityKey theirIdentityKey) {
Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(username, getIdentity(), theirUsername, theirIdentityKey); return Utils.computeSafetyNumber(username, getIdentity(), theirUsername, theirIdentityKey);
return fingerprint.getDisplayableFingerprint().getDisplayText();
} }
public interface ReceiveMessageHandler { public interface ReceiveMessageHandler {

View file

@ -0,0 +1,234 @@
package org.asamk.signal.manager;
import org.apache.http.util.TextUtils;
import org.asamk.signal.AttachmentInvalidException;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.fingerprint.Fingerprint;
import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.internal.util.Base64;
import java.io.*;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.util.*;
class Utils {
static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
List<SignalServiceAttachment> SignalServiceAttachments = null;
if (attachments != null) {
SignalServiceAttachments = new ArrayList<>(attachments.size());
for (String attachment : attachments) {
try {
SignalServiceAttachments.add(createAttachment(new File(attachment)));
} catch (IOException e) {
throw new AttachmentInvalidException(attachment, e);
}
}
}
return SignalServiceAttachments;
}
static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
InputStream attachmentStream = new FileInputStream(attachmentFile);
final long attachmentSize = attachmentFile.length();
String mime = Files.probeContentType(attachmentFile.toPath());
if (mime == null) {
mime = "application/octet-stream";
}
// TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
Optional<byte[]> preview = Optional.absent();
Optional<String> caption = Optional.absent();
return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, null);
}
static CertificateValidator getCertificateValidator() {
try {
ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
return new CertificateValidator(unidentifiedSenderTrustRoot);
} catch (InvalidKeyException | IOException e) {
throw new AssertionError(e);
}
}
private 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;
}
static String createDeviceLinkUri(DeviceLinkInfo info) {
try {
return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8");
} catch (UnsupportedEncodingException e) {
// Shouldn't happen
return null;
}
}
static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
Map<String, String> query = getQueryMap(linkUri.getRawQuery());
String deviceIdentifier = query.get("uuid");
String publicKeyEncoded = query.get("pub_key");
if (TextUtils.isEmpty(deviceIdentifier) || TextUtils.isEmpty(publicKeyEncoded)) {
throw new RuntimeException("Invalid device link uri");
}
ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
return new DeviceLinkInfo(deviceIdentifier, deviceKey);
}
static Set<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients, String localNumber) {
Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
for (String recipient : recipients) {
try {
recipientsTS.add(getPushAddress(recipient, localNumber));
} catch (InvalidNumberException e) {
System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
System.err.println("Aborting sending.");
return null;
}
}
return recipientsTS;
}
static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
return PhoneNumberFormatter.formatNumber(number, localNumber);
}
private static SignalServiceAddress getPushAddress(String number, String localNumber) throws InvalidNumberException {
String e164number = canonicalizeNumber(number, localNumber);
return new SignalServiceAddress(e164number);
}
static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
try (FileInputStream f = new FileInputStream(file)) {
DataInputStream in = new DataInputStream(f);
int version = in.readInt();
if (version > 2) {
return null;
}
int type = in.readInt();
String source = in.readUTF();
int sourceDevice = in.readInt();
if (version == 1) {
// read legacy relay field
in.readUTF();
}
long timestamp = in.readLong();
byte[] content = null;
int contentLen = in.readInt();
if (contentLen > 0) {
content = new byte[contentLen];
in.readFully(content);
}
byte[] legacyMessage = null;
int legacyMessageLen = in.readInt();
if (legacyMessageLen > 0) {
legacyMessage = new byte[legacyMessageLen];
in.readFully(legacyMessage);
}
long serverTimestamp = 0;
String uuid = null;
if (version == 2) {
serverTimestamp = in.readLong();
uuid = in.readUTF();
if ("".equals(uuid)) {
uuid = null;
}
}
return new SignalServiceEnvelope(type, source, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
}
}
static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
try (FileOutputStream f = new FileOutputStream(file)) {
try (DataOutputStream out = new DataOutputStream(f)) {
out.writeInt(2); // version
out.writeInt(envelope.getType());
out.writeUTF(envelope.getSource());
out.writeInt(envelope.getSourceDevice());
out.writeLong(envelope.getTimestamp());
if (envelope.hasContent()) {
out.writeInt(envelope.getContent().length);
out.write(envelope.getContent());
} else {
out.writeInt(0);
}
if (envelope.hasLegacyMessage()) {
out.writeInt(envelope.getLegacyMessage().length);
out.write(envelope.getLegacyMessage());
} else {
out.writeInt(0);
}
out.writeLong(envelope.getServerTimestamp());
String uuid = envelope.getUuid();
out.writeUTF(uuid == null ? "" : uuid);
}
}
}
static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
InputStream input = stream.getInputStream();
try (OutputStream output = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int read;
while ((read = input.read(buffer)) != -1) {
output.write(buffer, 0, read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
return outputFile;
}
static String computeSafetyNumber(String ownUsername, IdentityKey ownIdentityKey, String theirUsername, IdentityKey theirIdentityKey) {
Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(ownUsername, ownIdentityKey, theirUsername, theirIdentityKey);
return fingerprint.getDisplayableFingerprint().getDisplayText();
}
static class DeviceLinkInfo {
String deviceIdentifier;
ECPublicKey deviceKey;
DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
this.deviceIdentifier = deviceIdentifier;
this.deviceKey = deviceKey;
}
}
}

View file

@ -3,10 +3,6 @@ package org.asamk.signal.util;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import java.io.InvalidObjectException; import java.io.InvalidObjectException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
public class Util { public class Util {
@ -23,28 +19,6 @@ public class Util {
return f.toString(); 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) { public static String join(CharSequence separator, Iterable<? extends CharSequence> list) {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
for (CharSequence str : list) { for (CharSequence str : list) {