mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Cleanup utils
This commit is contained in:
parent
b738f5740c
commit
bbdd6a8910
19 changed files with 477 additions and 456 deletions
60
src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java
Normal file
60
src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,8 +37,11 @@ import org.asamk.signal.manager.storage.profiles.SignalProfile;
|
||||||
import org.asamk.signal.manager.storage.profiles.SignalProfileEntry;
|
import org.asamk.signal.manager.storage.profiles.SignalProfileEntry;
|
||||||
import org.asamk.signal.manager.storage.protocol.IdentityInfo;
|
import org.asamk.signal.manager.storage.protocol.IdentityInfo;
|
||||||
import org.asamk.signal.manager.storage.stickers.Sticker;
|
import org.asamk.signal.manager.storage.stickers.Sticker;
|
||||||
import org.asamk.signal.util.IOUtils;
|
import org.asamk.signal.manager.util.AttachmentUtils;
|
||||||
import org.asamk.signal.util.Util;
|
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.InvalidMetadataMessageException;
|
||||||
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
|
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
|
||||||
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
|
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
|
||||||
|
@ -50,6 +53,7 @@ import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
|
||||||
import org.signal.libsignal.metadata.ProtocolNoSessionException;
|
import org.signal.libsignal.metadata.ProtocolNoSessionException;
|
||||||
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
||||||
import org.signal.libsignal.metadata.SelfSendException;
|
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.GroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||||
|
@ -125,6 +129,7 @@ import org.whispersystems.signalservice.api.push.ContactTokenDetails;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
|
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
|
||||||
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.StreamDetails;
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||||
|
@ -185,6 +190,7 @@ public class Manager implements Closeable {
|
||||||
final static Logger logger = LoggerFactory.getLogger(Manager.class);
|
final static Logger logger = LoggerFactory.getLogger(Manager.class);
|
||||||
|
|
||||||
private final SleepTimer timer = new UptimeSleepTimer();
|
private final SleepTimer timer = new UptimeSleepTimer();
|
||||||
|
private final CertificateValidator certificateValidator = new CertificateValidator(ServiceConfig.getUnidentifiedSenderTrustRoot());
|
||||||
|
|
||||||
private final SignalServiceConfiguration serviceConfiguration;
|
private final SignalServiceConfiguration serviceConfiguration;
|
||||||
private final String userAgent;
|
private final String userAgent;
|
||||||
|
@ -419,7 +425,7 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
|
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
|
||||||
Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri);
|
DeviceLinkInfo info = DeviceLinkInfo.parseDeviceLinkUri(linkUri);
|
||||||
|
|
||||||
addDevice(info.deviceIdentifier, info.deviceKey);
|
addDevice(info.deviceIdentifier, info.deviceKey);
|
||||||
}
|
}
|
||||||
|
@ -696,7 +702,7 @@ public class Manager implements Closeable {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of(Utils.createAttachment(file));
|
return Optional.of(AttachmentUtils.createAttachment(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException {
|
private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException {
|
||||||
|
@ -705,7 +711,7 @@ public class Manager implements Closeable {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of(Utils.createAttachment(file));
|
return Optional.of(AttachmentUtils.createAttachment(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
|
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
|
||||||
|
@ -751,7 +757,7 @@ public class Manager implements Closeable {
|
||||||
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
|
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
|
||||||
.withBody(messageText);
|
.withBody(messageText);
|
||||||
if (attachments != null) {
|
if (attachments != null) {
|
||||||
messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
|
messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendGroupMessage(messageBuilder, groupId);
|
return sendGroupMessage(messageBuilder, groupId);
|
||||||
|
@ -928,7 +934,7 @@ public class Manager implements Closeable {
|
||||||
newE164Members.remove(contact.getNumber());
|
newE164Members.remove(contact.getNumber());
|
||||||
}
|
}
|
||||||
throw new IOException("Failed to add members "
|
throw new IOException("Failed to add members "
|
||||||
+ Util.join(", ", newE164Members)
|
+ String.join(", ", newE164Members)
|
||||||
+ " to group: Not registered on Signal");
|
+ " to group: Not registered on Signal");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -971,7 +977,7 @@ public class Manager implements Closeable {
|
||||||
File aFile = getGroupAvatarFile(g.getGroupId());
|
File aFile = getGroupAvatarFile(g.getGroupId());
|
||||||
if (aFile.exists()) {
|
if (aFile.exists()) {
|
||||||
try {
|
try {
|
||||||
group.withAvatar(Utils.createAttachment(aFile));
|
group.withAvatar(AttachmentUtils.createAttachment(aFile));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new AttachmentInvalidException(aFile.toString(), e);
|
throw new AttachmentInvalidException(aFile.toString(), e);
|
||||||
}
|
}
|
||||||
|
@ -1022,7 +1028,7 @@ public class Manager implements Closeable {
|
||||||
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
|
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
|
||||||
.withBody(messageText);
|
.withBody(messageText);
|
||||||
if (attachments != null) {
|
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
|
// Upload attachments here, so we only upload once even for multiple recipients
|
||||||
SignalServiceMessageSender messageSender = createMessageSender();
|
SignalServiceMessageSender messageSender = createMessageSender();
|
||||||
|
@ -1510,7 +1516,7 @@ public class Manager implements Closeable {
|
||||||
private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
|
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(),
|
SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(),
|
||||||
account.getSignalProtocolStore(),
|
account.getSignalProtocolStore(),
|
||||||
Utils.getCertificateValidator());
|
certificateValidator);
|
||||||
try {
|
try {
|
||||||
return cipher.decrypt(envelope);
|
return cipher.decrypt(envelope);
|
||||||
} catch (ProtocolUntrustedIdentityException e) {
|
} catch (ProtocolUntrustedIdentityException e) {
|
||||||
|
@ -1820,7 +1826,7 @@ public class Manager implements Closeable {
|
||||||
) {
|
) {
|
||||||
SignalServiceEnvelope envelope;
|
SignalServiceEnvelope envelope;
|
||||||
try {
|
try {
|
||||||
envelope = Utils.loadEnvelope(fileEntry);
|
envelope = MessageCacheUtils.loadEnvelope(fileEntry);
|
||||||
if (envelope == null) {
|
if (envelope == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1887,7 +1893,7 @@ public class Manager implements Closeable {
|
||||||
try {
|
try {
|
||||||
String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
|
String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
|
||||||
File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
|
File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
|
||||||
Utils.storeEnvelope(envelope1, cacheFile);
|
MessageCacheUtils.storeEnvelope(envelope1, cacheFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
|
logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -2240,7 +2246,7 @@ public class Manager implements Closeable {
|
||||||
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
|
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
|
||||||
} else {
|
} else {
|
||||||
SignalServiceAttachmentStream stream = attachment.asStream();
|
SignalServiceAttachmentStream stream = attachment.asStream();
|
||||||
return Utils.retrieveAttachment(stream, getContactAvatarFile(number));
|
return AttachmentUtils.retrieveAttachment(stream, getContactAvatarFile(number));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2257,7 +2263,7 @@ public class Manager implements Closeable {
|
||||||
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
|
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
|
||||||
} else {
|
} else {
|
||||||
SignalServiceAttachmentStream stream = attachment.asStream();
|
SignalServiceAttachmentStream stream = attachment.asStream();
|
||||||
return Utils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
|
return AttachmentUtils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2509,7 +2515,7 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactInfo getContact(String number) {
|
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 GroupInfo getGroup(GroupId groupId) {
|
||||||
|
@ -2613,7 +2619,8 @@ public class Manager implements Closeable {
|
||||||
public String computeSafetyNumber(
|
public String computeSafetyNumber(
|
||||||
SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
|
SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
|
||||||
) {
|
) {
|
||||||
return Utils.computeSafetyNumber(account.getSelfAddress(),
|
return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(),
|
||||||
|
account.getSelfAddress(),
|
||||||
getIdentityKeyPair().getPublicKey(),
|
getIdentityKeyPair().getPublicKey(),
|
||||||
theirAddress,
|
theirAddress,
|
||||||
theirIdentityKey);
|
theirIdentityKey);
|
||||||
|
@ -2626,12 +2633,12 @@ public class Manager implements Closeable {
|
||||||
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
|
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
|
||||||
String canonicalizedNumber = UuidUtil.isUuid(identifier)
|
String canonicalizedNumber = UuidUtil.isUuid(identifier)
|
||||||
? identifier
|
? identifier
|
||||||
: Util.canonicalizeNumber(identifier, account.getUsername());
|
: PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
|
||||||
return resolveSignalServiceAddress(canonicalizedNumber);
|
return resolveSignalServiceAddress(canonicalizedNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
|
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
|
||||||
SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier);
|
SignalServiceAddress address = Utils.getSignalServiceAddressFromIdentifier(identifier);
|
||||||
|
|
||||||
return resolveSignalServiceAddress(address);
|
return resolveSignalServiceAddress(address);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.asamk.signal.manager;
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
import org.asamk.signal.manager.storage.SignalAccount;
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
|
import org.asamk.signal.manager.util.KeyUtils;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||||
|
@ -71,8 +72,7 @@ public class ProvisioningManager {
|
||||||
public String getDeviceLinkUri() throws TimeoutException, IOException {
|
public String getDeviceLinkUri() throws TimeoutException, IOException {
|
||||||
String deviceUuid = accountManager.getNewDeviceUuid();
|
String deviceUuid = accountManager.getNewDeviceUuid();
|
||||||
|
|
||||||
return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(deviceUuid,
|
return new DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
|
||||||
identityKey.getPublicKey().getPublicKey()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
|
public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.asamk.signal.manager;
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
import org.signal.zkgroup.ServerPublicParams;
|
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.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||||
|
@ -106,6 +109,14 @@ public class ServiceConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
private static Map<Integer, SignalCdnUrl[]> makeSignalCdnUrlMapFor(
|
||||||
SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls
|
SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.asamk.signal.manager.groups;
|
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 {
|
public class GroupIdV1 extends GroupId {
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.asamk.signal.manager.groups;
|
package org.asamk.signal.manager.groups;
|
||||||
|
|
||||||
import org.asamk.signal.manager.KeyUtils;
|
import org.asamk.signal.manager.util.KeyUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import org.asamk.signal.manager.groups.GroupLinkPassword;
|
||||||
import org.asamk.signal.manager.groups.GroupUtils;
|
import org.asamk.signal.manager.groups.GroupUtils;
|
||||||
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
|
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
|
||||||
import org.asamk.signal.manager.storage.profiles.SignalProfile;
|
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.AccessControl;
|
||||||
import org.signal.storageservice.protos.groups.GroupChange;
|
import org.signal.storageservice.protos.groups.GroupChange;
|
||||||
import org.signal.storageservice.protos.groups.Member;
|
import org.signal.storageservice.protos.groups.Member;
|
||||||
|
|
|
@ -25,8 +25,8 @@ import org.asamk.signal.manager.storage.protocol.SignalServiceAddressResolver;
|
||||||
import org.asamk.signal.manager.storage.stickers.StickerStore;
|
import org.asamk.signal.manager.storage.stickers.StickerStore;
|
||||||
import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
|
import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
|
||||||
import org.asamk.signal.manager.storage.threads.ThreadInfo;
|
import org.asamk.signal.manager.storage.threads.ThreadInfo;
|
||||||
import org.asamk.signal.util.IOUtils;
|
import org.asamk.signal.manager.util.IOUtils;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.manager.util.Utils;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -211,28 +211,28 @@ public class SignalAccount implements Closeable {
|
||||||
deviceId = node.asInt();
|
deviceId = node.asInt();
|
||||||
}
|
}
|
||||||
if (rootNode.has("isMultiDevice")) {
|
if (rootNode.has("isMultiDevice")) {
|
||||||
isMultiDevice = Util.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
|
isMultiDevice = Utils.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
|
||||||
}
|
}
|
||||||
username = Util.getNotNullNode(rootNode, "username").asText();
|
username = Utils.getNotNullNode(rootNode, "username").asText();
|
||||||
password = Util.getNotNullNode(rootNode, "password").asText();
|
password = Utils.getNotNullNode(rootNode, "password").asText();
|
||||||
JsonNode pinNode = rootNode.get("registrationLockPin");
|
JsonNode pinNode = rootNode.get("registrationLockPin");
|
||||||
registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText();
|
registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText();
|
||||||
if (rootNode.has("signalingKey")) {
|
if (rootNode.has("signalingKey")) {
|
||||||
signalingKey = Util.getNotNullNode(rootNode, "signalingKey").asText();
|
signalingKey = Utils.getNotNullNode(rootNode, "signalingKey").asText();
|
||||||
}
|
}
|
||||||
if (rootNode.has("preKeyIdOffset")) {
|
if (rootNode.has("preKeyIdOffset")) {
|
||||||
preKeyIdOffset = Util.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
|
preKeyIdOffset = Utils.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
|
||||||
} else {
|
} else {
|
||||||
preKeyIdOffset = 0;
|
preKeyIdOffset = 0;
|
||||||
}
|
}
|
||||||
if (rootNode.has("nextSignedPreKeyId")) {
|
if (rootNode.has("nextSignedPreKeyId")) {
|
||||||
nextSignedPreKeyId = Util.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
|
nextSignedPreKeyId = Utils.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
|
||||||
} else {
|
} else {
|
||||||
nextSignedPreKeyId = 0;
|
nextSignedPreKeyId = 0;
|
||||||
}
|
}
|
||||||
if (rootNode.has("profileKey")) {
|
if (rootNode.has("profileKey")) {
|
||||||
try {
|
try {
|
||||||
profileKey = new ProfileKey(Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText()));
|
profileKey = new ProfileKey(Base64.decode(Utils.getNotNullNode(rootNode, "profileKey").asText()));
|
||||||
} catch (InvalidInputException e) {
|
} catch (InvalidInputException e) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
|
"Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
|
||||||
|
@ -240,9 +240,9 @@ public class SignalAccount implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signalProtocolStore = jsonProcessor.convertValue(Util.getNotNullNode(rootNode, "axolotlStore"),
|
signalProtocolStore = jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
|
||||||
JsonSignalProtocolStore.class);
|
JsonSignalProtocolStore.class);
|
||||||
registered = Util.getNotNullNode(rootNode, "registered").asBoolean();
|
registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
|
||||||
JsonNode groupStoreNode = rootNode.get("groupStore");
|
JsonNode groupStoreNode = rootNode.get("groupStore");
|
||||||
if (groupStoreNode != null) {
|
if (groupStoreNode != null) {
|
||||||
groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
|
groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
|
||||||
|
|
|
@ -16,8 +16,8 @@ import org.asamk.signal.manager.groups.GroupId;
|
||||||
import org.asamk.signal.manager.groups.GroupIdV1;
|
import org.asamk.signal.manager.groups.GroupIdV1;
|
||||||
import org.asamk.signal.manager.groups.GroupIdV2;
|
import org.asamk.signal.manager.groups.GroupIdV2;
|
||||||
import org.asamk.signal.manager.groups.GroupUtils;
|
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.Hex;
|
||||||
import org.asamk.signal.util.IOUtils;
|
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
|
||||||
import org.asamk.signal.manager.TrustLevel;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
|
@ -51,7 +51,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
|
||||||
if (resolver != null) {
|
if (resolver != null) {
|
||||||
return resolver.resolveSignalServiceAddress(identifier);
|
return resolver.resolveSignalServiceAddress(identifier);
|
||||||
} else {
|
} else {
|
||||||
return Util.getSignalServiceAddressFromIdentifier(identifier);
|
return Utils.getSignalServiceAddressFromIdentifier(identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
|
||||||
UUID uuid = trustedKey.hasNonNull("uuid") ? UuidUtil.parseOrNull(trustedKey.get("uuid")
|
UUID uuid = trustedKey.hasNonNull("uuid") ? UuidUtil.parseOrNull(trustedKey.get("uuid")
|
||||||
.asText()) : null;
|
.asText()) : null;
|
||||||
final SignalServiceAddress serviceAddress = uuid == null
|
final SignalServiceAddress serviceAddress = uuid == null
|
||||||
? Util.getSignalServiceAddressFromIdentifier(trustedKeyName)
|
? Utils.getSignalServiceAddressFromIdentifier(trustedKeyName)
|
||||||
: new SignalServiceAddress(uuid, trustedKeyName);
|
: new SignalServiceAddress(uuid, trustedKeyName);
|
||||||
try {
|
try {
|
||||||
IdentityKey id = new IdentityKey(Base64.decode(trustedKey.get("identityKey").asText()), 0);
|
IdentityKey id = new IdentityKey(Base64.decode(trustedKey.get("identityKey").asText()), 0);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||||
|
@ -43,7 +43,7 @@ class JsonSessionStore implements SessionStore {
|
||||||
if (resolver != null) {
|
if (resolver != null) {
|
||||||
return resolver.resolveSignalServiceAddress(identifier);
|
return resolver.resolveSignalServiceAddress(identifier);
|
||||||
} else {
|
} else {
|
||||||
return Util.getSignalServiceAddressFromIdentifier(identifier);
|
return Utils.getSignalServiceAddressFromIdentifier(identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ class JsonSessionStore implements SessionStore {
|
||||||
|
|
||||||
UUID uuid = session.hasNonNull("uuid") ? UuidUtil.parseOrNull(session.get("uuid").asText()) : null;
|
UUID uuid = session.hasNonNull("uuid") ? UuidUtil.parseOrNull(session.get("uuid").asText()) : null;
|
||||||
final SignalServiceAddress serviceAddress = uuid == null
|
final SignalServiceAddress serviceAddress = uuid == null
|
||||||
? Util.getSignalServiceAddressFromIdentifier(sessionName)
|
? Utils.getSignalServiceAddressFromIdentifier(sessionName)
|
||||||
: new SignalServiceAddress(uuid, sessionName);
|
: new SignalServiceAddress(uuid, sessionName);
|
||||||
final int deviceId = session.get("deviceId").asInt();
|
final int deviceId = session.get("deviceId").asInt();
|
||||||
final String record = session.get("record").asText();
|
final String record = session.get("record").asText();
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
72
src/main/java/org/asamk/signal/manager/util/IOUtils.java
Normal file
72
src/main/java/org/asamk/signal/manager/util/IOUtils.java
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.asamk.signal.manager;
|
package org.asamk.signal.manager.util;
|
||||||
|
|
||||||
import org.asamk.signal.util.RandomUtils;
|
import org.asamk.signal.util.RandomUtils;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
|
@ -10,11 +10,11 @@ public class KeyUtils {
|
||||||
private KeyUtils() {
|
private KeyUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static String createSignalingKey() {
|
public static String createSignalingKey() {
|
||||||
return getSecret(52);
|
return getSecret(52);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ProfileKey createProfileKey() {
|
public static ProfileKey createProfileKey() {
|
||||||
try {
|
try {
|
||||||
return new ProfileKey(getSecretBytes(32));
|
return new ProfileKey(getSecretBytes(32));
|
||||||
} catch (InvalidInputException e) {
|
} catch (InvalidInputException e) {
|
||||||
|
@ -22,11 +22,11 @@ public class KeyUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String createPassword() {
|
public static String createPassword() {
|
||||||
return getSecret(18);
|
return getSecret(18);
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] createStickerUploadKey() {
|
public static byte[] createStickerUploadKey() {
|
||||||
return getSecretBytes(32);
|
return getSecretBytes(32);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
src/main/java/org/asamk/signal/manager/util/Utils.java
Normal file
97
src/main/java/org/asamk/signal/manager/util/Utils.java
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +1,16 @@
|
||||||
package org.asamk.signal.util;
|
package org.asamk.signal.util;
|
||||||
|
|
||||||
import org.whispersystems.signalservice.internal.util.Util;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.nio.charset.Charset;
|
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 {
|
public class IOUtils {
|
||||||
|
|
||||||
private 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 {
|
public static String readAll(InputStream in, Charset charset) throws IOException {
|
||||||
StringWriter output = new StringWriter();
|
StringWriter output = new StringWriter();
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
|
@ -40,36 +21,6 @@ public class IOUtils {
|
||||||
return output.toString();
|
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() {
|
public static File getDataHomeDir() {
|
||||||
String dataHome = System.getenv("XDG_DATA_HOME");
|
String dataHome = System.getenv("XDG_DATA_HOME");
|
||||||
if (dataHome != null) {
|
if (dataHome != null) {
|
||||||
|
@ -78,19 +29,4 @@ public class IOUtils {
|
||||||
|
|
||||||
return new File(new File(System.getProperty("user.home"), ".local"), "share");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
package org.asamk.signal.util;
|
package org.asamk.signal.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
|
|
||||||
import org.asamk.signal.manager.groups.GroupId;
|
import org.asamk.signal.manager.groups.GroupId;
|
||||||
import org.asamk.signal.manager.groups.GroupIdFormatException;
|
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 {
|
public class Util {
|
||||||
|
|
||||||
|
@ -26,41 +18,7 @@ public class Util {
|
||||||
return f.toString();
|
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 {
|
public static GroupId decodeGroupId(String groupId) throws GroupIdFormatException {
|
||||||
return GroupId.fromBase64(groupId);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue