X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/ef55196806a13b932f57bb9d8af971afd18ecdf1..bb342babba6bf716375ef4ba65919f80258aa29f:/src/main/java/org/asamk/signal/Manager.java diff --git a/src/main/java/org/asamk/signal/Manager.java b/src/main/java/org/asamk/signal/Manager.java index 49d22db0..8a606edd 100644 --- a/src/main/java/org/asamk/signal/Manager.java +++ b/src/main/java/org/asamk/signal/Manager.java @@ -35,7 +35,6 @@ import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; import org.asamk.signal.storage.threads.JsonThreadStore; import org.asamk.signal.storage.threads.ThreadInfo; -import org.asamk.signal.util.Base64; import org.asamk.signal.util.Util; import org.whispersystems.libsignal.*; import org.whispersystems.libsignal.ecc.Curve; @@ -62,8 +61,13 @@ import org.whispersystems.signalservice.api.push.TrustStore; import org.whispersystems.signalservice.api.push.exceptions.*; 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.configuration.SignalCdnUrl; +import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; +import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; -import org.whispersystems.signalservice.internal.push.SignalServiceUrl; +import org.whispersystems.signalservice.internal.util.Base64; import java.io.*; import java.net.URI; @@ -87,8 +91,12 @@ import static java.nio.file.attribute.PosixFilePermission.*; class Manager implements Signal { private final static String URL = "https://textsecure-service.whispersystems.org"; + private final static String CDN_URL = "https://cdn.signal.org"; private final static TrustStore TRUST_STORE = new WhisperTrustStore(); - private final static SignalServiceUrl[] serviceUrls = new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}; + private final static SignalServiceConfiguration serviceConfiguration = new SignalServiceConfiguration( + new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}, + new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)} + ); public final static String PROJECT_NAME = Manager.class.getPackage().getImplementationTitle(); public final static String PROJECT_VERSION = Manager.class.getPackage().getImplementationVersion(); @@ -110,6 +118,7 @@ class Manager implements Signal { private String username; private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; private String password; + private String registrationLockPin; private String signalingKey; private int preKeyIdOffset; private int nextSignedPreKeyId; @@ -123,6 +132,8 @@ class Manager implements Signal { private JsonThreadStore threadStore; private SignalServiceMessagePipe messagePipe = null; + private SleepTimer timer = new UptimeSleepTimer(); + public Manager(String username, String settingsPath) { this.username = username; this.settingsPath = settingsPath; @@ -231,7 +242,7 @@ class Manager implements Signal { migrateLegacyConfigs(); - accountManager = new SignalServiceAccountManager(serviceUrls, username, password, deviceId, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceConfiguration, username, password, deviceId, USER_AGENT, timer); try { if (registered && accountManager.getPreKeysCount() < PREKEY_MINIMUM_COUNT) { refreshPreKeys(); @@ -252,6 +263,8 @@ class Manager implements Signal { } username = getNotNullNode(rootNode, "username").asText(); password = getNotNullNode(rootNode, "password").asText(); + JsonNode pinNode = rootNode.get("registrationLockPin"); + registrationLockPin = pinNode == null ? null : pinNode.asText(); if (rootNode.has("signalingKey")) { signalingKey = getNotNullNode(rootNode, "signalingKey").asText(); } @@ -320,6 +333,7 @@ class Manager implements Signal { rootNode.put("username", username) .put("deviceId", deviceId) .put("password", password) + .put("registrationLockPin", registrationLockPin) .put("signalingKey", signalingKey) .put("preKeyIdOffset", preKeyIdOffset) .put("nextSignedPreKeyId", nextSignedPreKeyId) @@ -356,7 +370,7 @@ class Manager implements Signal { public void register(boolean voiceVerification) throws IOException { password = Util.getSecret(18); - accountManager = new SignalServiceAccountManager(serviceUrls, username, password, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceConfiguration, username, password, USER_AGENT, timer); if (voiceVerification) accountManager.requestVoiceVerificationCode(); @@ -368,7 +382,7 @@ class Manager implements Signal { } public void updateAccountAttributes() throws IOException { - accountManager.setAccountAttributes(signalingKey, signalProtocolStore.getLocalRegistrationId(), false, false, true); + accountManager.setAccountAttributes(signalingKey, signalProtocolStore.getLocalRegistrationId(), true, registrationLockPin); } public void unregister() throws IOException { @@ -381,7 +395,7 @@ class Manager implements Signal { public URI getDeviceLinkUri() throws TimeoutException, IOException { password = Util.getSecret(18); - accountManager = new SignalServiceAccountManager(serviceUrls, username, password, USER_AGENT); + accountManager = new SignalServiceAccountManager(serviceConfiguration, username, password, USER_AGENT, timer); String uuid = accountManager.getNewDeviceUuid(); registered = false; @@ -460,7 +474,8 @@ class Manager implements Signal { IdentityKeyPair identityKeyPair = signalProtocolStore.getIdentityKeyPair(); String verificationCode = accountManager.getNewDeviceVerificationCode(); - accountManager.addDevice(deviceIdentifier, deviceKey, identityKeyPair, verificationCode); + // TODO send profile key + accountManager.addDevice(deviceIdentifier, deviceKey, identityKeyPair, Optional.absent(), verificationCode); } private List generatePreKeys() { @@ -481,24 +496,6 @@ class Manager implements Signal { return records; } - private PreKeyRecord getOrGenerateLastResortPreKey() { - if (signalProtocolStore.containsPreKey(Medium.MAX_VALUE)) { - try { - return signalProtocolStore.loadPreKey(Medium.MAX_VALUE); - } catch (InvalidKeyIdException e) { - signalProtocolStore.removePreKey(Medium.MAX_VALUE); - } - } - - ECKeyPair keyPair = Curve.generateKeyPair(); - PreKeyRecord record = new PreKeyRecord(Medium.MAX_VALUE, keyPair); - - signalProtocolStore.storePreKey(Medium.MAX_VALUE, record); - save(); - - return record; - } - private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) { try { ECKeyPair keyPair = Curve.generateKeyPair(); @@ -515,24 +512,33 @@ class Manager implements Signal { } } - public void verifyAccount(String verificationCode) throws IOException { + public void verifyAccount(String verificationCode, String pin) throws IOException { verificationCode = verificationCode.replace("-", ""); signalingKey = Util.getSecret(52); - accountManager.verifyAccountWithCode(verificationCode, signalingKey, signalProtocolStore.getLocalRegistrationId(), false, false, true); + accountManager.verifyAccountWithCode(verificationCode, signalingKey, signalProtocolStore.getLocalRegistrationId(), true, pin); //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); registered = true; + registrationLockPin = pin; refreshPreKeys(); save(); } + public void setRegistrationLockPin(Optional pin) throws IOException { + accountManager.setPin(pin); + if (pin.isPresent()) { + registrationLockPin = pin.get(); + } else { + registrationLockPin = null; + } + } + private void refreshPreKeys() throws IOException { List oneTimePreKeys = generatePreKeys(); - PreKeyRecord lastResortKey = getOrGenerateLastResortPreKey(); SignedPreKeyRecord signedPreKeyRecord = generateSignedPreKey(signalProtocolStore.getIdentityKeyPair()); - accountManager.setPreKeys(signalProtocolStore.getIdentityKeyPair().getPublicKey(), lastResortKey, signedPreKeyRecord, oneTimePreKeys); + accountManager.setPreKeys(signalProtocolStore.getIdentityKeyPair().getPublicKey(), signedPreKeyRecord, oneTimePreKeys); } @@ -559,7 +565,7 @@ class Manager implements Signal { mime = "application/octet-stream"; } // TODO mabybe add a parameter to set the voiceNote and preview option - return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, Optional.absent(), null); + return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, Optional.absent(), 0, 0, null); } private Optional createGroupAvatarAttachment(byte[] groupId) throws IOException { @@ -816,6 +822,16 @@ class Manager implements Signal { save(); } + @Override + public List getGroupIds() { + List groups = getGroups(); + List ids = new ArrayList(groups.size()); + for (GroupInfo group : groups) { + ids.add(group.groupId); + } + return ids; + } + @Override public String getGroupName(byte[] groupId) { GroupInfo group = getGroup(groupId); @@ -875,7 +891,7 @@ class Manager implements Signal { private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { - SignalServiceMessageSender messageSender = new SignalServiceMessageSender(serviceUrls, username, password, + SignalServiceMessageSender messageSender = new SignalServiceMessageSender(serviceConfiguration, username, password, deviceId, signalProtocolStore, USER_AGENT, Optional.fromNullable(messagePipe), Optional.absent()); try { messageSender.sendMessage(message); @@ -892,7 +908,7 @@ class Manager implements Signal { SignalServiceDataMessage message = null; try { - SignalServiceMessageSender messageSender = new SignalServiceMessageSender(serviceUrls, username, password, + SignalServiceMessageSender messageSender = new SignalServiceMessageSender(serviceConfiguration, username, password, deviceId, signalProtocolStore, USER_AGENT, Optional.fromNullable(messagePipe), Optional.absent()); message = messageBuilder.build(); @@ -1116,12 +1132,14 @@ class Manager implements Signal { System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage()); } } + // Try to delete directory if empty + dir.delete(); } } public void receiveMessages(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException { retryFailedReceivedMessages(handler, ignoreAttachments); - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration, username, password, deviceId, signalingKey, USER_AGENT, null, timer); try { if (messagePipe == null) { @@ -1169,6 +1187,8 @@ class Manager implements Signal { try { cacheFile = getMessageCacheFile(envelope.getSource(), now, envelope.getTimestamp()); Files.delete(cacheFile.toPath()); + // Try to delete directory if empty + new File(getMessageCachePath()).delete(); } catch (IOException e) { System.err.println("Failed to delete cached message file “" + cacheFile + "”: " + e.getMessage()); } @@ -1199,7 +1219,6 @@ class Manager implements Signal { if (rm.isContactsRequest()) { try { sendContacts(); - sendVerifiedMessage(); } catch (UntrustedIdentityException | IOException e) { e.printStackTrace(); } @@ -1229,6 +1248,9 @@ class Manager implements Signal { } syncGroup.members.addAll(g.getMembers()); syncGroup.active = g.isActive(); + if (g.getColor().isPresent()) { + syncGroup.color = g.getColor().get(); + } if (g.getAvatar().isPresent()) { retrieveGroupAvatarAttachment(g.getAvatar().get(), syncGroup.groupId); @@ -1294,10 +1316,8 @@ class Manager implements Signal { } } if (syncMessage.getVerified().isPresent()) { - final List verifiedList = syncMessage.getVerified().get(); - for (VerifiedMessage v : verifiedList) { - signalProtocolStore.saveIdentity(v.getDestination(), v.getIdentityKey(), TrustLevel.fromVerifiedState(v.getVerified())); - } + final VerifiedMessage verifiedMessage = syncMessage.getVerified().get(); + signalProtocolStore.saveIdentity(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); } } } @@ -1424,7 +1444,7 @@ class Manager implements Signal { } } - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration, username, password, deviceId, signalingKey, USER_AGENT, null, timer); File tmpFile = Util.createTempFile(); try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, MAX_ATTACHMENT_SIZE)) { @@ -1450,7 +1470,7 @@ class Manager implements Signal { } private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException { - final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT); + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration, username, password, deviceId, signalingKey, USER_AGENT, null, timer); return messageReceiver.retrieveAttachment(pointer, tmpFile, MAX_ATTACHMENT_SIZE); } @@ -1476,9 +1496,11 @@ class Manager implements Signal { try (OutputStream fos = new FileOutputStream(groupsFile)) { DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos); for (GroupInfo record : groupStore.getGroups()) { + ThreadInfo info = threadStore.getThread(Base64.encodeBytes(record.groupId)); out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name), new ArrayList<>(record.members), createGroupAvatarAttachment(record.groupId), - record.active)); + record.active, Optional.fromNullable(info != null ? info.messageExpirationTime : null), + Optional.fromNullable(record.color))); } } @@ -1509,8 +1531,24 @@ class Manager implements Signal { try (OutputStream fos = new FileOutputStream(contactsFile)) { DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos); for (ContactInfo record : contactStore.getContacts()) { + VerifiedMessage verifiedMessage = null; + ThreadInfo info = threadStore.getThread(record.number); + if (getIdentities().containsKey(record.number)) { + JsonIdentityKeyStore.Identity currentIdentity = null; + for (JsonIdentityKeyStore.Identity id : getIdentities().get(record.number)) { + if (currentIdentity == null || id.getDateAdded().after(currentIdentity.getDateAdded())) { + currentIdentity = id; + } + } + if (currentIdentity != null) { + verifiedMessage = new VerifiedMessage(record.number, currentIdentity.getIdentityKey(), currentIdentity.getTrustLevel().toVerifiedState(), currentIdentity.getDateAdded().getTime()); + } + } + + // TODO include profile key out.write(new DeviceContact(record.number, Optional.fromNullable(record.name), - createContactAvatarAttachment(record.number), Optional.fromNullable(record.color))); + createContactAvatarAttachment(record.number), Optional.fromNullable(record.color), + Optional.fromNullable(verifiedMessage), Optional.absent(), false, Optional.fromNullable(info != null ? info.messageExpirationTime : null))); } } @@ -1534,23 +1572,8 @@ class Manager implements Signal { } } - private void sendVerifiedMessage() throws IOException, UntrustedIdentityException { - List verifiedMessages = new LinkedList<>(); - for (Map.Entry> x : getIdentities().entrySet()) { - final String name = x.getKey(); - for (JsonIdentityKeyStore.Identity id : x.getValue()) { - if (id.getTrustLevel() == TrustLevel.TRUSTED_UNVERIFIED) { - continue; - } - VerifiedMessage verifiedMessage = new VerifiedMessage(name, id.getIdentityKey(), id.getTrustLevel().toVerifiedState()); - verifiedMessages.add(verifiedMessage); - } - } - sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessages)); - } - private void sendVerifiedMessage(String destination, IdentityKey identityKey, TrustLevel trustLevel) throws IOException, UntrustedIdentityException { - VerifiedMessage verifiedMessage = new VerifiedMessage(destination, identityKey, trustLevel.toVerifiedState()); + VerifiedMessage verifiedMessage = new VerifiedMessage(destination, identityKey, trustLevel.toVerifiedState(), System.currentTimeMillis()); sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); }