X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/c0a0f89896c3f685f8016ac853fb93c7cee75863..ee5062a2cc83078d1d1d33cba32bbaa89e96f52e:/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 2edeb921..68f66455 100644 --- a/src/main/java/org/asamk/signal/Manager.java +++ b/src/main/java/org/asamk/signal/Manager.java @@ -32,7 +32,6 @@ import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECKeyPair; import org.whispersystems.libsignal.ecc.ECPublicKey; import org.whispersystems.libsignal.state.PreKeyRecord; -import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.util.KeyHelper; import org.whispersystems.libsignal.util.Medium; @@ -61,12 +60,17 @@ import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static java.nio.file.attribute.PosixFilePermission.*; + class Manager implements Signal { private final static String URL = "https://textsecure-service.whispersystems.org"; private final static TrustStore TRUST_STORE = new WhisperTrustStore(); @@ -96,7 +100,7 @@ class Manager implements Signal { private boolean registered = false; - private SignalProtocolStore signalProtocolStore; + private JsonSignalProtocolStore signalProtocolStore; private SignalServiceAccountManager accountManager; private JsonGroupStore groupStore; private JsonContactsStore contactStore; @@ -125,10 +129,29 @@ class Manager implements Signal { } public String getFileName() { - new File(dataPath).mkdirs(); return dataPath + "/" + username; } + private static void createPrivateDirectories(String path) throws IOException { + final Path file = new File(path).toPath(); + try { + Set perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE); + Files.createDirectories(file, PosixFilePermissions.asFileAttribute(perms)); + } catch (UnsupportedOperationException e) { + Files.createDirectories(file); + } + } + + private static void createPrivateFile(String path) throws IOException { + final Path file = new File(path).toPath(); + try { + Set perms = EnumSet.of(OWNER_READ, OWNER_WRITE); + Files.createFile(file, PosixFilePermissions.asFileAttribute(perms)); + } catch (UnsupportedOperationException e) { + Files.createFile(file); + } + } + public boolean userExists() { if (username == null) { return false; @@ -154,6 +177,10 @@ class Manager implements Signal { if (fileChannel != null) return; + createPrivateDirectories(dataPath); + if (!new File(getFileName()).exists()) { + createPrivateFile(getFileName()); + } fileChannel = new RandomAccessFile(new File(getFileName()), "rw").getChannel(); lock = fileChannel.tryLock(); if (lock == null) { @@ -203,7 +230,7 @@ class Manager implements Signal { File attachmentFile = getAttachmentFile(g.getAvatarId()); if (!avatarFile.exists() && attachmentFile.exists()) { try { - new File(avatarsPath).mkdirs(); + createPrivateDirectories(avatarsPath); Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { // Ignore @@ -464,6 +491,9 @@ class Manager implements Signal { InputStream attachmentStream = new FileInputStream(attachmentFile); final long attachmentSize = attachmentFile.length(); String mime = Files.probeContentType(attachmentFile.toPath()); + if (mime == null) { + mime = "application/octet-stream"; + } return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, null); } @@ -567,7 +597,7 @@ class Manager implements Signal { File aFile = getGroupAvatarFile(g.groupId); if (avatarFile != null) { - new File(avatarsPath).mkdirs(); + createPrivateDirectories(avatarsPath); Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } if (aFile.exists()) { @@ -645,7 +675,12 @@ class Manager implements Signal { throws IOException, UntrustedIdentityException { SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); - messageSender.sendMessage(message); + try { + messageSender.sendMessage(message); + } catch (UntrustedIdentityException e) { + signalProtocolStore.saveIdentity(e.getE164Number(), e.getIdentityKey(), TrustLevel.UNTRUSTED); + throw e; + } } private void sendMessage(SignalServiceDataMessage message, Collection recipients) @@ -667,7 +702,13 @@ class Manager implements Signal { deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); if (message.getGroupInfo().isPresent()) { - messageSender.sendMessage(new ArrayList<>(recipientsTS), message); + try { + messageSender.sendMessage(new ArrayList<>(recipientsTS), message); + } catch (EncapsulatedExceptions encapsulatedExceptions) { + for (UntrustedIdentityException e : encapsulatedExceptions.getUntrustedIdentityExceptions()) { + signalProtocolStore.saveIdentity(e.getE164Number(), e.getIdentityKey(), TrustLevel.UNTRUSTED); + } + } } else { // Send to all individually, so sync messages are sent correctly List untrustedIdentities = new LinkedList<>(); @@ -677,6 +718,7 @@ class Manager implements Signal { try { messageSender.sendMessage(address, message); } catch (UntrustedIdentityException e) { + signalProtocolStore.saveIdentity(e.getE164Number(), e.getIdentityKey(), TrustLevel.UNTRUSTED); untrustedIdentities.add(e); } catch (UnregisteredUserException e) { unregisteredUsers.add(e); @@ -702,6 +744,10 @@ class Manager implements Signal { SignalServiceCipher cipher = new SignalServiceCipher(new SignalServiceAddress(username), signalProtocolStore); try { return cipher.decrypt(envelope); + } catch (org.whispersystems.libsignal.UntrustedIdentityException e) { + // TODO temporarily store message, until user has accepted the key + signalProtocolStore.saveIdentity(e.getName(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED); + throw e; } catch (Exception e) { throw e; } @@ -890,7 +936,7 @@ class Manager implements Signal { } private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException { - new File(avatarsPath).mkdirs(); + createPrivateDirectories(avatarsPath); if (attachment.isPointer()) { SignalServiceAttachmentPointer pointer = attachment.asPointer(); return retrieveAttachment(pointer, getContactAvatarFile(number), false); @@ -905,7 +951,7 @@ class Manager implements Signal { } private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException { - new File(avatarsPath).mkdirs(); + createPrivateDirectories(avatarsPath); if (attachment.isPointer()) { SignalServiceAttachmentPointer pointer = attachment.asPointer(); return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false); @@ -920,7 +966,7 @@ class Manager implements Signal { } private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException { - new File(attachmentsPath).mkdirs(); + createPrivateDirectories(attachmentsPath); return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true); } @@ -1082,4 +1128,54 @@ class Manager implements Signal { public GroupInfo getGroup(byte[] groupId) { return groupStore.getGroup(groupId); } + + public Map> getIdentities() { + return signalProtocolStore.getIdentities(); + } + + public List getIdentities(String number) { + return signalProtocolStore.getIdentities(number); + } + + /** + * Trust this the identity with this fingerprint + * + * @param name username of the identity + * @param fingerprint Fingerprint + */ + public boolean trustIdentityVerified(String name, byte[] fingerprint) { + List ids = signalProtocolStore.getIdentities(name); + if (ids == null) { + return false; + } + for (JsonIdentityKeyStore.Identity id : ids) { + if (!Arrays.equals(id.identityKey.serialize(), fingerprint)) { + continue; + } + + signalProtocolStore.saveIdentity(name, id.identityKey, TrustLevel.TRUSTED_VERIFIED); + save(); + return true; + } + return false; + } + + /** + * Trust all keys of this identity without verification + * + * @param name username of the identity + */ + public boolean trustIdentityAllKeys(String name) { + List ids = signalProtocolStore.getIdentities(name); + if (ids == null) { + return false; + } + for (JsonIdentityKeyStore.Identity id : ids) { + if (id.trustLevel == TrustLevel.UNTRUSTED) { + signalProtocolStore.saveIdentity(name, id.identityKey, TrustLevel.TRUSTED_UNVERIFIED); + } + } + save(); + return true; + } }