--- /dev/null
+package org.asamk.signal.manager;
+
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.ecc.Curve;
+import org.whispersystems.libsignal.ecc.ECPublicKey;
+import org.whispersystems.util.Base64;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
+
+public class DeviceLinkInfo {
+
+ final String deviceIdentifier;
+ final ECPublicKey deviceKey;
+
+ public 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 (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
+ throw new RuntimeException("Invalid device link uri");
+ }
+
+ ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
+
+ return new DeviceLinkInfo(deviceIdentifier, deviceKey);
+ }
+
+ private static Map<String, String> getQueryMap(String query) {
+ String[] params = query.split("&");
+ Map<String, String> map = new HashMap<>();
+ for (String param : params) {
+ final String[] paramParts = param.split("=");
+ String name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
+ String value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
+ map.put(name, value);
+ }
+ return map;
+ }
+
+ public DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
+ this.deviceIdentifier = deviceIdentifier;
+ this.deviceKey = deviceKey;
+ }
+
+ public String createDeviceLinkUri() {
+ return "tsdevice:/?uuid="
+ + URLEncoder.encode(deviceIdentifier, StandardCharsets.UTF_8)
+ + "&pub_key="
+ + URLEncoder.encode(Base64.encodeBytesWithoutPadding(deviceKey.serialize()), StandardCharsets.UTF_8);
+ }
+}
import org.asamk.signal.manager.storage.profiles.SignalProfileEntry;
import org.asamk.signal.manager.storage.protocol.IdentityInfo;
import org.asamk.signal.manager.storage.stickers.Sticker;
-import org.asamk.signal.util.IOUtils;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.AttachmentUtils;
+import org.asamk.signal.manager.util.IOUtils;
+import org.asamk.signal.manager.util.KeyUtils;
+import org.asamk.signal.manager.util.MessageCacheUtils;
+import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
import org.signal.libsignal.metadata.ProtocolNoSessionException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.libsignal.metadata.SelfSendException;
+import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
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.StreamDetails;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
final static Logger logger = LoggerFactory.getLogger(Manager.class);
private final SleepTimer timer = new UptimeSleepTimer();
+ private final CertificateValidator certificateValidator = new CertificateValidator(ServiceConfig.getUnidentifiedSenderTrustRoot());
private final SignalServiceConfiguration serviceConfiguration;
private final String userAgent;
}
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
- Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri);
+ DeviceLinkInfo info = DeviceLinkInfo.parseDeviceLinkUri(linkUri);
addDevice(info.deviceIdentifier, info.deviceKey);
}
return Optional.absent();
}
- return Optional.of(Utils.createAttachment(file));
+ return Optional.of(AttachmentUtils.createAttachment(file));
}
private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException {
return Optional.absent();
}
- return Optional.of(Utils.createAttachment(file));
+ return Optional.of(AttachmentUtils.createAttachment(file));
}
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withBody(messageText);
if (attachments != null) {
- messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
+ messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
}
return sendGroupMessage(messageBuilder, groupId);
newE164Members.remove(contact.getNumber());
}
throw new IOException("Failed to add members "
- + Util.join(", ", newE164Members)
+ + String.join(", ", newE164Members)
+ " to group: Not registered on Signal");
}
File aFile = getGroupAvatarFile(g.getGroupId());
if (aFile.exists()) {
try {
- group.withAvatar(Utils.createAttachment(aFile));
+ group.withAvatar(AttachmentUtils.createAttachment(aFile));
} catch (IOException e) {
throw new AttachmentInvalidException(aFile.toString(), e);
}
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withBody(messageText);
if (attachments != null) {
- List<SignalServiceAttachment> attachmentStreams = Utils.getSignalServiceAttachments(attachments);
+ List<SignalServiceAttachment> attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments);
// Upload attachments here, so we only upload once even for multiple recipients
SignalServiceMessageSender messageSender = createMessageSender();
private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(),
account.getSignalProtocolStore(),
- Utils.getCertificateValidator());
+ certificateValidator);
try {
return cipher.decrypt(envelope);
} catch (ProtocolUntrustedIdentityException e) {
) {
SignalServiceEnvelope envelope;
try {
- envelope = Utils.loadEnvelope(fileEntry);
+ envelope = MessageCacheUtils.loadEnvelope(fileEntry);
if (envelope == null) {
return;
}
try {
String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
- Utils.storeEnvelope(envelope1, cacheFile);
+ MessageCacheUtils.storeEnvelope(envelope1, cacheFile);
} catch (IOException e) {
logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
}
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
} else {
SignalServiceAttachmentStream stream = attachment.asStream();
- return Utils.retrieveAttachment(stream, getContactAvatarFile(number));
+ return AttachmentUtils.retrieveAttachment(stream, getContactAvatarFile(number));
}
}
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
} else {
SignalServiceAttachmentStream stream = attachment.asStream();
- return Utils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
+ return AttachmentUtils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
}
}
}
public ContactInfo getContact(String number) {
- return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number));
+ return account.getContactStore().getContact(Utils.getSignalServiceAddressFromIdentifier(number));
}
public GroupInfo getGroup(GroupId groupId) {
public String computeSafetyNumber(
SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
) {
- return Utils.computeSafetyNumber(account.getSelfAddress(),
+ return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(),
+ account.getSelfAddress(),
getIdentityKeyPair().getPublicKey(),
theirAddress,
theirIdentityKey);
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
String canonicalizedNumber = UuidUtil.isUuid(identifier)
? identifier
- : Util.canonicalizeNumber(identifier, account.getUsername());
+ : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
return resolveSignalServiceAddress(canonicalizedNumber);
}
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
- SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier);
+ SignalServiceAddress address = Utils.getSignalServiceAddressFromIdentifier(identifier);
return resolveSignalServiceAddress(address);
}
package org.asamk.signal.manager;
import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.util.KeyUtils;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey;
import org.whispersystems.libsignal.IdentityKeyPair;
public String getDeviceLinkUri() throws TimeoutException, IOException {
String deviceUuid = accountManager.getNewDeviceUuid();
- return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(deviceUuid,
- identityKey.getPublicKey().getPublicKey()));
+ return new DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
}
public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
package org.asamk.signal.manager;
import org.signal.zkgroup.ServerPublicParams;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.ecc.Curve;
+import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.push.TrustStore;
}
}
+ static ECPublicKey getUnidentifiedSenderTrustRoot() {
+ try {
+ return Curve.decodePoint(Base64.decode(UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
+ } catch (InvalidKeyException | IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
private static Map<Integer, SignalCdnUrl[]> makeSignalCdnUrlMapFor(
SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls
) {
+++ /dev/null
-package org.asamk.signal.manager;
-
-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.StreamDetails;
-import org.whispersystems.signalservice.api.util.UuidUtil;
-import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
-import org.whispersystems.util.Base64;
-
-import java.io.BufferedInputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URLConnection;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
-
-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 String getFileMimeType(File file, String defaultMimeType) throws IOException {
- String mime = Files.probeContentType(file.toPath());
- if (mime == null) {
- try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
- mime = URLConnection.guessContentTypeFromStream(bufferedStream);
- }
- }
- if (mime == null) {
- return defaultMimeType;
- }
- return mime;
- }
-
- static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
- InputStream attachmentStream = new FileInputStream(attachmentFile);
- final long attachmentSize = attachmentFile.length();
- final String mime = getFileMimeType(attachmentFile, "application/octet-stream");
- // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option
- final long uploadTimestamp = System.currentTimeMillis();
- Optional<byte[]> preview = Optional.absent();
- Optional<String> caption = Optional.absent();
- Optional<String> blurHash = Optional.absent();
- final Optional<ResumableUploadSpec> resumableUploadSpec = Optional.absent();
- return new SignalServiceAttachmentStream(attachmentStream,
- mime,
- attachmentSize,
- Optional.of(attachmentFile.getName()),
- false,
- false,
- preview,
- 0,
- 0,
- uploadTimestamp,
- caption,
- blurHash,
- null,
- null,
- resumableUploadSpec);
- }
-
- static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
- InputStream stream = new FileInputStream(file);
- final long size = file.length();
- String mime = Files.probeContentType(file.toPath());
- if (mime == null) {
- mime = "application/octet-stream";
- }
- return new StreamDetails(stream, mime, size);
- }
-
- static CertificateValidator getCertificateValidator() {
- try {
- ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(ServiceConfig.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) {
- final String[] paramParts = param.split("=");
- String name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
- String value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
- map.put(name, value);
- }
- return map;
- }
-
- static String createDeviceLinkUri(DeviceLinkInfo info) {
- return "tsdevice:/?uuid="
- + URLEncoder.encode(info.deviceIdentifier, StandardCharsets.UTF_8)
- + "&pub_key="
- + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()),
- StandardCharsets.UTF_8);
- }
-
- 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 (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
- throw new RuntimeException("Invalid device link uri");
- }
-
- ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
-
- return new DeviceLinkInfo(deviceIdentifier, deviceKey);
- }
-
- static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
- try (FileInputStream f = new FileInputStream(file)) {
- DataInputStream in = new DataInputStream(f);
- int version = in.readInt();
- if (version > 4) {
- return null;
- }
- int type = in.readInt();
- String source = in.readUTF();
- UUID sourceUuid = null;
- if (version >= 3) {
- sourceUuid = UuidUtil.parseOrNull(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 serverReceivedTimestamp = 0;
- String uuid = null;
- if (version >= 2) {
- serverReceivedTimestamp = in.readLong();
- uuid = in.readUTF();
- if ("".equals(uuid)) {
- uuid = null;
- }
- }
- long serverDeliveredTimestamp = 0;
- if (version >= 4) {
- serverDeliveredTimestamp = in.readLong();
- }
- Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
- ? Optional.absent()
- : Optional.of(new SignalServiceAddress(sourceUuid, source));
- return new SignalServiceEnvelope(type,
- addressOptional,
- sourceDevice,
- timestamp,
- legacyMessage,
- content,
- serverReceivedTimestamp,
- serverDeliveredTimestamp,
- uuid);
- }
- }
-
- static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
- try (FileOutputStream f = new FileOutputStream(file)) {
- try (DataOutputStream out = new DataOutputStream(f)) {
- out.writeInt(4); // version
- out.writeInt(envelope.getType());
- out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
- out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
- 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.getServerReceivedTimestamp());
- String uuid = envelope.getUuid();
- out.writeUTF(uuid == null ? "" : uuid);
- out.writeLong(envelope.getServerDeliveredTimestamp());
- }
- }
- }
-
- 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(
- SignalServiceAddress ownAddress,
- IdentityKey ownIdentityKey,
- SignalServiceAddress theirAddress,
- IdentityKey theirIdentityKey
- ) {
- int version;
- byte[] ownId;
- byte[] theirId;
-
- if (ServiceConfig.capabilities.isUuid() && ownAddress.getUuid().isPresent() && theirAddress.getUuid()
- .isPresent()) {
- // Version 2: UUID user
- version = 2;
- ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
- theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
- } else {
- // Version 1: E164 user
- version = 1;
- if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
- return "INVALID ID";
- }
- ownId = ownAddress.getNumber().get().getBytes();
- theirId = theirAddress.getNumber().get().getBytes();
- }
-
- Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version,
- ownId,
- ownIdentityKey,
- theirId,
- theirIdentityKey);
- return fingerprint.getDisplayableFingerprint().getDisplayText();
- }
-
- static class DeviceLinkInfo {
-
- final String deviceIdentifier;
- final ECPublicKey deviceKey;
-
- DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
- this.deviceIdentifier = deviceIdentifier;
- this.deviceKey = deviceKey;
- }
- }
-}
package org.asamk.signal.manager.groups;
-import static org.asamk.signal.manager.KeyUtils.getSecretBytes;
+import static org.asamk.signal.manager.util.KeyUtils.getSecretBytes;
public class GroupIdV1 extends GroupId {
package org.asamk.signal.manager.groups;
-import org.asamk.signal.manager.KeyUtils;
+import org.asamk.signal.manager.util.KeyUtils;
import java.util.Arrays;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
import org.asamk.signal.manager.storage.profiles.SignalProfile;
-import org.asamk.signal.util.IOUtils;
+import org.asamk.signal.manager.util.IOUtils;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.Member;
import org.asamk.signal.manager.storage.stickers.StickerStore;
import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
import org.asamk.signal.manager.storage.threads.ThreadInfo;
-import org.asamk.signal.util.IOUtils;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.IOUtils;
+import org.asamk.signal.manager.util.Utils;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
deviceId = node.asInt();
}
if (rootNode.has("isMultiDevice")) {
- isMultiDevice = Util.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
+ isMultiDevice = Utils.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
}
- username = Util.getNotNullNode(rootNode, "username").asText();
- password = Util.getNotNullNode(rootNode, "password").asText();
+ username = Utils.getNotNullNode(rootNode, "username").asText();
+ password = Utils.getNotNullNode(rootNode, "password").asText();
JsonNode pinNode = rootNode.get("registrationLockPin");
registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText();
if (rootNode.has("signalingKey")) {
- signalingKey = Util.getNotNullNode(rootNode, "signalingKey").asText();
+ signalingKey = Utils.getNotNullNode(rootNode, "signalingKey").asText();
}
if (rootNode.has("preKeyIdOffset")) {
- preKeyIdOffset = Util.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
+ preKeyIdOffset = Utils.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
} else {
preKeyIdOffset = 0;
}
if (rootNode.has("nextSignedPreKeyId")) {
- nextSignedPreKeyId = Util.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
+ nextSignedPreKeyId = Utils.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
} else {
nextSignedPreKeyId = 0;
}
if (rootNode.has("profileKey")) {
try {
- profileKey = new ProfileKey(Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText()));
+ profileKey = new ProfileKey(Base64.decode(Utils.getNotNullNode(rootNode, "profileKey").asText()));
} catch (InvalidInputException e) {
throw new IOException(
"Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
}
}
- signalProtocolStore = jsonProcessor.convertValue(Util.getNotNullNode(rootNode, "axolotlStore"),
+ signalProtocolStore = jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
JsonSignalProtocolStore.class);
- registered = Util.getNotNullNode(rootNode, "registered").asBoolean();
+ registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
JsonNode groupStoreNode = rootNode.get("groupStore");
if (groupStoreNode != null) {
groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.groups.GroupIdV2;
import org.asamk.signal.manager.groups.GroupUtils;
+import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.util.Hex;
-import org.asamk.signal.util.IOUtils;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.GroupMasterKey;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.asamk.signal.manager.TrustLevel;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.IdentityKey;
if (resolver != null) {
return resolver.resolveSignalServiceAddress(identifier);
} else {
- return Util.getSignalServiceAddressFromIdentifier(identifier);
+ return Utils.getSignalServiceAddressFromIdentifier(identifier);
}
}
UUID uuid = trustedKey.hasNonNull("uuid") ? UuidUtil.parseOrNull(trustedKey.get("uuid")
.asText()) : null;
final SignalServiceAddress serviceAddress = uuid == null
- ? Util.getSignalServiceAddressFromIdentifier(trustedKeyName)
+ ? Utils.getSignalServiceAddressFromIdentifier(trustedKeyName)
: new SignalServiceAddress(uuid, trustedKeyName);
try {
IdentityKey id = new IdentityKey(Base64.decode(trustedKey.get("identityKey").asText()), 0);
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.SignalProtocolAddress;
if (resolver != null) {
return resolver.resolveSignalServiceAddress(identifier);
} else {
- return Util.getSignalServiceAddressFromIdentifier(identifier);
+ return Utils.getSignalServiceAddressFromIdentifier(identifier);
}
}
UUID uuid = session.hasNonNull("uuid") ? UuidUtil.parseOrNull(session.get("uuid").asText()) : null;
final SignalServiceAddress serviceAddress = uuid == null
- ? Util.getSignalServiceAddressFromIdentifier(sessionName)
+ ? Utils.getSignalServiceAddressFromIdentifier(sessionName)
: new SignalServiceAddress(uuid, sessionName);
final int deviceId = session.get("deviceId").asInt();
final String record = session.get("record").asText();
--- /dev/null
+package org.asamk.signal.manager.util;
+
+import org.asamk.signal.manager.AttachmentInvalidException;
+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.internal.push.http.ResumableUploadSpec;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AttachmentUtils {
+
+ public 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;
+ }
+
+ public static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
+ InputStream attachmentStream = new FileInputStream(attachmentFile);
+ final long attachmentSize = attachmentFile.length();
+ final String mime = Utils.getFileMimeType(attachmentFile, "application/octet-stream");
+ // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option
+ final long uploadTimestamp = System.currentTimeMillis();
+ Optional<byte[]> preview = Optional.absent();
+ Optional<String> caption = Optional.absent();
+ Optional<String> blurHash = Optional.absent();
+ final Optional<ResumableUploadSpec> resumableUploadSpec = Optional.absent();
+ return new SignalServiceAttachmentStream(attachmentStream,
+ mime,
+ attachmentSize,
+ Optional.of(attachmentFile.getName()),
+ false,
+ false,
+ preview,
+ 0,
+ 0,
+ uploadTimestamp,
+ caption,
+ blurHash,
+ null,
+ null,
+ resumableUploadSpec);
+ }
+
+ public 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;
+ }
+}
--- /dev/null
+package org.asamk.signal.manager.util;
+
+import org.whispersystems.signalservice.internal.util.Util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+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.OWNER_EXECUTE;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
+
+public class IOUtils {
+
+ public static File createTempFile() throws IOException {
+ return File.createTempFile("signal_tmp_", ".tmp");
+ }
+
+ public static byte[] readFully(InputStream in) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Util.copy(in, baos);
+ return baos.toByteArray();
+ }
+
+ public static void createPrivateDirectories(File file) throws IOException {
+ if (file.exists()) {
+ return;
+ }
+
+ final Path path = file.toPath();
+ try {
+ Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
+ Files.createDirectories(path, PosixFilePermissions.asFileAttribute(perms));
+ } catch (UnsupportedOperationException e) {
+ Files.createDirectories(path);
+ }
+ }
+
+ public static void createPrivateFile(File path) throws IOException {
+ final Path 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 static void copyStreamToFile(InputStream input, File outputFile) throws IOException {
+ copyStreamToFile(input, outputFile, 8192);
+ }
+
+ public static void copyStreamToFile(InputStream input, File outputFile, int bufferSize) throws IOException {
+ try (OutputStream output = new FileOutputStream(outputFile)) {
+ byte[] buffer = new byte[bufferSize];
+ int read;
+
+ while ((read = input.read(buffer)) != -1) {
+ output.write(buffer, 0, read);
+ }
+ }
+ }
+}
-package org.asamk.signal.manager;
+package org.asamk.signal.manager.util;
import org.asamk.signal.util.RandomUtils;
import org.signal.zkgroup.InvalidInputException;
private KeyUtils() {
}
- static String createSignalingKey() {
+ public static String createSignalingKey() {
return getSecret(52);
}
- static ProfileKey createProfileKey() {
+ public static ProfileKey createProfileKey() {
try {
return new ProfileKey(getSecretBytes(32));
} catch (InvalidInputException e) {
}
}
- static String createPassword() {
+ public static String createPassword() {
return getSecret(18);
}
- static byte[] createStickerUploadKey() {
+ public static byte[] createStickerUploadKey() {
return getSecretBytes(32);
}
--- /dev/null
+package org.asamk.signal.manager.util;
+
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.UUID;
+
+public class MessageCacheUtils {
+
+ public static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
+ try (FileInputStream f = new FileInputStream(file)) {
+ DataInputStream in = new DataInputStream(f);
+ int version = in.readInt();
+ if (version > 4) {
+ return null;
+ }
+ int type = in.readInt();
+ String source = in.readUTF();
+ UUID sourceUuid = null;
+ if (version >= 3) {
+ sourceUuid = UuidUtil.parseOrNull(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 serverReceivedTimestamp = 0;
+ String uuid = null;
+ if (version >= 2) {
+ serverReceivedTimestamp = in.readLong();
+ uuid = in.readUTF();
+ if ("".equals(uuid)) {
+ uuid = null;
+ }
+ }
+ long serverDeliveredTimestamp = 0;
+ if (version >= 4) {
+ serverDeliveredTimestamp = in.readLong();
+ }
+ Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
+ ? Optional.absent()
+ : Optional.of(new SignalServiceAddress(sourceUuid, source));
+ return new SignalServiceEnvelope(type,
+ addressOptional,
+ sourceDevice,
+ timestamp,
+ legacyMessage,
+ content,
+ serverReceivedTimestamp,
+ serverDeliveredTimestamp,
+ uuid);
+ }
+ }
+
+ public static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
+ try (FileOutputStream f = new FileOutputStream(file)) {
+ try (DataOutputStream out = new DataOutputStream(f)) {
+ out.writeInt(4); // version
+ out.writeInt(envelope.getType());
+ out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
+ out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
+ 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.getServerReceivedTimestamp());
+ String uuid = envelope.getUuid();
+ out.writeUTF(uuid == null ? "" : uuid);
+ out.writeLong(envelope.getServerDeliveredTimestamp());
+ }
+ }
+ }
+}
--- /dev/null
+package org.asamk.signal.manager.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.fingerprint.Fingerprint;
+import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.StreamDetails;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidObjectException;
+import java.net.URLConnection;
+import java.nio.file.Files;
+
+public class Utils {
+
+ public static String getFileMimeType(File file, String defaultMimeType) throws IOException {
+ String mime = Files.probeContentType(file.toPath());
+ if (mime == null) {
+ try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
+ mime = URLConnection.guessContentTypeFromStream(bufferedStream);
+ }
+ }
+ if (mime == null) {
+ return defaultMimeType;
+ }
+ return mime;
+ }
+
+ public static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
+ InputStream stream = new FileInputStream(file);
+ final long size = file.length();
+ String mime = Files.probeContentType(file.toPath());
+ if (mime == null) {
+ mime = "application/octet-stream";
+ }
+ return new StreamDetails(stream, mime, size);
+ }
+
+ public static String computeSafetyNumber(
+ boolean isUuidCapable,
+ SignalServiceAddress ownAddress,
+ IdentityKey ownIdentityKey,
+ SignalServiceAddress theirAddress,
+ IdentityKey theirIdentityKey
+ ) {
+ int version;
+ byte[] ownId;
+ byte[] theirId;
+
+ if (isUuidCapable && ownAddress.getUuid().isPresent() && theirAddress.getUuid().isPresent()) {
+ // Version 2: UUID user
+ version = 2;
+ ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
+ theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
+ } else {
+ // Version 1: E164 user
+ version = 1;
+ if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
+ return "INVALID ID";
+ }
+ ownId = ownAddress.getNumber().get().getBytes();
+ theirId = theirAddress.getNumber().get().getBytes();
+ }
+
+ Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version,
+ ownId,
+ ownIdentityKey,
+ theirId,
+ theirIdentityKey);
+ return fingerprint.getDisplayableFingerprint().getDisplayText();
+ }
+
+ public static SignalServiceAddress getSignalServiceAddressFromIdentifier(final String identifier) {
+ if (UuidUtil.isUuid(identifier)) {
+ return new SignalServiceAddress(UuidUtil.parseOrNull(identifier), null);
+ } else {
+ return new SignalServiceAddress(null, identifier);
+ }
+ }
+
+ public static JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
+ JsonNode node = parent.get(name);
+ if (node == null) {
+ throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ",
+ name));
+ }
+
+ return node;
+ }
+}
package org.asamk.signal.util;
-import org.whispersystems.signalservice.internal.util.Util;
-
-import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
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.OWNER_EXECUTE;
-import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
-import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
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];
return output.toString();
}
- public static byte[] readFully(InputStream in) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- Util.copy(in, baos);
- return baos.toByteArray();
- }
-
- public static void createPrivateDirectories(File file) throws IOException {
- if (file.exists()) {
- return;
- }
-
- final Path path = file.toPath();
- try {
- Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
- Files.createDirectories(path, PosixFilePermissions.asFileAttribute(perms));
- } catch (UnsupportedOperationException e) {
- Files.createDirectories(path);
- }
- }
-
- public static void createPrivateFile(File path) throws IOException {
- final Path 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 static File getDataHomeDir() {
String dataHome = System.getenv("XDG_DATA_HOME");
if (dataHome != null) {
return new File(new File(System.getProperty("user.home"), ".local"), "share");
}
-
- public static void copyStreamToFile(InputStream input, File outputFile) throws IOException {
- copyStreamToFile(input, outputFile, 8192);
- }
-
- public static void copyStreamToFile(InputStream input, File outputFile, int bufferSize) throws IOException {
- try (OutputStream output = new FileOutputStream(outputFile)) {
- byte[] buffer = new byte[bufferSize];
- int read;
-
- while ((read = input.read(buffer)) != -1) {
- output.write(buffer, 0, read);
- }
- }
- }
}
package org.asamk.signal.util;
-import com.fasterxml.jackson.databind.JsonNode;
-
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupIdFormatException;
-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.api.util.UuidUtil;
-
-import java.io.InvalidObjectException;
public class Util {
return f.toString();
}
- 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();
- }
-
- public static JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
- JsonNode node = parent.get(name);
- if (node == null) {
- throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ",
- name));
- }
-
- return node;
- }
-
public static GroupId decodeGroupId(String groupId) throws GroupIdFormatException {
return GroupId.fromBase64(groupId);
}
-
- public static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
- return PhoneNumberFormatter.formatNumber(number, localNumber);
- }
-
- public static SignalServiceAddress getSignalServiceAddressFromIdentifier(final String identifier) {
- if (UuidUtil.isUuid(identifier)) {
- return new SignalServiceAddress(UuidUtil.parseOrNull(identifier), null);
- } else {
- return new SignalServiceAddress(null, identifier);
- }
- }
}