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