Cleanup utils

This commit is contained in:
AsamK 2020-12-29 22:48:39 +01:00
parent b738f5740c
commit bbdd6a8910
19 changed files with 477 additions and 456 deletions

View 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);
}
}

View file

@ -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);
} }

View file

@ -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 {

View file

@ -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
) { ) {

View file

@ -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;
}
}
}

View file

@ -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 {

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -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;
}
}

View 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);
}
}
}
}

View file

@ -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);
} }

View file

@ -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());
}
}
}
}

View 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;
}
}

View file

@ -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);
}
}
}
} }

View file

@ -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);
}
}
} }