From 83d5d53d8a201cebbdbfb2ebe3f39d8d91a80091 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 21 Dec 2020 21:59:41 +0100 Subject: [PATCH 01/16] Bump version --- CHANGELOG.md | 2 ++ build.gradle | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5094ac2..38907d6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] + +## [0.7.1] - 2020-12-21 ### Added - Accept group invitation with `updateGroup -g GROUP_ID` - Decline group invitation with `quitGroup -g GROUP_ID` diff --git a/build.gradle b/build.gradle index 8b097612..48b57a4f 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ targetCompatibility = JavaVersion.VERSION_11 mainClassName = 'org.asamk.signal.Main' -version = '0.7.0' +version = '0.7.1' compileJava.options.encoding = 'UTF-8' -- 2.51.0 From 548c313b4ca373559c14f3746d31c0b6cb9a721b Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 23 Dec 2020 00:18:21 +0100 Subject: [PATCH 02/16] Download quote attachment thumbnails and slightly improve the quote output --- .../org/asamk/signal/ReceiveMessageHandler.java | 6 +++--- .../java/org/asamk/signal/manager/Manager.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 5925b2b8..7ef89d19 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -452,9 +452,9 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (quote.getAttachments().size() > 0) { System.out.println(" Attachments: "); for (SignalServiceDataMessage.Quote.QuotedAttachment attachment : quote.getAttachments()) { - System.out.println(" Filename: " + attachment.getFileName()); - System.out.println(" Type: " + attachment.getContentType()); - System.out.println(" Thumbnail:"); + System.out.println(" - Filename: " + attachment.getFileName()); + System.out.println(" Type: " + attachment.getContentType()); + System.out.println(" Thumbnail:"); if (attachment.getThumbnail() != null) { printAttachment(attachment.getThumbnail()); } diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index ab063d1b..8a46846f 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -1690,6 +1690,23 @@ public class Manager implements Closeable { } } } + if (message.getQuote().isPresent()) { + final SignalServiceDataMessage.Quote quote = message.getQuote().get(); + + for (SignalServiceDataMessage.Quote.QuotedAttachment quotedAttachment : quote.getAttachments()) { + final SignalServiceAttachment attachment = quotedAttachment.getThumbnail(); + if (attachment != null && attachment.isPointer()) { + try { + retrieveAttachment(attachment.asPointer()); + } catch (IOException | InvalidMessageException | MissingConfigurationException e) { + System.err.println("Failed to retrieve attachment (" + + attachment.asPointer().getRemoteId() + + "): " + + e.getMessage()); + } + } + } + } if (message.getSticker().isPresent()) { final SignalServiceDataMessage.Sticker messageSticker = message.getSticker().get(); Sticker sticker = account.getStickerStore().getSticker(messageSticker.getPackId()); -- 2.51.0 From 58db3cbd53f3faec94ddfcd5e029865a380e6242 Mon Sep 17 00:00:00 2001 From: Atomic-Bean <75401809+Atomic-Bean@users.noreply.github.com> Date: Wed, 23 Dec 2020 20:53:40 +1030 Subject: [PATCH 03/16] Quotes, Mentions and Reactions in non-daemon JSON mode (#389) * Added support for quotes, mentions and reactions in non-daemon JSON output --- .../signal/JsonReceiveMessageHandler.java | 2 +- .../asamk/signal/ReceiveMessageHandler.java | 31 +++++++++----- .../asamk/signal/json/JsonDataMessage.java | 27 +++++++++++- .../org/asamk/signal/json/JsonMention.java | 22 ++++++++++ .../signal/json/JsonMessageEnvelope.java | 7 ++-- .../java/org/asamk/signal/json/JsonQuote.java | 42 +++++++++++++++++++ .../signal/json/JsonQuotedAttachment.java | 21 ++++++++++ .../org/asamk/signal/json/JsonReaction.java | 19 +++++++++ .../signal/json/JsonSyncDataMessage.java | 5 ++- .../asamk/signal/json/JsonSyncMessage.java | 5 ++- 10 files changed, 162 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/asamk/signal/json/JsonMention.java create mode 100644 src/main/java/org/asamk/signal/json/JsonQuote.java create mode 100644 src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java create mode 100644 src/main/java/org/asamk/signal/json/JsonReaction.java diff --git a/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java index dfe51fe7..363fc304 100644 --- a/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java @@ -35,7 +35,7 @@ public class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler result.putPOJO("error", new JsonError(exception)); } if (envelope != null) { - result.putPOJO("envelope", new JsonMessageEnvelope(envelope, content)); + result.putPOJO("envelope", new JsonMessageEnvelope(envelope, content, m)); } try { jsonProcessor.writeValue(System.out, result); diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 7ef89d19..f961fa1f 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -447,8 +447,14 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (message.getQuote().isPresent()) { SignalServiceDataMessage.Quote quote = message.getQuote().get(); System.out.println("Quote: (" + quote.getId() + ")"); - System.out.println(" Author: " + quote.getAuthor().getLegacyIdentifier()); + System.out.println(" Author: " + m.resolveSignalServiceAddress(quote.getAuthor()).getLegacyIdentifier()); System.out.println(" Text: " + quote.getText()); + if (quote.getMentions().size() > 0) { + System.out.println(" Mentions: "); + for (SignalServiceDataMessage.Mention mention : quote.getMentions()) { + printMention(mention, m); + } + } if (quote.getAttachments().size() > 0) { System.out.println(" Attachments: "); for (SignalServiceDataMessage.Quote.QuotedAttachment attachment : quote.getAttachments()) { @@ -467,16 +473,9 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { System.out.println("Remote delete message: timestamp = " + remoteDelete.getTargetSentTimestamp()); } if (message.getMentions().isPresent()) { - final List mentions = message.getMentions().get(); System.out.println("Mentions: "); - for (SignalServiceDataMessage.Mention mention : mentions) { - System.out.println("- " - + mention.getUuid() - + ": " - + mention.getStart() - + " (length: " - + mention.getLength() - + ")"); + for (SignalServiceDataMessage.Mention mention : message.getMentions().get()) { + printMention(mention, m); } } @@ -488,6 +487,18 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } + private void printMention(SignalServiceDataMessage.Mention mention, Manager m) { + System.out.println("- " + + m.resolveSignalServiceAddress( + new SignalServiceAddress(mention.getUuid(), null) + ).getLegacyIdentifier() + + ": " + + mention.getStart() + + " (length: " + + mention.getLength() + + ")"); + } + private void printAttachment(SignalServiceAttachment attachment) { System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + ( attachment.isStream() ? "Stream" : "" diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index 653a59e6..957e3a79 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -1,6 +1,7 @@ package org.asamk.signal.json; import org.asamk.Signal; +import org.asamk.signal.manager.Manager; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; @@ -15,10 +16,14 @@ class JsonDataMessage { long timestamp; String message; int expiresInSeconds; + + JsonReaction reaction; + JsonQuote quote; + List mentions; List attachments; JsonGroupInfo groupInfo; - JsonDataMessage(SignalServiceDataMessage dataMessage) { + JsonDataMessage(SignalServiceDataMessage dataMessage, Manager m) { this.timestamp = dataMessage.getTimestamp(); if (dataMessage.getGroupContext().isPresent()) { if (dataMessage.getGroupContext().get().getGroupV1().isPresent()) { @@ -33,6 +38,20 @@ class JsonDataMessage { this.message = dataMessage.getBody().get(); } this.expiresInSeconds = dataMessage.getExpiresInSeconds(); + if (dataMessage.getReaction().isPresent()) { + this.reaction = new JsonReaction(dataMessage.getReaction().get(), m); + } + if (dataMessage.getQuote().isPresent()) { + this.quote = new JsonQuote(dataMessage.getQuote().get(), m); + } + if (dataMessage.getMentions().isPresent()) { + this.mentions = new ArrayList<>(dataMessage.getMentions().get().size()); + for (SignalServiceDataMessage.Mention mention : dataMessage.getMentions().get()) { + this.mentions.add(new JsonMention(mention, m)); + } + } else { + this.mentions = new ArrayList<>(); + } if (dataMessage.getAttachments().isPresent()) { this.attachments = new ArrayList<>(dataMessage.getAttachments().get().size()); for (SignalServiceAttachment attachment : dataMessage.getAttachments().get()) { @@ -47,6 +66,9 @@ class JsonDataMessage { timestamp = messageReceived.getTimestamp(); message = messageReceived.getMessage(); groupInfo = new JsonGroupInfo(messageReceived.getGroupId()); + reaction = null; // TODO Replace these 3 with the proper commands + quote = null; + mentions = null; attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList()); } @@ -54,6 +76,9 @@ class JsonDataMessage { timestamp = messageReceived.getTimestamp(); message = messageReceived.getMessage(); groupInfo = new JsonGroupInfo(messageReceived.getGroupId()); + reaction = null; // TODO Replace these 3 with the proper commands + quote = null; + mentions = null; attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList()); } } diff --git a/src/main/java/org/asamk/signal/json/JsonMention.java b/src/main/java/org/asamk/signal/json/JsonMention.java new file mode 100644 index 00000000..80683842 --- /dev/null +++ b/src/main/java/org/asamk/signal/json/JsonMention.java @@ -0,0 +1,22 @@ +package org.asamk.signal.json; + +import org.asamk.signal.manager.Manager; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; + +public class JsonMention { + + String name; + int start; + int length; + + JsonMention(SignalServiceDataMessage.Mention mention, Manager m) { + this.name = m.resolveSignalServiceAddress( + new SignalServiceAddress(mention.getUuid(), null) + ).getLegacyIdentifier(); + this.start = mention.getStart(); + this.length = mention.getLength(); + + } + +} diff --git a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java index 5e5e6a33..787f62e2 100644 --- a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java +++ b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java @@ -1,6 +1,7 @@ package org.asamk.signal.json; import org.asamk.Signal; +import org.asamk.signal.manager.Manager; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -18,7 +19,7 @@ public class JsonMessageEnvelope { JsonCallMessage callMessage; JsonReceiptMessage receiptMessage; - public JsonMessageEnvelope(SignalServiceEnvelope envelope, SignalServiceContent content) { + public JsonMessageEnvelope(SignalServiceEnvelope envelope, SignalServiceContent content, Manager m) { if (!envelope.isUnidentifiedSender() && envelope.hasSource()) { SignalServiceAddress source = envelope.getSourceAddress(); this.source = source.getLegacyIdentifier(); @@ -35,10 +36,10 @@ public class JsonMessageEnvelope { this.sourceDevice = content.getSenderDevice(); } if (content.getDataMessage().isPresent()) { - this.dataMessage = new JsonDataMessage(content.getDataMessage().get()); + this.dataMessage = new JsonDataMessage(content.getDataMessage().get(), m); } if (content.getSyncMessage().isPresent()) { - this.syncMessage = new JsonSyncMessage(content.getSyncMessage().get()); + this.syncMessage = new JsonSyncMessage(content.getSyncMessage().get(), m); } if (content.getCallMessage().isPresent()) { this.callMessage = new JsonCallMessage(content.getCallMessage().get()); diff --git a/src/main/java/org/asamk/signal/json/JsonQuote.java b/src/main/java/org/asamk/signal/json/JsonQuote.java new file mode 100644 index 00000000..9a740582 --- /dev/null +++ b/src/main/java/org/asamk/signal/json/JsonQuote.java @@ -0,0 +1,42 @@ +package org.asamk.signal.json; + +import org.asamk.signal.manager.Manager; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; + +import java.util.ArrayList; +import java.util.List; + +public class JsonQuote { + + long id; + String author; + String text; + + List mentions; + List attachments; + + JsonQuote(SignalServiceDataMessage.Quote quote, Manager m) { + this.id = quote.getId(); + this.author = m.resolveSignalServiceAddress(quote.getAuthor()).getLegacyIdentifier(); + this.text = quote.getText(); + + if (quote.getMentions().size() > 0) { + this.mentions = new ArrayList<>(quote.getMentions().size()); + + for (SignalServiceDataMessage.Mention quotedMention: quote.getMentions()){ + this.mentions.add(new JsonMention(quotedMention, m)); + } + } + + if (quote.getAttachments().size() > 0) { + this.attachments = new ArrayList<>(quote.getAttachments().size()); + + for (SignalServiceDataMessage.Quote.QuotedAttachment quotedAttachment : quote.getAttachments()) { + this.attachments.add(new JsonQuotedAttachment(quotedAttachment)); + } + } else { + this.attachments = new ArrayList<>(); + } + } + +} diff --git a/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java b/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java new file mode 100644 index 00000000..1aae3104 --- /dev/null +++ b/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java @@ -0,0 +1,21 @@ +package org.asamk.signal.json; + +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; + +public class JsonQuotedAttachment { + + String contentType; + String filename; + JsonAttachment thumbnail; + + JsonQuotedAttachment(SignalServiceDataMessage.Quote.QuotedAttachment quotedAttachment) { + contentType = quotedAttachment.getContentType(); + filename = quotedAttachment.getFileName(); + if (quotedAttachment.getThumbnail() != null) { + thumbnail = new JsonAttachment(quotedAttachment.getThumbnail()); + } + else { + thumbnail = null; + } + } +} diff --git a/src/main/java/org/asamk/signal/json/JsonReaction.java b/src/main/java/org/asamk/signal/json/JsonReaction.java new file mode 100644 index 00000000..5e978fe0 --- /dev/null +++ b/src/main/java/org/asamk/signal/json/JsonReaction.java @@ -0,0 +1,19 @@ +package org.asamk.signal.json; + +import org.asamk.signal.manager.Manager; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Reaction; + +public class JsonReaction { + + String emoji; + String targetAuthor; + long targetSentTimestamp; + boolean isRemove; + + JsonReaction(Reaction reaction, Manager m) { + this.emoji = reaction.getEmoji(); + this.targetAuthor = m.resolveSignalServiceAddress(reaction.getTargetAuthor()).getLegacyIdentifier(); + this.targetSentTimestamp = reaction.getTargetSentTimestamp(); + this.isRemove = reaction.isRemove(); + } +} diff --git a/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java index c6571a93..7ea75bbd 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java @@ -1,14 +1,15 @@ package org.asamk.signal.json; import org.asamk.Signal; +import org.asamk.signal.manager.Manager; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; class JsonSyncDataMessage extends JsonDataMessage { String destination; - JsonSyncDataMessage(SentTranscriptMessage transcriptMessage) { - super(transcriptMessage.getMessage()); + JsonSyncDataMessage(SentTranscriptMessage transcriptMessage, Manager m) { + super(transcriptMessage.getMessage(), m); if (transcriptMessage.getDestination().isPresent()) { this.destination = transcriptMessage.getDestination().get().getLegacyIdentifier(); } diff --git a/src/main/java/org/asamk/signal/json/JsonSyncMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java index 31c39a3f..f29bc02e 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java @@ -1,6 +1,7 @@ package org.asamk.signal.json; import org.asamk.Signal; +import org.asamk.signal.manager.Manager; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -21,9 +22,9 @@ class JsonSyncMessage { List readMessages; JsonSyncMessageType type; - JsonSyncMessage(SignalServiceSyncMessage syncMessage) { + JsonSyncMessage(SignalServiceSyncMessage syncMessage, Manager m) { if (syncMessage.getSent().isPresent()) { - this.sentMessage = new JsonSyncDataMessage(syncMessage.getSent().get()); + this.sentMessage = new JsonSyncDataMessage(syncMessage.getSent().get(), m); } if (syncMessage.getBlockedList().isPresent()) { this.blockedNumbers = new ArrayList<>(syncMessage.getBlockedList().get().getAddresses().size()); -- 2.51.0 From 67f62947c6d4cc5f4b9d4334bfe55b753bc6a12c Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 23 Dec 2020 11:24:07 +0100 Subject: [PATCH 04/16] Add null check and change some formatting --- .../asamk/signal/ReceiveMessageHandler.java | 13 +++------- .../asamk/signal/json/JsonDataMessage.java | 24 +++++++++---------- .../org/asamk/signal/json/JsonMention.java | 7 ++---- .../java/org/asamk/signal/json/JsonQuote.java | 22 ++++++++--------- .../signal/json/JsonQuotedAttachment.java | 3 +-- 5 files changed, 28 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index f961fa1f..711f503d 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -449,7 +449,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { System.out.println("Quote: (" + quote.getId() + ")"); System.out.println(" Author: " + m.resolveSignalServiceAddress(quote.getAuthor()).getLegacyIdentifier()); System.out.println(" Text: " + quote.getText()); - if (quote.getMentions().size() > 0) { + if (quote.getMentions() != null && quote.getMentions().size() > 0) { System.out.println(" Mentions: "); for (SignalServiceDataMessage.Mention mention : quote.getMentions()) { printMention(mention, m); @@ -488,15 +488,8 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } private void printMention(SignalServiceDataMessage.Mention mention, Manager m) { - System.out.println("- " - + m.resolveSignalServiceAddress( - new SignalServiceAddress(mention.getUuid(), null) - ).getLegacyIdentifier() - + ": " - + mention.getStart() - + " (length: " - + mention.getLength() - + ")"); + System.out.println("- " + m.resolveSignalServiceAddress(new SignalServiceAddress(mention.getUuid(), null)) + .getLegacyIdentifier() + ": " + mention.getStart() + " (length: " + mention.getLength() + ")"); } private void printAttachment(SignalServiceAttachment attachment) { diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index 957e3a79..57201eda 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -2,12 +2,10 @@ package org.asamk.signal.json; import org.asamk.Signal; import org.asamk.signal.manager.Manager; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -45,20 +43,22 @@ class JsonDataMessage { this.quote = new JsonQuote(dataMessage.getQuote().get(), m); } if (dataMessage.getMentions().isPresent()) { - this.mentions = new ArrayList<>(dataMessage.getMentions().get().size()); - for (SignalServiceDataMessage.Mention mention : dataMessage.getMentions().get()) { - this.mentions.add(new JsonMention(mention, m)); - } + this.mentions = dataMessage.getMentions() + .get() + .stream() + .map(mention -> new JsonMention(mention, m)) + .collect(Collectors.toList()); } else { - this.mentions = new ArrayList<>(); + this.mentions = List.of(); } if (dataMessage.getAttachments().isPresent()) { - this.attachments = new ArrayList<>(dataMessage.getAttachments().get().size()); - for (SignalServiceAttachment attachment : dataMessage.getAttachments().get()) { - this.attachments.add(new JsonAttachment(attachment)); - } + this.attachments = dataMessage.getAttachments() + .get() + .stream() + .map(JsonAttachment::new) + .collect(Collectors.toList()); } else { - this.attachments = new ArrayList<>(); + this.attachments = List.of(); } } diff --git a/src/main/java/org/asamk/signal/json/JsonMention.java b/src/main/java/org/asamk/signal/json/JsonMention.java index 80683842..302128ed 100644 --- a/src/main/java/org/asamk/signal/json/JsonMention.java +++ b/src/main/java/org/asamk/signal/json/JsonMention.java @@ -11,12 +11,9 @@ public class JsonMention { int length; JsonMention(SignalServiceDataMessage.Mention mention, Manager m) { - this.name = m.resolveSignalServiceAddress( - new SignalServiceAddress(mention.getUuid(), null) - ).getLegacyIdentifier(); + this.name = m.resolveSignalServiceAddress(new SignalServiceAddress(mention.getUuid(), null)) + .getLegacyIdentifier(); this.start = mention.getStart(); this.length = mention.getLength(); - } - } diff --git a/src/main/java/org/asamk/signal/json/JsonQuote.java b/src/main/java/org/asamk/signal/json/JsonQuote.java index 9a740582..10cd0bf4 100644 --- a/src/main/java/org/asamk/signal/json/JsonQuote.java +++ b/src/main/java/org/asamk/signal/json/JsonQuote.java @@ -5,6 +5,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class JsonQuote { @@ -20,23 +21,20 @@ public class JsonQuote { this.author = m.resolveSignalServiceAddress(quote.getAuthor()).getLegacyIdentifier(); this.text = quote.getText(); - if (quote.getMentions().size() > 0) { - this.mentions = new ArrayList<>(quote.getMentions().size()); - - for (SignalServiceDataMessage.Mention quotedMention: quote.getMentions()){ - this.mentions.add(new JsonMention(quotedMention, m)); - } + if (quote.getMentions() != null && quote.getMentions().size() > 0) { + this.mentions = quote.getMentions() + .stream() + .map(quotedMention -> new JsonMention(quotedMention, m)) + .collect(Collectors.toList()); } if (quote.getAttachments().size() > 0) { - this.attachments = new ArrayList<>(quote.getAttachments().size()); - - for (SignalServiceDataMessage.Quote.QuotedAttachment quotedAttachment : quote.getAttachments()) { - this.attachments.add(new JsonQuotedAttachment(quotedAttachment)); - } + this.attachments = quote.getAttachments() + .stream() + .map(JsonQuotedAttachment::new) + .collect(Collectors.toList()); } else { this.attachments = new ArrayList<>(); } } - } diff --git a/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java b/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java index 1aae3104..bcbbe2a5 100644 --- a/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java +++ b/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java @@ -13,8 +13,7 @@ public class JsonQuotedAttachment { filename = quotedAttachment.getFileName(); if (quotedAttachment.getThumbnail() != null) { thumbnail = new JsonAttachment(quotedAttachment.getThumbnail()); - } - else { + } else { thumbnail = null; } } -- 2.51.0 From 9942d967a4a83230aadd21c86344c6f1ac246611 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 24 Dec 2020 16:36:47 +0100 Subject: [PATCH 05/16] Refactor to use GroupId class to wrap the byte array Helps distinguish between group v1 and v2 ids --- .../signal/JsonDbusReceiveMessageHandler.java | 21 +--- .../asamk/signal/ReceiveMessageHandler.java | 14 ++- .../asamk/signal/commands/BlockCommand.java | 5 +- .../signal/commands/JoinGroupCommand.java | 10 +- .../signal/commands/ListGroupsCommand.java | 5 +- .../signal/commands/QuitGroupCommand.java | 5 +- .../asamk/signal/commands/SendCommand.java | 4 +- .../signal/commands/SendReactionCommand.java | 5 +- .../asamk/signal/commands/UnblockCommand.java | 5 +- .../signal/commands/UpdateGroupCommand.java | 4 +- .../org/asamk/signal/dbus/DbusSignalImpl.java | 19 +-- .../org/asamk/signal/json/JsonGroupInfo.java | 2 +- .../org/asamk/signal/manager/GroupId.java | 63 ++++++++++ .../manager/GroupIdFormatException.java | 8 ++ .../org/asamk/signal/manager/GroupIdV1.java | 14 +++ .../org/asamk/signal/manager/GroupIdV2.java | 14 +++ .../manager/GroupNotFoundException.java | 6 +- .../org/asamk/signal/manager/GroupUtils.java | 31 ++++- .../asamk/signal/manager/HandleAction.java | 27 ++-- .../org/asamk/signal/manager/KeyUtils.java | 4 - .../org/asamk/signal/manager/Manager.java | 116 +++++++++--------- .../manager/NotAGroupMemberException.java | 6 +- .../signal/manager/helper/GroupHelper.java | 4 +- .../asamk/signal/storage/SignalAccount.java | 3 +- .../signal/storage/groups/GroupInfo.java | 10 +- .../signal/storage/groups/GroupInfoV1.java | 61 ++++++--- .../signal/storage/groups/GroupInfoV2.java | 11 +- .../signal/storage/groups/JsonGroupStore.java | 76 +++++------- .../org/asamk/signal/util/ErrorUtils.java | 1 + .../signal/util/GroupIdFormatException.java | 10 -- src/main/java/org/asamk/signal/util/Util.java | 12 +- 31 files changed, 353 insertions(+), 223 deletions(-) create mode 100644 src/main/java/org/asamk/signal/manager/GroupId.java create mode 100644 src/main/java/org/asamk/signal/manager/GroupIdFormatException.java create mode 100644 src/main/java/org/asamk/signal/manager/GroupIdV1.java create mode 100644 src/main/java/org/asamk/signal/manager/GroupIdV2.java delete mode 100644 src/main/java/org/asamk/signal/util/GroupIdFormatException.java diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index 41b91a48..50eb9f9b 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -65,7 +65,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { } else if (content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); - byte[] groupId = getGroupId(m, message); + byte[] groupId = getGroupId(message); if (!message.isEndSession() && ( groupId == null || message.getGroupContext().get().getGroupV1Type() == null @@ -91,7 +91,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { .getGroupContext() .isPresent()) { SignalServiceDataMessage message = transcript.getMessage(); - byte[] groupId = getGroupId(m, message); + byte[] groupId = getGroupId(message); try { conn.sendMessage(new Signal.SyncMessageReceived(objectPath, @@ -112,20 +112,9 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { } } - private static byte[] getGroupId(final Manager m, final SignalServiceDataMessage message) { - byte[] groupId; - if (message.getGroupContext().isPresent()) { - if (message.getGroupContext().get().getGroupV1().isPresent()) { - groupId = message.getGroupContext().get().getGroupV1().get().getGroupId(); - } else if (message.getGroupContext().get().getGroupV2().isPresent()) { - groupId = GroupUtils.getGroupId(message.getGroupContext().get().getGroupV2().get().getMasterKey()); - } else { - groupId = null; - } - } else { - groupId = null; - } - return groupId; + private static byte[] getGroupId(final SignalServiceDataMessage message) { + return message.getGroupContext().isPresent() ? GroupUtils.getGroupId(message.getGroupContext().get()) + .serialize() : null; } static private List getAttachments(SignalServiceDataMessage message, Manager m) { diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 711f503d..99010e13 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -1,5 +1,6 @@ package org.asamk.signal; +import org.asamk.signal.manager.GroupId; import org.asamk.signal.manager.GroupUtils; import org.asamk.signal.manager.Manager; import org.asamk.signal.storage.contacts.ContactInfo; @@ -328,8 +329,9 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { System.out.println(" - Timestamp: " + DateUtils.formatTimestamp(typingMessage.getTimestamp())); if (typingMessage.getGroupId().isPresent()) { System.out.println(" - Group Info:"); - System.out.println(" Id: " + Base64.encodeBytes(typingMessage.getGroupId().get())); - GroupInfo group = m.getGroup(typingMessage.getGroupId().get()); + final GroupId groupId = GroupId.unknownVersion(typingMessage.getGroupId().get()); + System.out.println(" Id: " + groupId.toBase64()); + GroupInfo group = m.getGroup(groupId); if (group != null) { System.out.println(" Name: " + group.getTitle()); } else { @@ -356,13 +358,14 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (message.getGroupContext().isPresent()) { System.out.println("Group info:"); final SignalServiceGroupContext groupContext = message.getGroupContext().get(); + final GroupId groupId = GroupUtils.getGroupId(groupContext); if (groupContext.getGroupV1().isPresent()) { SignalServiceGroup groupInfo = groupContext.getGroupV1().get(); - System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId())); + System.out.println(" Id: " + groupId.toBase64()); if (groupInfo.getType() == SignalServiceGroup.Type.UPDATE && groupInfo.getName().isPresent()) { System.out.println(" Name: " + groupInfo.getName().get()); } else { - GroupInfo group = m.getGroup(groupInfo.getGroupId()); + GroupInfo group = m.getGroup(groupId); if (group != null) { System.out.println(" Name: " + group.getTitle()); } else { @@ -381,8 +384,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } else if (groupContext.getGroupV2().isPresent()) { final SignalServiceGroupV2 groupInfo = groupContext.getGroupV2().get(); - byte[] groupId = GroupUtils.getGroupId(groupInfo.getMasterKey()); - System.out.println(" Id: " + Base64.encodeBytes(groupId)); + System.out.println(" Id: " + groupId.toBase64()); GroupInfo group = m.getGroup(groupId); if (group != null) { System.out.println(" Name: " + group.getTitle()); diff --git a/src/main/java/org/asamk/signal/commands/BlockCommand.java b/src/main/java/org/asamk/signal/commands/BlockCommand.java index 95c3738c..2a9bc4e9 100644 --- a/src/main/java/org/asamk/signal/commands/BlockCommand.java +++ b/src/main/java/org/asamk/signal/commands/BlockCommand.java @@ -3,9 +3,10 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.GroupId; +import org.asamk.signal.manager.GroupIdFormatException; import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; -import org.asamk.signal.util.GroupIdFormatException; import org.asamk.signal.util.Util; import org.whispersystems.signalservice.api.util.InvalidNumberException; @@ -36,7 +37,7 @@ public class BlockCommand implements LocalCommand { if (ns.getList("group") != null) { for (String groupIdString : ns.getList("group")) { try { - byte[] groupId = Util.decodeGroupId(groupIdString); + GroupId groupId = Util.decodeGroupId(groupIdString); m.setGroupBlocked(groupId, true); } catch (GroupIdFormatException | GroupNotFoundException e) { System.err.println(e.getMessage()); diff --git a/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java b/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java index 62b996cf..8438e1fa 100644 --- a/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java @@ -4,6 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.Signal; +import org.asamk.signal.manager.GroupId; import org.asamk.signal.manager.GroupInviteLinkUrl; import org.asamk.signal.manager.Manager; import org.freedesktop.dbus.exceptions.DBusExecutionException; @@ -11,7 +12,6 @@ import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException; -import org.whispersystems.util.Base64; import java.io.IOException; import java.util.List; @@ -52,12 +52,12 @@ public class JoinGroupCommand implements LocalCommand { } try { - final Pair> results = m.joinGroup(linkUrl); - byte[] newGroupId = results.first(); + final Pair> results = m.joinGroup(linkUrl); + GroupId newGroupId = results.first(); if (!m.getGroup(newGroupId).isMember(m.getSelfAddress())) { - System.out.println("Requested to join group \"" + Base64.encodeBytes(newGroupId) + "\""); + System.out.println("Requested to join group \"" + newGroupId.toBase64() + "\""); } else { - System.out.println("Joined group \"" + Base64.encodeBytes(newGroupId) + "\""); + System.out.println("Joined group \"" + newGroupId.toBase64() + "\""); } return handleTimestampAndSendMessageResults(0, results.second()); } catch (AssertionError e) { diff --git a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java index e7844fda..4d1032a2 100644 --- a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java @@ -8,7 +8,6 @@ import org.asamk.signal.manager.GroupInviteLinkUrl; import org.asamk.signal.manager.Manager; import org.asamk.signal.storage.groups.GroupInfo; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.util.Base64; import java.util.List; import java.util.Set; @@ -40,7 +39,7 @@ public class ListGroupsCommand implements LocalCommand { System.out.println(String.format( "Id: %s Name: %s Active: %s Blocked: %b Members: %s Pending members: %s Requesting members: %s Link: %s", - Base64.encodeBytes(group.groupId), + group.getGroupId().toBase64(), group.getTitle(), group.isMember(m.getSelfAddress()), group.isBlocked(), @@ -50,7 +49,7 @@ public class ListGroupsCommand implements LocalCommand { groupInviteLink == null ? '-' : groupInviteLink.getUrl())); } else { System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b", - Base64.encodeBytes(group.groupId), + group.getGroupId().toBase64(), group.getTitle(), group.isMember(m.getSelfAddress()), group.isBlocked())); diff --git a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java index 20d06eba..efc63f8f 100644 --- a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java @@ -3,10 +3,11 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.GroupId; +import org.asamk.signal.manager.GroupIdFormatException; import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotAGroupMemberException; -import org.asamk.signal.util.GroupIdFormatException; import org.asamk.signal.util.Util; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.messages.SendMessageResult; @@ -36,7 +37,7 @@ public class QuitGroupCommand implements LocalCommand { } try { - final byte[] groupId = Util.decodeGroupId(ns.getString("group")); + final GroupId groupId = Util.decodeGroupId(ns.getString("group")); final Pair> results = m.sendQuitGroupMessage(groupId); return handleTimestampAndSendMessageResults(results.first(), results.second()); } catch (IOException e) { diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 551cf938..04b06434 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -5,7 +5,7 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.Signal; -import org.asamk.signal.util.GroupIdFormatException; +import org.asamk.signal.manager.GroupIdFormatException; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; import org.freedesktop.dbus.exceptions.DBusExecutionException; @@ -79,7 +79,7 @@ public class SendCommand implements DbusCommand { if (ns.getString("group") != null) { byte[] groupId; try { - groupId = Util.decodeGroupId(ns.getString("group")); + groupId = Util.decodeGroupId(ns.getString("group")).serialize(); } catch (GroupIdFormatException e) { handleGroupIdFormatException(e); return 1; diff --git a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java index 000a1349..345c9180 100644 --- a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java @@ -4,10 +4,11 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.GroupId; +import org.asamk.signal.manager.GroupIdFormatException; import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotAGroupMemberException; -import org.asamk.signal.util.GroupIdFormatException; import org.asamk.signal.util.Util; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.messages.SendMessageResult; @@ -65,7 +66,7 @@ public class SendReactionCommand implements LocalCommand { try { final Pair> results; if (ns.getString("group") != null) { - byte[] groupId = Util.decodeGroupId(ns.getString("group")); + GroupId groupId = Util.decodeGroupId(ns.getString("group")); results = m.sendGroupMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, groupId); } else { results = m.sendMessageReaction(emoji, diff --git a/src/main/java/org/asamk/signal/commands/UnblockCommand.java b/src/main/java/org/asamk/signal/commands/UnblockCommand.java index b4f6cc3b..73e578ac 100644 --- a/src/main/java/org/asamk/signal/commands/UnblockCommand.java +++ b/src/main/java/org/asamk/signal/commands/UnblockCommand.java @@ -3,9 +3,10 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.manager.GroupId; +import org.asamk.signal.manager.GroupIdFormatException; import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; -import org.asamk.signal.util.GroupIdFormatException; import org.asamk.signal.util.Util; import org.whispersystems.signalservice.api.util.InvalidNumberException; @@ -36,7 +37,7 @@ public class UnblockCommand implements LocalCommand { if (ns.getList("group") != null) { for (String groupIdString : ns.getList("group")) { try { - byte[] groupId = Util.decodeGroupId(groupIdString); + GroupId groupId = Util.decodeGroupId(groupIdString); m.setGroupBlocked(groupId, false); } catch (GroupIdFormatException | GroupNotFoundException e) { System.err.println(e.getMessage()); diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java index 4216fd9b..dae06b86 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -4,7 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.Signal; -import org.asamk.signal.util.GroupIdFormatException; +import org.asamk.signal.manager.GroupIdFormatException; import org.asamk.signal.util.Util; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.whispersystems.util.Base64; @@ -35,7 +35,7 @@ public class UpdateGroupCommand implements DbusCommand { byte[] groupId = null; if (ns.getString("group") != null) { try { - groupId = Util.decodeGroupId(ns.getString("group")); + groupId = Util.decodeGroupId(ns.getString("group")).serialize(); } catch (GroupIdFormatException e) { handleGroupIdFormatException(e); return 1; diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 396063f2..cbb72835 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -2,6 +2,7 @@ package org.asamk.signal.dbus; import org.asamk.Signal; import org.asamk.signal.manager.AttachmentInvalidException; +import org.asamk.signal.manager.GroupId; import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotAGroupMemberException; @@ -92,7 +93,9 @@ public class DbusSignalImpl implements Signal { @Override public long sendGroupMessage(final String message, final List attachments, final byte[] groupId) { try { - Pair> results = m.sendGroupMessage(message, attachments, groupId); + Pair> results = m.sendGroupMessage(message, + attachments, + GroupId.unknownVersion(groupId)); checkSendMessageResults(results.first(), results.second()); return results.first(); } catch (IOException e) { @@ -134,7 +137,7 @@ public class DbusSignalImpl implements Signal { @Override public void setGroupBlocked(final byte[] groupId, final boolean blocked) { try { - m.setGroupBlocked(groupId, blocked); + m.setGroupBlocked(GroupId.unknownVersion(groupId), blocked); } catch (GroupNotFoundException e) { throw new Error.GroupNotFound(e.getMessage()); } @@ -145,14 +148,14 @@ public class DbusSignalImpl implements Signal { List groups = m.getGroups(); List ids = new ArrayList<>(groups.size()); for (GroupInfo group : groups) { - ids.add(group.groupId); + ids.add(group.getGroupId().serialize()); } return ids; } @Override public String getGroupName(final byte[] groupId) { - GroupInfo group = m.getGroup(groupId); + GroupInfo group = m.getGroup(GroupId.unknownVersion(groupId)); if (group == null) { return ""; } else { @@ -162,7 +165,7 @@ public class DbusSignalImpl implements Signal { @Override public List getGroupMembers(final byte[] groupId) { - GroupInfo group = m.getGroup(groupId); + GroupInfo group = m.getGroup(GroupId.unknownVersion(groupId)); if (group == null) { return Collections.emptyList(); } else { @@ -189,9 +192,11 @@ public class DbusSignalImpl implements Signal { if (avatar.isEmpty()) { avatar = null; } - final Pair> results = m.updateGroup(groupId, name, members, avatar); + final Pair> results = m.updateGroup(groupId == null + ? null + : GroupId.unknownVersion(groupId), name, members, avatar); checkSendMessageResults(0, results.second()); - return results.first(); + return results.first().serialize(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException e) { diff --git a/src/main/java/org/asamk/signal/json/JsonGroupInfo.java b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java index 970cde52..9709be20 100644 --- a/src/main/java/org/asamk/signal/json/JsonGroupInfo.java +++ b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java @@ -31,7 +31,7 @@ class JsonGroupInfo { } JsonGroupInfo(SignalServiceGroupV2 groupInfo) { - this.groupId = Base64.encodeBytes(GroupUtils.getGroupId(groupInfo.getMasterKey())); + this.groupId = GroupUtils.getGroupIdV2(groupInfo.getMasterKey()).toBase64(); this.type = groupInfo.hasSignedGroupChange() ? "UPDATE" : "DELIVER"; } diff --git a/src/main/java/org/asamk/signal/manager/GroupId.java b/src/main/java/org/asamk/signal/manager/GroupId.java new file mode 100644 index 00000000..34e18e8e --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/GroupId.java @@ -0,0 +1,63 @@ +package org.asamk.signal.manager; + +import org.whispersystems.util.Base64; + +import java.util.Arrays; + +public abstract class GroupId { + + private final byte[] id; + + public static GroupIdV1 v1(byte[] id) { + return new GroupIdV1(id); + } + + public static GroupIdV2 v2(byte[] id) { + return new GroupIdV2(id); + } + + public static GroupId unknownVersion(byte[] id) { + if (id.length == 16) { + return new GroupIdV1(id); + } else if (id.length == 32) { + return new GroupIdV2(id); + } + + throw new AssertionError("Invalid group id of size " + id.length); + } + + public static GroupId fromBase64(String id) throws GroupIdFormatException { + try { + return unknownVersion(java.util.Base64.getDecoder().decode(id)); + } catch (Throwable e) { + throw new GroupIdFormatException(id, e); + } + } + + public GroupId(final byte[] id) { + this.id = id; + } + + public byte[] serialize() { + return id; + } + + public String toBase64() { + return Base64.encodeBytes(id); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final GroupId groupId = (GroupId) o; + + return Arrays.equals(id, groupId.id); + } + + @Override + public int hashCode() { + return Arrays.hashCode(id); + } +} diff --git a/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java b/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java new file mode 100644 index 00000000..83afd15b --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager; + +public class GroupIdFormatException extends Exception { + + public GroupIdFormatException(String groupId, Throwable e) { + super("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage(), e); + } +} diff --git a/src/main/java/org/asamk/signal/manager/GroupIdV1.java b/src/main/java/org/asamk/signal/manager/GroupIdV1.java new file mode 100644 index 00000000..40862f07 --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/GroupIdV1.java @@ -0,0 +1,14 @@ +package org.asamk.signal.manager; + +import static org.asamk.signal.manager.KeyUtils.getSecretBytes; + +public class GroupIdV1 extends GroupId { + + public static GroupIdV1 createRandom() { + return new GroupIdV1(getSecretBytes(16)); + } + + public GroupIdV1(final byte[] id) { + super(id); + } +} diff --git a/src/main/java/org/asamk/signal/manager/GroupIdV2.java b/src/main/java/org/asamk/signal/manager/GroupIdV2.java new file mode 100644 index 00000000..b329be1d --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/GroupIdV2.java @@ -0,0 +1,14 @@ +package org.asamk.signal.manager; + +import java.util.Base64; + +public class GroupIdV2 extends GroupId { + + public static GroupIdV2 fromBase64(String groupId) { + return new GroupIdV2(Base64.getDecoder().decode(groupId)); + } + + public GroupIdV2(final byte[] id) { + super(id); + } +} diff --git a/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java b/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java index 0c0d6d2d..d7efa923 100644 --- a/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java +++ b/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java @@ -1,10 +1,8 @@ package org.asamk.signal.manager; -import org.whispersystems.util.Base64; - public class GroupNotFoundException extends Exception { - public GroupNotFoundException(byte[] groupId) { - super("Group not found: " + Base64.encodeBytes(groupId)); + public GroupNotFoundException(GroupId groupId) { + super("Group not found: " + groupId.toBase64()); } } diff --git a/src/main/java/org/asamk/signal/manager/GroupUtils.java b/src/main/java/org/asamk/signal/manager/GroupUtils.java index a0e95c7a..d86dfbe9 100644 --- a/src/main/java/org/asamk/signal/manager/GroupUtils.java +++ b/src/main/java/org/asamk/signal/manager/GroupUtils.java @@ -9,6 +9,7 @@ import org.signal.zkgroup.groups.GroupSecretParams; import org.whispersystems.libsignal.kdf.HKDFv3; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; +import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; public class GroupUtils { @@ -18,7 +19,7 @@ public class GroupUtils { ) { if (groupInfo instanceof GroupInfoV1) { SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER) - .withId(groupInfo.groupId) + .withId(groupInfo.getGroupId().serialize()) .build(); messageBuilder.asGroupMessage(group); } else { @@ -30,14 +31,34 @@ public class GroupUtils { } } - public static byte[] getGroupId(GroupMasterKey groupMasterKey) { + public static GroupId getGroupId(SignalServiceGroupContext context) { + if (context.getGroupV1().isPresent()) { + return GroupId.v1(context.getGroupV1().get().getGroupId()); + } else if (context.getGroupV2().isPresent()) { + return getGroupIdV2(context.getGroupV2().get().getMasterKey()); + } else { + return null; + } + } + + public static GroupIdV2 getGroupIdV2(GroupSecretParams groupSecretParams) { + return GroupId.v2(groupSecretParams.getPublicParams().getGroupIdentifier().serialize()); + } + + public static GroupIdV2 getGroupIdV2(GroupMasterKey groupMasterKey) { final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - return groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); + return getGroupIdV2(groupSecretParams); + } + + public static GroupIdV2 getGroupIdV2(GroupIdV1 groupIdV1) { + final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(deriveV2MigrationMasterKey( + groupIdV1)); + return getGroupIdV2(groupSecretParams); } - public static GroupMasterKey deriveV2MigrationMasterKey(byte[] groupIdV1) { + private static GroupMasterKey deriveV2MigrationMasterKey(GroupIdV1 groupIdV1) { try { - return new GroupMasterKey(new HKDFv3().deriveSecrets(groupIdV1, + return new GroupMasterKey(new HKDFv3().deriveSecrets(groupIdV1.serialize(), "GV2 Migration".getBytes(), GroupMasterKey.SIZE)); } catch (InvalidInputException e) { diff --git a/src/main/java/org/asamk/signal/manager/HandleAction.java b/src/main/java/org/asamk/signal/manager/HandleAction.java index 9bdd3885..aa25d8c5 100644 --- a/src/main/java/org/asamk/signal/manager/HandleAction.java +++ b/src/main/java/org/asamk/signal/manager/HandleAction.java @@ -2,7 +2,6 @@ package org.asamk.signal.manager; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import java.util.Arrays; import java.util.Objects; interface HandleAction { @@ -93,9 +92,9 @@ class SendSyncBlockedListAction implements HandleAction { class SendGroupInfoRequestAction implements HandleAction { private final SignalServiceAddress address; - private final byte[] groupId; + private final GroupIdV1 groupId; - public SendGroupInfoRequestAction(final SignalServiceAddress address, final byte[] groupId) { + public SendGroupInfoRequestAction(final SignalServiceAddress address, final GroupIdV1 groupId) { this.address = address; this.groupId = groupId; } @@ -109,14 +108,17 @@ class SendGroupInfoRequestAction implements HandleAction { public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; + final SendGroupInfoRequestAction that = (SendGroupInfoRequestAction) o; - return address.equals(that.address) && Arrays.equals(groupId, that.groupId); + + if (!address.equals(that.address)) return false; + return groupId.equals(that.groupId); } @Override public int hashCode() { - int result = Objects.hash(address); - result = 31 * result + Arrays.hashCode(groupId); + int result = address.hashCode(); + result = 31 * result + groupId.hashCode(); return result; } } @@ -124,9 +126,9 @@ class SendGroupInfoRequestAction implements HandleAction { class SendGroupUpdateAction implements HandleAction { private final SignalServiceAddress address; - private final byte[] groupId; + private final GroupIdV1 groupId; - public SendGroupUpdateAction(final SignalServiceAddress address, final byte[] groupId) { + public SendGroupUpdateAction(final SignalServiceAddress address, final GroupIdV1 groupId) { this.address = address; this.groupId = groupId; } @@ -140,14 +142,17 @@ class SendGroupUpdateAction implements HandleAction { public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; + final SendGroupUpdateAction that = (SendGroupUpdateAction) o; - return address.equals(that.address) && Arrays.equals(groupId, that.groupId); + + if (!address.equals(that.address)) return false; + return groupId.equals(that.groupId); } @Override public int hashCode() { - int result = Objects.hash(address); - result = 31 * result + Arrays.hashCode(groupId); + int result = address.hashCode(); + result = 31 * result + groupId.hashCode(); return result; } } diff --git a/src/main/java/org/asamk/signal/manager/KeyUtils.java b/src/main/java/org/asamk/signal/manager/KeyUtils.java index d6f332c0..21f6037f 100644 --- a/src/main/java/org/asamk/signal/manager/KeyUtils.java +++ b/src/main/java/org/asamk/signal/manager/KeyUtils.java @@ -26,10 +26,6 @@ class KeyUtils { return getSecret(18); } - static byte[] createGroupId() { - return getSecretBytes(16); - } - static byte[] createStickerUploadKey() { return getSecretBytes(32); } diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 8a46846f..b5d425d8 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -679,7 +679,7 @@ public class Manager implements Closeable { } } - private Optional createGroupAvatarAttachment(byte[] groupId) throws IOException { + private Optional createGroupAvatarAttachment(GroupId groupId) throws IOException { File file = getGroupAvatarFile(groupId); if (!file.exists()) { return Optional.absent(); @@ -697,7 +697,7 @@ public class Manager implements Closeable { return Optional.of(Utils.createAttachment(file)); } - private GroupInfo getGroupForSending(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException { + private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { GroupInfo g = account.getGroupStore().getGroup(groupId); if (g == null) { throw new GroupNotFoundException(groupId); @@ -708,7 +708,7 @@ public class Manager implements Closeable { return g; } - private GroupInfo getGroupForUpdating(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException { + private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { GroupInfo g = account.getGroupStore().getGroup(groupId); if (g == null) { throw new GroupNotFoundException(groupId); @@ -724,7 +724,7 @@ public class Manager implements Closeable { } public Pair> sendGroupMessage( - SignalServiceDataMessage.Builder messageBuilder, byte[] groupId + SignalServiceDataMessage.Builder messageBuilder, GroupId groupId ) throws IOException, GroupNotFoundException, NotAGroupMemberException { final GroupInfo g = getGroupForSending(groupId); @@ -735,7 +735,7 @@ public class Manager implements Closeable { } public Pair> sendGroupMessage( - String messageText, List attachments, byte[] groupId + String messageText, List attachments, GroupId groupId ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .withBody(messageText); @@ -747,7 +747,7 @@ public class Manager implements Closeable { } public Pair> sendGroupMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, byte[] groupId + String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, GroupId groupId ) throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException { SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, @@ -759,7 +759,7 @@ public class Manager implements Closeable { return sendGroupMessage(messageBuilder, groupId); } - public Pair> sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException { + public Pair> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException { SignalServiceDataMessage.Builder messageBuilder; @@ -767,7 +767,7 @@ public class Manager implements Closeable { if (g instanceof GroupInfoV1) { GroupInfoV1 groupInfoV1 = (GroupInfoV1) g; SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT) - .withId(groupId) + .withId(groupId.serialize()) .build(); messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group); groupInfoV1.removeMember(account.getSelfAddress()); @@ -783,8 +783,8 @@ public class Manager implements Closeable { return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - private Pair> sendUpdateGroupMessage( - byte[] groupId, String name, Collection members, String avatarFile + private Pair> sendUpdateGroupMessage( + GroupId groupId, String name, Collection members, String avatarFile ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { GroupInfo g; SignalServiceDataMessage.Builder messageBuilder; @@ -792,7 +792,7 @@ public class Manager implements Closeable { // Create new group GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile); if (gv2 == null) { - GroupInfoV1 gv1 = new GroupInfoV1(KeyUtils.createGroupId()); + GroupInfoV1 gv1 = new GroupInfoV1(GroupIdV1.createRandom()); gv1.addMembers(Collections.singleton(account.getSelfAddress())); updateGroupV1(gv1, name, members, avatarFile); messageBuilder = getGroupUpdateMessageBuilder(gv1); @@ -834,7 +834,7 @@ public class Manager implements Closeable { groupGroupChangePair.second()); } - return new Pair<>(group.groupId, result.second()); + return new Pair<>(group.getGroupId(), result.second()); } else { GroupInfoV1 gv1 = (GroupInfoV1) group; updateGroupV1(gv1, name, members, avatarFile); @@ -847,16 +847,16 @@ public class Manager implements Closeable { final Pair> result = sendMessage(messageBuilder, g.getMembersIncludingPendingWithout(account.getSelfAddress())); - return new Pair<>(g.groupId, result.second()); + return new Pair<>(g.getGroupId(), result.second()); } - public Pair> joinGroup( + public Pair> joinGroup( GroupInviteLinkUrl inviteLinkUrl ) throws IOException, GroupLinkNotActiveException { return sendJoinGroupMessage(inviteLinkUrl); } - private Pair> sendJoinGroupMessage( + private Pair> sendJoinGroupMessage( GroupInviteLinkUrl inviteLinkUrl ) throws IOException, GroupLinkNotActiveException { final DecryptedGroupJoinInfo groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(), @@ -870,12 +870,12 @@ public class Manager implements Closeable { if (group.getGroup() == null) { // Only requested member, can't send update to group members - return new Pair<>(group.groupId, List.of()); + return new Pair<>(group.getGroupId(), List.of()); } final Pair> result = sendUpdateGroupMessage(group, group.getGroup(), groupChange); - return new Pair<>(group.groupId, result.second()); + return new Pair<>(group.getGroupId(), result.second()); } private Pair> sendUpdateGroupMessage( @@ -923,13 +923,13 @@ public class Manager implements Closeable { if (avatarFile != null) { IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); - File aFile = getGroupAvatarFile(g.groupId); + File aFile = getGroupAvatarFile(g.getGroupId()); Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } } Pair> sendUpdateGroupMessage( - byte[] groupId, SignalServiceAddress recipient + GroupIdV1 groupId, SignalServiceAddress recipient ) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { GroupInfoV1 g; GroupInfo group = getGroupForSending(groupId); @@ -950,11 +950,11 @@ public class Manager implements Closeable { private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException { SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.UPDATE) - .withId(g.groupId) + .withId(g.getGroupId().serialize()) .withName(g.name) .withMembers(new ArrayList<>(g.getMembers())); - File aFile = getGroupAvatarFile(g.groupId); + File aFile = getGroupAvatarFile(g.getGroupId()); if (aFile.exists()) { try { group.withAvatar(Utils.createAttachment(aFile)); @@ -978,10 +978,10 @@ public class Manager implements Closeable { } Pair> sendGroupInfoRequest( - byte[] groupId, SignalServiceAddress recipient + GroupIdV1 groupId, SignalServiceAddress recipient ) throws IOException { SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO) - .withId(groupId); + .withId(groupId.serialize()); SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .asGroupMessage(group.build()); @@ -1087,7 +1087,7 @@ public class Manager implements Closeable { account.save(); } - public void setGroupBlocked(final byte[] groupId, final boolean blocked) throws GroupNotFoundException { + public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException { GroupInfo group = getGroup(groupId); if (group == null) { throw new GroupNotFoundException(groupId); @@ -1098,8 +1098,8 @@ public class Manager implements Closeable { account.save(); } - public Pair> updateGroup( - byte[] groupId, String name, List members, String avatar + public Pair> updateGroup( + GroupId groupId, String name, List members, String avatar ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException { return sendUpdateGroupMessage(groupId, name, @@ -1137,7 +1137,7 @@ public class Manager implements Closeable { /** * Change the expiration timer for a group */ - public void setExpirationTimer(byte[] groupId, int messageExpirationTimer) { + public void setExpirationTimer(GroupId groupId, int messageExpirationTimer) { GroupInfo g = account.getGroupStore().getGroup(groupId); if (g instanceof GroupInfoV1) { GroupInfoV1 groupInfoV1 = (GroupInfoV1) g; @@ -1551,20 +1551,21 @@ public class Manager implements Closeable { if (message.getGroupContext().isPresent()) { if (message.getGroupContext().get().getGroupV1().isPresent()) { SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); - GroupInfo group = account.getGroupStore().getGroupByV1Id(groupInfo.getGroupId()); + GroupIdV1 groupId = GroupId.v1(groupInfo.getGroupId()); + GroupInfo group = account.getGroupStore().getGroup(groupId); if (group == null || group instanceof GroupInfoV1) { GroupInfoV1 groupV1 = (GroupInfoV1) group; switch (groupInfo.getType()) { case UPDATE: { if (groupV1 == null) { - groupV1 = new GroupInfoV1(groupInfo.getGroupId()); + groupV1 = new GroupInfoV1(groupId); } if (groupInfo.getAvatar().isPresent()) { SignalServiceAttachment avatar = groupInfo.getAvatar().get(); if (avatar.isPointer()) { try { - retrieveGroupAvatarAttachment(avatar.asPointer(), groupV1.groupId); + retrieveGroupAvatarAttachment(avatar.asPointer(), groupV1.getGroupId()); } catch (IOException | InvalidMessageException | MissingConfigurationException e) { System.err.println("Failed to retrieve group avatar (" + avatar.asPointer() .getRemoteId() + "): " + e.getMessage()); @@ -1589,7 +1590,7 @@ public class Manager implements Closeable { } case DELIVER: if (groupV1 == null && !isSync) { - actions.add(new SendGroupInfoRequestAction(source, groupInfo.getGroupId())); + actions.add(new SendGroupInfoRequestAction(source, groupV1.getGroupId())); } break; case QUIT: { @@ -1601,7 +1602,7 @@ public class Manager implements Closeable { } case REQUEST_INFO: if (groupV1 != null && !isSync) { - actions.add(new SendGroupUpdateAction(source, groupV1.groupId)); + actions.add(new SendGroupUpdateAction(source, groupV1.getGroupId())); } break; } @@ -1627,7 +1628,7 @@ public class Manager implements Closeable { if (message.getGroupContext().isPresent()) { if (message.getGroupContext().get().getGroupV1().isPresent()) { SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); - GroupInfoV1 group = account.getGroupStore().getOrCreateGroupV1(groupInfo.getGroupId()); + GroupInfoV1 group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId())); if (group != null) { if (group.messageExpirationTime != message.getExpiresInSeconds()) { group.messageExpirationTime = message.getExpiresInSeconds(); @@ -1723,17 +1724,17 @@ public class Manager implements Closeable { ) { final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); - byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); - GroupInfo groupInfo = account.getGroupStore().getGroupByV2Id(groupId); + GroupIdV2 groupId = GroupUtils.getGroupIdV2(groupSecretParams); + GroupInfo groupInfo = account.getGroupStore().getGroup(groupId); final GroupInfoV2 groupInfoV2; if (groupInfo instanceof GroupInfoV1) { // Received a v2 group message for a v1 group, we need to locally migrate the group - account.getGroupStore().deleteGroup(groupInfo.groupId); + account.getGroupStore().deleteGroup(groupInfo.getGroupId()); groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey); System.err.println("Locally migrated group " - + Base64.encodeBytes(groupInfo.groupId) + + groupInfo.getGroupId().toBase64() + " to group v2, id: " - + Base64.encodeBytes(groupInfoV2.groupId) + + groupInfoV2.getGroupId().toBase64() + " !!!"); } else if (groupInfo instanceof GroupInfoV2) { groupInfoV2 = (GroupInfoV2) groupInfo; @@ -1970,19 +1971,14 @@ public class Manager implements Closeable { if (content != null && content.getDataMessage().isPresent()) { SignalServiceDataMessage message = content.getDataMessage().get(); if (message.getGroupContext().isPresent()) { - GroupInfo group = null; if (message.getGroupContext().get().getGroupV1().isPresent()) { SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); - if (groupInfo.getType() == SignalServiceGroup.Type.DELIVER) { - group = getGroup(groupInfo.getGroupId()); + if (groupInfo.getType() != SignalServiceGroup.Type.DELIVER) { + return false; } } - if (message.getGroupContext().get().getGroupV2().isPresent()) { - SignalServiceGroupV2 groupContext = message.getGroupContext().get().getGroupV2().get(); - final GroupMasterKey groupMasterKey = groupContext.getMasterKey(); - byte[] groupId = GroupUtils.getGroupId(groupMasterKey); - group = account.getGroupStore().getGroupByV2Id(groupId); - } + GroupId groupId = GroupUtils.getGroupId(message.getGroupContext().get()); + GroupInfo group = account.getGroupStore().getGroup(groupId); if (group != null && group.isBlocked()) { return true; } @@ -2055,7 +2051,8 @@ public class Manager implements Closeable { DeviceGroupsInputStream s = new DeviceGroupsInputStream(attachmentAsStream); DeviceGroup g; while ((g = s.read()) != null) { - GroupInfoV1 syncGroup = account.getGroupStore().getOrCreateGroupV1(g.getId()); + GroupInfoV1 syncGroup = account.getGroupStore() + .getOrCreateGroupV1(GroupId.v1(g.getId())); if (syncGroup != null) { if (g.getName().isPresent()) { syncGroup.name = g.getName().get(); @@ -2076,7 +2073,7 @@ public class Manager implements Closeable { } if (g.getAvatar().isPresent()) { - retrieveGroupAvatarAttachment(g.getAvatar().get(), syncGroup.groupId); + retrieveGroupAvatarAttachment(g.getAvatar().get(), syncGroup.getGroupId()); } syncGroup.inboxPosition = g.getInboxPosition().orNull(); syncGroup.archived = g.isArchived(); @@ -2104,12 +2101,15 @@ public class Manager implements Closeable { for (SignalServiceAddress address : blockedListMessage.getAddresses()) { setContactBlocked(resolveSignalServiceAddress(address), true); } - for (byte[] groupId : blockedListMessage.getGroupIds()) { + for (GroupId groupId : blockedListMessage.getGroupIds() + .stream() + .map(GroupId::unknownVersion) + .collect(Collectors.toSet())) { try { setGroupBlocked(groupId, true); } catch (GroupNotFoundException e) { System.err.println("BlockedListMessage contained groupID that was not found in GroupStore: " - + Base64.encodeBytes(groupId)); + + groupId.toBase64()); } } } @@ -2229,12 +2229,12 @@ public class Manager implements Closeable { } } - private File getGroupAvatarFile(byte[] groupId) { - return new File(pathConfig.getAvatarsPath(), "group-" + Base64.encodeBytes(groupId).replace("/", "_")); + private File getGroupAvatarFile(GroupId groupId) { + return new File(pathConfig.getAvatarsPath(), "group-" + groupId.toBase64().replace("/", "_")); } private File retrieveGroupAvatarAttachment( - SignalServiceAttachment attachment, byte[] groupId + SignalServiceAttachment attachment, GroupId groupId ) throws IOException, InvalidMessageException, MissingConfigurationException { IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); if (attachment.isPointer()) { @@ -2333,10 +2333,10 @@ public class Manager implements Closeable { for (GroupInfo record : account.getGroupStore().getGroups()) { if (record instanceof GroupInfoV1) { GroupInfoV1 groupInfo = (GroupInfoV1) record; - out.write(new DeviceGroup(groupInfo.groupId, + out.write(new DeviceGroup(groupInfo.getGroupId().serialize(), Optional.fromNullable(groupInfo.name), new ArrayList<>(groupInfo.getMembers()), - createGroupAvatarAttachment(groupInfo.groupId), + createGroupAvatarAttachment(groupInfo.getGroupId()), groupInfo.isMember(account.getSelfAddress()), Optional.of(groupInfo.messageExpirationTime), Optional.fromNullable(groupInfo.color), @@ -2442,7 +2442,7 @@ public class Manager implements Closeable { List groupIds = new ArrayList<>(); for (GroupInfo record : account.getGroupStore().getGroups()) { if (record.isBlocked()) { - groupIds.add(record.groupId); + groupIds.add(record.getGroupId().serialize()); } } sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds))); @@ -2466,7 +2466,7 @@ public class Manager implements Closeable { return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number)); } - public GroupInfo getGroup(byte[] groupId) { + public GroupInfo getGroup(GroupId groupId) { return account.getGroupStore().getGroup(groupId); } diff --git a/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java b/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java index 8c0e9be0..2c9b3f33 100644 --- a/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java +++ b/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java @@ -1,10 +1,8 @@ package org.asamk.signal.manager; -import org.whispersystems.util.Base64; - public class NotAGroupMemberException extends Exception { - public NotAGroupMemberException(byte[] groupId, String groupName) { - super("User is not a member in group: " + groupName + " (" + Base64.encodeBytes(groupId) + ")"); + public NotAGroupMemberException(GroupId groupId, String groupName) { + super("User is not a member in group: " + groupName + " (" + groupId.toBase64() + ")"); } } diff --git a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index d66831bb..6fd35003 100644 --- a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -2,7 +2,9 @@ package org.asamk.signal.manager.helper; import com.google.protobuf.InvalidProtocolBufferException; +import org.asamk.signal.manager.GroupIdV2; import org.asamk.signal.manager.GroupLinkPassword; +import org.asamk.signal.manager.GroupUtils; import org.asamk.signal.storage.groups.GroupInfoV2; import org.asamk.signal.util.IOUtils; import org.signal.storageservice.protos.groups.AccessControl; @@ -117,7 +119,7 @@ public class GroupHelper { return null; } - final byte[] groupId = groupSecretParams.getPublicParams().getGroupIdentifier().serialize(); + final GroupIdV2 groupId = GroupUtils.getGroupIdV2(groupSecretParams); final GroupMasterKey masterKey = groupSecretParams.getMasterKey(); GroupInfoV2 g = new GroupInfoV2(groupId, masterKey); g.setGroup(decryptedGroup); diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index 4f9d8628..d3145ecd 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.asamk.signal.manager.GroupId; import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.contacts.JsonContactsStore; import org.asamk.signal.storage.groups.GroupInfo; @@ -309,7 +310,7 @@ public class SignalAccount implements Closeable { contactInfo.messageExpirationTime = thread.messageExpirationTime; contactStore.updateContact(contactInfo); } else { - GroupInfo groupInfo = groupStore.getGroup(Base64.decode(thread.id)); + GroupInfo groupInfo = groupStore.getGroup(GroupId.fromBase64(thread.id)); if (groupInfo instanceof GroupInfoV1) { ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime; groupStore.updateGroup(groupInfo); diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java index 3571985b..40b8c884 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java +++ b/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java @@ -1,8 +1,8 @@ package org.asamk.signal.storage.groups; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; +import org.asamk.signal.manager.GroupId; import org.asamk.signal.manager.GroupInviteLinkUrl; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -12,12 +12,8 @@ import java.util.stream.Stream; public abstract class GroupInfo { - @JsonProperty - public final byte[] groupId; - - public GroupInfo(byte[] groupId) { - this.groupId = groupId; - } + @JsonIgnore + public abstract GroupId getGroupId(); @JsonIgnore public abstract String getTitle(); diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java index b06e2436..90b26b81 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java +++ b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java @@ -13,7 +13,11 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.asamk.signal.manager.GroupId; +import org.asamk.signal.manager.GroupIdV1; +import org.asamk.signal.manager.GroupIdV2; import org.asamk.signal.manager.GroupInviteLinkUrl; +import org.asamk.signal.manager.GroupUtils; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.io.IOException; @@ -26,8 +30,9 @@ public class GroupInfoV1 extends GroupInfo { private static final ObjectMapper jsonProcessor = new ObjectMapper(); - @JsonProperty - public byte[] expectedV2Id; + private final GroupIdV1 groupId; + + private GroupIdV2 expectedV2Id; @JsonProperty public String name; @@ -47,18 +52,8 @@ public class GroupInfoV1 extends GroupInfo { @JsonProperty(defaultValue = "false") public boolean archived; - public GroupInfoV1(byte[] groupId) { - super(groupId); - } - - @Override - public String getTitle() { - return name; - } - - @Override - public GroupInviteLinkUrl getGroupInviteLink() { - return null; + public GroupInfoV1(GroupIdV1 groupId) { + this.groupId = groupId; } public GroupInfoV1( @@ -74,8 +69,8 @@ public class GroupInfoV1 extends GroupInfo { @JsonProperty("messageExpirationTime") int messageExpirationTime, @JsonProperty("active") boolean _ignored_active ) { - super(groupId); - this.expectedV2Id = expectedV2Id; + this.groupId = GroupId.v1(groupId); + this.expectedV2Id = GroupId.v2(expectedV2Id); this.name = name; this.members.addAll(members); this.color = color; @@ -85,6 +80,40 @@ public class GroupInfoV1 extends GroupInfo { this.messageExpirationTime = messageExpirationTime; } + @Override + @JsonIgnore + public GroupIdV1 getGroupId() { + return groupId; + } + + @JsonProperty("groupId") + private byte[] getGroupIdJackson() { + return groupId.serialize(); + } + + @JsonIgnore + public GroupIdV2 getExpectedV2Id() { + if (expectedV2Id == null) { + expectedV2Id = GroupUtils.getGroupIdV2(groupId); + } + return expectedV2Id; + } + + @JsonProperty("expectedV2Id") + private byte[] getExpectedV2IdJackson() { + return expectedV2Id.serialize(); + } + + @Override + public String getTitle() { + return name; + } + + @Override + public GroupInviteLinkUrl getGroupInviteLink() { + return null; + } + @JsonIgnore public Set getMembers() { return members; diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java index 65f8c9a4..1b00caaa 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java +++ b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java @@ -1,5 +1,6 @@ package org.asamk.signal.storage.groups; +import org.asamk.signal.manager.GroupIdV2; import org.asamk.signal.manager.GroupInviteLinkUrl; import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.local.DecryptedGroup; @@ -13,16 +14,22 @@ import java.util.stream.Collectors; public class GroupInfoV2 extends GroupInfo { + private final GroupIdV2 groupId; private final GroupMasterKey masterKey; private boolean blocked; private DecryptedGroup group; // stored as a file with hexadecimal groupId as name - public GroupInfoV2(final byte[] groupId, final GroupMasterKey masterKey) { - super(groupId); + public GroupInfoV2(final GroupIdV2 groupId, final GroupMasterKey masterKey) { + this.groupId = groupId; this.masterKey = masterKey; } + @Override + public GroupIdV2 getGroupId() { + return groupId; + } + public GroupMasterKey getMasterKey() { return masterKey; } diff --git a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java index 2175e293..d6a99f1c 100644 --- a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java +++ b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java @@ -12,6 +12,9 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.asamk.signal.manager.GroupId; +import org.asamk.signal.manager.GroupIdV1; +import org.asamk.signal.manager.GroupIdV2; import org.asamk.signal.manager.GroupUtils; import org.asamk.signal.util.Hex; import org.asamk.signal.util.IOUtils; @@ -25,7 +28,6 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -39,7 +41,7 @@ public class JsonGroupStore { @JsonProperty("groups") @JsonSerialize(using = GroupsSerializer.class) @JsonDeserialize(using = GroupsDeserializer.class) - private final Map groups = new HashMap<>(); + private final Map groups = new HashMap<>(); private JsonGroupStore() { } @@ -49,11 +51,11 @@ public class JsonGroupStore { } public void updateGroup(GroupInfo group) { - groups.put(Base64.encodeBytes(group.groupId), group); + groups.put(group.getGroupId(), group); if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() != null) { try { IOUtils.createPrivateDirectories(groupCachePath); - try (FileOutputStream stream = new FileOutputStream(getGroupFile(group.groupId))) { + try (FileOutputStream stream = new FileOutputStream(getGroupFile(group.getGroupId()))) { ((GroupInfoV2) group).getGroup().writeTo(stream); } } catch (IOException e) { @@ -62,57 +64,50 @@ public class JsonGroupStore { } } - public void deleteGroup(byte[] groupId) { - groups.remove(Base64.encodeBytes(groupId)); + public void deleteGroup(GroupId groupId) { + groups.remove(groupId); } - public GroupInfo getGroup(byte[] groupId) { - final GroupInfo group = groups.get(Base64.encodeBytes(groupId)); - if (group == null & groupId.length == 16) { - return getGroupByV1Id(groupId); - } - loadDecryptedGroup(group); - return group; - } - - public GroupInfo getGroupByV1Id(byte[] groupIdV1) { - GroupInfo group = groups.get(Base64.encodeBytes(groupIdV1)); + public GroupInfo getGroup(GroupId groupId) { + GroupInfo group = groups.get(groupId); if (group == null) { - group = groups.get(Base64.encodeBytes(GroupUtils.getGroupId(GroupUtils.deriveV2MigrationMasterKey(groupIdV1)))); + if (groupId instanceof GroupIdV1) { + group = groups.get(GroupUtils.getGroupIdV2((GroupIdV1) groupId)); + } else if (groupId instanceof GroupIdV2) { + group = getGroupV1ByV2Id((GroupIdV2) groupId); + } } loadDecryptedGroup(group); return group; } - public GroupInfo getGroupByV2Id(byte[] groupIdV2) { - GroupInfo group = groups.get(Base64.encodeBytes(groupIdV2)); - if (group == null) { - for (GroupInfo g : groups.values()) { - if (g instanceof GroupInfoV1 && Arrays.equals(groupIdV2, ((GroupInfoV1) g).expectedV2Id)) { - group = g; - break; + private GroupInfoV1 getGroupV1ByV2Id(GroupIdV2 groupIdV2) { + for (GroupInfo g : groups.values()) { + if (g instanceof GroupInfoV1) { + final GroupInfoV1 gv1 = (GroupInfoV1) g; + if (groupIdV2.equals(gv1.getExpectedV2Id())) { + return gv1; } } } - loadDecryptedGroup(group); - return group; + return null; } private void loadDecryptedGroup(final GroupInfo group) { if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() == null) { - try (FileInputStream stream = new FileInputStream(getGroupFile(group.groupId))) { + try (FileInputStream stream = new FileInputStream(getGroupFile(group.getGroupId()))) { ((GroupInfoV2) group).setGroup(DecryptedGroup.parseFrom(stream)); } catch (IOException ignored) { } } } - private File getGroupFile(final byte[] groupId) { - return new File(groupCachePath, Hex.toStringCondensed(groupId)); + private File getGroupFile(final GroupId groupId) { + return new File(groupCachePath, Hex.toStringCondensed(groupId.serialize())); } - public GroupInfoV1 getOrCreateGroupV1(byte[] groupId) { - GroupInfo group = groups.get(Base64.encodeBytes(groupId)); + public GroupInfoV1 getOrCreateGroupV1(GroupIdV1 groupId) { + GroupInfo group = getGroup(groupId); if (group instanceof GroupInfoV1) { return (GroupInfoV1) group; } @@ -146,7 +141,7 @@ public class JsonGroupStore { } else if (group instanceof GroupInfoV2) { final GroupInfoV2 groupV2 = (GroupInfoV2) group; jgen.writeStartObject(); - jgen.writeStringField("groupId", Base64.encodeBytes(groupV2.groupId)); + jgen.writeStringField("groupId", groupV2.getGroupId().toBase64()); jgen.writeStringField("masterKey", Base64.encodeBytes(groupV2.getMasterKey().serialize())); jgen.writeBooleanField("blocked", groupV2.isBlocked()); jgen.writeEndObject(); @@ -158,34 +153,31 @@ public class JsonGroupStore { } } - private static class GroupsDeserializer extends JsonDeserializer> { + private static class GroupsDeserializer extends JsonDeserializer> { @Override - public Map deserialize( + public Map deserialize( JsonParser jsonParser, DeserializationContext deserializationContext ) throws IOException { - Map groups = new HashMap<>(); + Map groups = new HashMap<>(); JsonNode node = jsonParser.getCodec().readTree(jsonParser); for (JsonNode n : node) { GroupInfo g; if (n.has("masterKey")) { // a v2 group - byte[] groupId = Base64.decode(n.get("groupId").asText()); + GroupIdV2 groupId = GroupIdV2.fromBase64(n.get("groupId").asText()); try { GroupMasterKey masterKey = new GroupMasterKey(Base64.decode(n.get("masterKey").asText())); g = new GroupInfoV2(groupId, masterKey); } catch (InvalidInputException e) { - throw new AssertionError("Invalid master key for group " + Base64.encodeBytes(groupId)); + throw new AssertionError("Invalid master key for group " + groupId.toBase64()); } g.setBlocked(n.get("blocked").asBoolean(false)); } else { GroupInfoV1 gv1 = jsonProcessor.treeToValue(n, GroupInfoV1.class); - if (gv1.expectedV2Id == null) { - gv1.expectedV2Id = GroupUtils.getGroupId(GroupUtils.deriveV2MigrationMasterKey(gv1.groupId)); - } g = gv1; } - groups.put(Base64.encodeBytes(g.groupId), g); + groups.put(g.getGroupId(), g); } return groups; diff --git a/src/main/java/org/asamk/signal/util/ErrorUtils.java b/src/main/java/org/asamk/signal/util/ErrorUtils.java index 8e65d440..44d505be 100644 --- a/src/main/java/org/asamk/signal/util/ErrorUtils.java +++ b/src/main/java/org/asamk/signal/util/ErrorUtils.java @@ -1,5 +1,6 @@ package org.asamk.signal.util; +import org.asamk.signal.manager.GroupIdFormatException; import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.NotAGroupMemberException; import org.whispersystems.signalservice.api.messages.SendMessageResult; diff --git a/src/main/java/org/asamk/signal/util/GroupIdFormatException.java b/src/main/java/org/asamk/signal/util/GroupIdFormatException.java deleted file mode 100644 index 5a5c4570..00000000 --- a/src/main/java/org/asamk/signal/util/GroupIdFormatException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.asamk.signal.util; - -import java.io.IOException; - -public class GroupIdFormatException extends Exception { - - public GroupIdFormatException(String groupId, IOException e) { - super("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage()); - } -} diff --git a/src/main/java/org/asamk/signal/util/Util.java b/src/main/java/org/asamk/signal/util/Util.java index bc2d3377..3cd5619a 100644 --- a/src/main/java/org/asamk/signal/util/Util.java +++ b/src/main/java/org/asamk/signal/util/Util.java @@ -2,13 +2,13 @@ package org.asamk.signal.util; import com.fasterxml.jackson.databind.JsonNode; +import org.asamk.signal.manager.GroupId; +import org.asamk.signal.manager.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 org.whispersystems.util.Base64; -import java.io.IOException; import java.io.InvalidObjectException; public class Util { @@ -48,12 +48,8 @@ public class Util { return node; } - public static byte[] decodeGroupId(String groupId) throws GroupIdFormatException { - try { - return Base64.decode(groupId); - } catch (IOException e) { - throw new GroupIdFormatException(groupId, e); - } + public static GroupId decodeGroupId(String groupId) throws GroupIdFormatException { + return GroupId.fromBase64(groupId); } public static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException { -- 2.51.0 From e11e02088648458f039f9b2b69a5e9a9dfe5789f Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 24 Dec 2020 17:53:23 +0100 Subject: [PATCH 06/16] Retrieve group v2 avatars Fixes #392 --- .../org/asamk/signal/manager/Manager.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index b5d425d8..4c10a529 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -1754,6 +1754,11 @@ public class Manager implements Closeable { } if (group != null) { storeProfileKeysFromMembers(group); + try { + retrieveGroupAvatar(groupId, groupSecretParams, group.getAvatar()); + } catch (IOException e) { + System.err.println("Failed to download group avatar, ignoring ..."); + } } groupInfoV2.setGroup(group); account.getGroupStore().updateGroup(groupInfoV2); @@ -2246,6 +2251,35 @@ public class Manager implements Closeable { } } + private File retrieveGroupAvatar( + GroupId groupId, GroupSecretParams groupSecretParams, String cdnKey + ) throws IOException { + IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath()); + SignalServiceMessageReceiver receiver = getOrCreateMessageReceiver(); + File outputFile = getGroupAvatarFile(groupId); + GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); + + File tmpFile = IOUtils.createTempFile(); + tmpFile.deleteOnExit(); + try (InputStream input = receiver.retrieveGroupsV2ProfileAvatar(cdnKey, + tmpFile, + ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) { + byte[] encryptedData = IOUtils.readFully(input); + + byte[] decryptedData = groupOperations.decryptAvatar(encryptedData); + try (OutputStream output = new FileOutputStream(outputFile)) { + output.write(decryptedData); + } + } finally { + try { + Files.delete(tmpFile.toPath()); + } catch (IOException e) { + System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage()); + } + } + return outputFile; + } + private File getProfileAvatarFile(SignalServiceAddress address) { return new File(pathConfig.getAvatarsPath(), "profile-" + address.getLegacyIdentifier()); } -- 2.51.0 From ff998fce578ebc035439f8eaad8e1b48e482a3c5 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 24 Dec 2020 18:05:12 +0100 Subject: [PATCH 07/16] Fix handling data messages of sync messages --- .../java/org/asamk/signal/manager/Manager.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 4c10a529..e02106b9 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -1621,7 +1621,7 @@ public class Manager implements Closeable { } final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source; - if (message.isEndSession()) { + if (conversationPartnerAddress != null && message.isEndSession()) { handleEndSession(conversationPartnerAddress); } if (message.isExpirationUpdate() || message.getBody().isPresent()) { @@ -1638,7 +1638,7 @@ public class Manager implements Closeable { } else if (message.getGroupContext().get().getGroupV2().isPresent()) { // disappearing message timer already stored in the DecryptedGroup } - } else { + } else if (conversationPartnerAddress != null) { ContactInfo contact = account.getContactStore().getContact(conversationPartnerAddress); if (contact == null) { contact = new ContactInfo(conversationPartnerAddress); @@ -2025,13 +2025,11 @@ public class Manager implements Closeable { if (syncMessage.getSent().isPresent()) { SentTranscriptMessage message = syncMessage.getSent().get(); final SignalServiceAddress destination = message.getDestination().orNull(); - if (destination != null) { - actions.addAll(handleSignalServiceDataMessage(message.getMessage(), - true, - sender, - destination, - ignoreAttachments)); - } + actions.addAll(handleSignalServiceDataMessage(message.getMessage(), + true, + sender, + destination, + ignoreAttachments)); } if (syncMessage.getRequest().isPresent()) { RequestMessage rm = syncMessage.getRequest().get(); -- 2.51.0 From caabde4acfc1c6bccec981ff763d6dca6f6ed383 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 25 Dec 2020 13:42:51 +0100 Subject: [PATCH 08/16] Fix prevention of adding group members a second time --- src/main/java/org/asamk/signal/manager/Manager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index e02106b9..2553a416 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -816,7 +816,10 @@ public class Manager implements Closeable { if (members != null) { final Set newMembers = new HashSet<>(members); - newMembers.removeAll(group.getMembers()); + newMembers.removeAll(group.getMembers() + .stream() + .map(this::resolveSignalServiceAddress) + .collect(Collectors.toSet())); if (newMembers.size() > 0) { Pair groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2, newMembers); -- 2.51.0 From 6a82029ab481eb657847651b4f5200acf57b2553 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 25 Dec 2020 13:46:35 +0100 Subject: [PATCH 09/16] Use base64 group id for protobuf group file to match avatar files base64 with '/' replaced by '_' --- .../signal/storage/groups/JsonGroupStore.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java index d6a99f1c..6f0f3c04 100644 --- a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java +++ b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java @@ -58,6 +58,10 @@ public class JsonGroupStore { try (FileOutputStream stream = new FileOutputStream(getGroupFile(group.getGroupId()))) { ((GroupInfoV2) group).getGroup().writeTo(stream); } + final File groupFileLegacy = getGroupFileLegacy(group.getGroupId()); + if (groupFileLegacy.exists()) { + groupFileLegacy.delete(); + } } catch (IOException e) { System.err.println("Failed to cache group, ignoring ..."); } @@ -95,17 +99,28 @@ public class JsonGroupStore { private void loadDecryptedGroup(final GroupInfo group) { if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() == null) { - try (FileInputStream stream = new FileInputStream(getGroupFile(group.getGroupId()))) { + File groupFile = getGroupFile(group.getGroupId()); + if (!groupFile.exists()) { + groupFile = getGroupFileLegacy(group.getGroupId()); + } + if (!groupFile.exists()) { + return; + } + try (FileInputStream stream = new FileInputStream(groupFile)) { ((GroupInfoV2) group).setGroup(DecryptedGroup.parseFrom(stream)); } catch (IOException ignored) { } } } - private File getGroupFile(final GroupId groupId) { + private File getGroupFileLegacy(final GroupId groupId) { return new File(groupCachePath, Hex.toStringCondensed(groupId.serialize())); } + private File getGroupFile(final GroupId groupId) { + return new File(groupCachePath, groupId.toBase64().replace("/", "_")); + } + public GroupInfoV1 getOrCreateGroupV1(GroupIdV1 groupId) { GroupInfo group = getGroup(groupId); if (group instanceof GroupInfoV1) { -- 2.51.0 From 5c754b6f5d5bd3273b3c0722cf3eabbcd02c20b9 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 25 Dec 2020 22:34:30 +0100 Subject: [PATCH 10/16] Use slf4j simple logger --- build.gradle | 2 +- src/main/java/org/asamk/signal/Main.java | 199 +++++++++--------- .../org/asamk/signal/manager/Manager.java | 114 +++++----- .../org/asamk/signal/manager/PathConfig.java | 22 +- .../signal/manager/ProvisioningManager.java | 3 +- .../signal/manager/helper/GroupHelper.java | 38 ++-- .../asamk/signal/storage/SignalAccount.java | 10 +- .../signal/storage/groups/JsonGroupStore.java | 6 +- .../protocol/JsonIdentityKeyStore.java | 6 +- .../storage/protocol/JsonPreKeyStore.java | 6 +- .../storage/protocol/JsonSessionStore.java | 8 +- .../protocol/JsonSignedPreKeyStore.java | 6 +- 12 files changed, 237 insertions(+), 183 deletions(-) diff --git a/build.gradle b/build.gradle index 48b57a4f..1fbf5948 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ dependencies { implementation 'org.bouncycastle:bcprov-jdk15on:1.67' implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1' implementation 'com.github.hypfvieh:dbus-java:3.2.4' - implementation 'org.slf4j:slf4j-nop:1.7.30' + implementation 'org.slf4j:slf4j-simple:1.7.30' } jar { diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index de827b1c..97e3f9b2 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -41,6 +41,8 @@ import org.asamk.signal.util.SecurityProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; @@ -50,10 +52,10 @@ import java.io.IOException; import java.security.Security; import java.util.Map; -import static org.whispersystems.signalservice.internal.util.Util.isEmpty; - public class Main { + final static Logger logger = LoggerFactory.getLogger(Main.class); + public static void main(String[] args) { installSecurityProviderWorkaround(); @@ -62,7 +64,7 @@ public class Main { System.exit(1); } - int res = handleCommands(ns); + int res = init(ns); System.exit(res); } @@ -72,77 +74,81 @@ public class Main { Security.addProvider(new BouncyCastleProvider()); } - private static int handleCommands(Namespace ns) { - final String username = ns.getString("username"); - + public static int init(Namespace ns) { if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) { - try { - DBusConnection.DBusBusType busType; - if (ns.getBoolean("dbus_system")) { - busType = DBusConnection.DBusBusType.SYSTEM; - } else { - busType = DBusConnection.DBusBusType.SESSION; - } - try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) { - Signal ts = dBusConn.getRemoteObject(DbusConfig.SIGNAL_BUSNAME, - DbusConfig.SIGNAL_OBJECTPATH, - Signal.class); + return initDbusClient(ns, ns.getBoolean("dbus_system")); + } - return handleCommands(ns, ts, dBusConn); - } - } catch (UnsatisfiedLinkError e) { - System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); - return 1; - } catch (DBusException | IOException e) { - e.printStackTrace(); - return 3; - } + final String username = ns.getString("username"); + + final File dataPath; + String config = ns.getString("config"); + if (config != null) { + dataPath = new File(config); } else { - String dataPath = ns.getString("config"); - if (isEmpty(dataPath)) { - dataPath = getDefaultDataPath(); - } + dataPath = getDefaultDataPath(); + } - final SignalServiceConfiguration serviceConfiguration = ServiceConfig.createDefaultServiceConfiguration( - BaseConfig.USER_AGENT); + final SignalServiceConfiguration serviceConfiguration = ServiceConfig.createDefaultServiceConfiguration( + BaseConfig.USER_AGENT); - if (!ServiceConfig.getCapabilities().isGv2()) { - System.err.println("WARNING: Support for new group V2 is disabled," - + " because the required native library dependency is missing: libzkgroup"); - } + if (!ServiceConfig.getCapabilities().isGv2()) { + logger.warn("WARNING: Support for new group V2 is disabled," + + " because the required native library dependency is missing: libzkgroup"); + } - if (username == null) { - ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT); - return handleCommands(ns, pm); - } + if (username == null) { + ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT); + return handleCommands(ns, pm); + } - Manager manager; + Manager manager; + try { + manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT); + } catch (Throwable e) { + logger.error("Error loading state file: {}", e.getMessage()); + return 2; + } + + try (Manager m = manager) { try { - manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT); - } catch (Throwable e) { - System.err.println("Error loading state file: " + e.getMessage()); + m.checkAccountState(); + } catch (AuthorizationFailedException e) { + if (!"register".equals(ns.getString("command"))) { + // Register command should still be possible, if current authorization fails + System.err.println("Authorization failed, was the number registered elsewhere?"); + return 2; + } + } catch (IOException e) { + logger.error("Error while checking account: {}", e.getMessage()); return 2; } - try (Manager m = manager) { - try { - m.checkAccountState(); - } catch (AuthorizationFailedException e) { - if (!"register".equals(ns.getString("command"))) { - // Register command should still be possible, if current authorization fails - System.err.println("Authorization failed, was the number registered elsewhere?"); - return 2; - } - } catch (IOException e) { - System.err.println("Error while checking account: " + e.getMessage()); - return 2; - } + return handleCommands(ns, m); + } catch (IOException e) { + logger.error("Cleanup failed", e); + return 3; + } + } - return handleCommands(ns, m); - } catch (IOException e) { - e.printStackTrace(); - return 3; + private static int initDbusClient(final Namespace ns, final boolean systemBus) { + try { + DBusConnection.DBusBusType busType; + if (systemBus) { + busType = DBusConnection.DBusBusType.SYSTEM; + } else { + busType = DBusConnection.DBusBusType.SESSION; + } + try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) { + Signal ts = dBusConn.getRemoteObject(DbusConfig.SIGNAL_BUSNAME, + DbusConfig.SIGNAL_OBJECTPATH, + Signal.class); + + return handleCommands(ns, ts, dBusConn); } + } catch (DBusException | IOException e) { + logger.error("Dbus client failed", e); + return 3; } } @@ -205,19 +211,19 @@ public class Main { * * @return the data directory to be used by signal-cli. */ - private static String getDefaultDataPath() { - String dataPath = IOUtils.getDataHomeDir() + "/signal-cli"; - if (new File(dataPath).exists()) { + private static File getDefaultDataPath() { + File dataPath = new File(IOUtils.getDataHomeDir(), "/signal-cli"); + if (dataPath.exists()) { return dataPath; } - String legacySettingsPath = System.getProperty("user.home") + "/.config/signal"; - if (new File(legacySettingsPath).exists()) { + File legacySettingsPath = new File(System.getProperty("user.home"), "/.config/signal"); + if (legacySettingsPath.exists()) { return legacySettingsPath; } - legacySettingsPath = System.getProperty("user.home") + "/.config/textsecure"; - if (new File(legacySettingsPath).exists()) { + legacySettingsPath = new File(System.getProperty("user.home"), "/.config/textsecure"); + if (legacySettingsPath.exists()) { return legacySettingsPath; } @@ -225,32 +231,7 @@ public class Main { } private static Namespace parseArgs(String[] args) { - ArgumentParser parser = ArgumentParsers.newFor("signal-cli") - .build() - .defaultHelp(true) - .description("Commandline interface for Signal.") - .version(BaseConfig.PROJECT_NAME + " " + BaseConfig.PROJECT_VERSION); - - parser.addArgument("-v", "--version").help("Show package version.").action(Arguments.version()); - parser.addArgument("--config") - .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli)."); - - MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup(); - mut.addArgument("-u", "--username").help("Specify your phone number, that will be used for verification."); - mut.addArgument("--dbus").help("Make request via user dbus.").action(Arguments.storeTrue()); - mut.addArgument("--dbus-system").help("Make request via system dbus.").action(Arguments.storeTrue()); - - Subparsers subparsers = parser.addSubparsers() - .title("subcommands") - .dest("command") - .description("valid subcommands") - .help("additional help"); - - final Map commands = Commands.getCommands(); - for (Map.Entry entry : commands.entrySet()) { - Subparser subparser = subparsers.addParser(entry.getKey()); - entry.getValue().attachToSubparser(subparser); - } + ArgumentParser parser = buildArgumentParser(); Namespace ns; try { @@ -283,4 +264,34 @@ public class Main { } return ns; } + + private static ArgumentParser buildArgumentParser() { + ArgumentParser parser = ArgumentParsers.newFor("signal-cli") + .build() + .defaultHelp(true) + .description("Commandline interface for Signal.") + .version(BaseConfig.PROJECT_NAME + " " + BaseConfig.PROJECT_VERSION); + + parser.addArgument("-v", "--version").help("Show package version.").action(Arguments.version()); + parser.addArgument("--config") + .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli)."); + + MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup(); + mut.addArgument("-u", "--username").help("Specify your phone number, that will be used for verification."); + mut.addArgument("--dbus").help("Make request via user dbus.").action(Arguments.storeTrue()); + mut.addArgument("--dbus-system").help("Make request via system dbus.").action(Arguments.storeTrue()); + + Subparsers subparsers = parser.addSubparsers() + .title("subcommands") + .dest("command") + .description("valid subcommands") + .help("additional help"); + + final Map commands = Commands.getCommands(); + for (Map.Entry entry : commands.entrySet()) { + Subparser subparser = subparsers.addParser(entry.getKey()); + entry.getValue().attachToSubparser(subparser); + } + return parser; + } } diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 2553a416..c679449d 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -55,6 +55,8 @@ import org.signal.zkgroup.groups.GroupSecretParams; import org.signal.zkgroup.profiles.ClientZkProfileOperations; import org.signal.zkgroup.profiles.ProfileKey; import org.signal.zkgroup.profiles.ProfileKeyCredential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.InvalidKeyException; @@ -173,6 +175,8 @@ import static org.asamk.signal.manager.ServiceConfig.getIasKeyStore; public class Manager implements Closeable { + final static Logger logger = LoggerFactory.getLogger(Manager.class); + private final SleepTimer timer = new UptimeSleepTimer(); private final SignalServiceConfiguration serviceConfiguration; @@ -274,7 +278,7 @@ public class Manager implements Closeable { } public static Manager init( - String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent + String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent ) throws IOException { PathConfig pathConfig = PathConfig.createDefault(settingsPath); @@ -590,7 +594,7 @@ public class Manager implements Closeable { try { profile = retrieveRecipientProfile(address, profileKey); } catch (IOException e) { - System.err.println("Failed to retrieve profile, ignoring: " + e.getMessage()); + logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage()); profileEntry.setRequestPending(false); return null; } @@ -613,7 +617,7 @@ public class Manager implements Closeable { profileAndCredential = profileHelper.retrieveProfileSync(address, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL); } catch (IOException e) { - System.err.println("Failed to retrieve profile key credential, ignoring: " + e.getMessage()); + logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage()); return null; } @@ -646,7 +650,7 @@ public class Manager implements Closeable { ? null : retrieveProfileAvatar(address, encryptedProfile.getAvatar(), profileKey); } catch (Throwable e) { - System.err.println("Failed to retrieve profile avatar, ignoring: " + e.getMessage()); + logger.warn("Failed to retrieve profile avatar, ignoring: {}", e.getMessage()); } ProfileCipher profileCipher = new ProfileCipher(profileKey); @@ -1327,7 +1331,7 @@ public class Manager implements Closeable { try { certificate = accountManager.getSenderCertificate(); } catch (IOException e) { - System.err.println("Failed to get sender certificate: " + e); + logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage()); return null; } // TODO cache for a day @@ -1366,7 +1370,7 @@ public class Manager implements Closeable { missingUuids.stream().map(a -> a.getNumber().get()).collect(Collectors.toSet()), CDS_MRENCLAVE); } catch (IOException | Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException e) { - System.err.println("Failed to resolve uuids from server: " + e.getMessage()); + logger.warn("Failed to resolve uuids from server, ignoring: {}", e.getMessage()); registeredUsers = new HashMap<>(); } @@ -1570,8 +1574,9 @@ public class Manager implements Closeable { try { retrieveGroupAvatarAttachment(avatar.asPointer(), groupV1.getGroupId()); } catch (IOException | InvalidMessageException | MissingConfigurationException e) { - System.err.println("Failed to retrieve group avatar (" + avatar.asPointer() - .getRemoteId() + "): " + e.getMessage()); + logger.warn("Failed to retrieve avatar for group {}, ignoring: {}", + groupId.toBase64(), + e.getMessage()); } } } @@ -1593,7 +1598,7 @@ public class Manager implements Closeable { } case DELIVER: if (groupV1 == null && !isSync) { - actions.add(new SendGroupInfoRequestAction(source, groupV1.getGroupId())); + actions.add(new SendGroupInfoRequestAction(source, groupId)); } break; case QUIT: { @@ -1658,10 +1663,9 @@ public class Manager implements Closeable { try { retrieveAttachment(attachment.asPointer()); } catch (IOException | InvalidMessageException | MissingConfigurationException e) { - System.err.println("Failed to retrieve attachment (" - + attachment.asPointer().getRemoteId() - + "): " - + e.getMessage()); + logger.warn("Failed to retrieve attachment ({}), ignoring: {}", + attachment.asPointer().getRemoteId(), + e.getMessage()); } } } @@ -1686,10 +1690,9 @@ public class Manager implements Closeable { try { retrieveAttachment(attachment); } catch (IOException | InvalidMessageException | MissingConfigurationException e) { - System.err.println("Failed to retrieve attachment (" - + attachment.getRemoteId() - + "): " - + e.getMessage()); + logger.warn("Failed to retrieve preview image ({}), ignoring: {}", + attachment.getRemoteId(), + e.getMessage()); } } } @@ -1703,10 +1706,9 @@ public class Manager implements Closeable { try { retrieveAttachment(attachment.asPointer()); } catch (IOException | InvalidMessageException | MissingConfigurationException e) { - System.err.println("Failed to retrieve attachment (" - + attachment.asPointer().getRemoteId() - + "): " - + e.getMessage()); + logger.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}", + attachment.asPointer().getRemoteId(), + e.getMessage()); } } } @@ -1734,11 +1736,9 @@ public class Manager implements Closeable { // Received a v2 group message for a v1 group, we need to locally migrate the group account.getGroupStore().deleteGroup(groupInfo.getGroupId()); groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey); - System.err.println("Locally migrated group " - + groupInfo.getGroupId().toBase64() - + " to group v2, id: " - + groupInfoV2.getGroupId().toBase64() - + " !!!"); + logger.info("Locally migrated group {} to group v2, id: {}", + groupInfo.getGroupId().toBase64(), + groupInfoV2.getGroupId().toBase64()); } else if (groupInfo instanceof GroupInfoV2) { groupInfoV2 = (GroupInfoV2) groupInfo; } else { @@ -1757,10 +1757,13 @@ public class Manager implements Closeable { } if (group != null) { storeProfileKeysFromMembers(group); - try { - retrieveGroupAvatar(groupId, groupSecretParams, group.getAvatar()); - } catch (IOException e) { - System.err.println("Failed to download group avatar, ignoring ..."); + final String avatar = group.getAvatar(); + if (avatar != null && !avatar.isEmpty()) { + try { + retrieveGroupAvatar(groupId, groupSecretParams, avatar); + } catch (IOException e) { + logger.warn("Failed to download group avatar, ignoring: {}", e.getMessage()); + } } } groupInfoV2.setGroup(group); @@ -1830,7 +1833,7 @@ public class Manager implements Closeable { try { Files.delete(fileEntry.toPath()); } catch (IOException e) { - System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage()); + logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage()); } return; } @@ -1848,7 +1851,7 @@ public class Manager implements Closeable { try { Files.delete(fileEntry.toPath()); } catch (IOException e) { - System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage()); + logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage()); } } @@ -1880,8 +1883,7 @@ public class Manager implements Closeable { File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp()); Utils.storeEnvelope(envelope1, cacheFile); } catch (IOException e) { - System.err.println("Failed to store encrypted message in disk cache, ignoring: " - + e.getMessage()); + logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage()); } }); if (result.isPresent()) { @@ -1910,7 +1912,7 @@ public class Manager implements Closeable { if (returnOnTimeout) return; continue; } catch (InvalidVersionException e) { - System.err.println("Ignoring error: " + e.getMessage()); + logger.warn("Error while receiving messages, ignoring: {}", e.getMessage()); continue; } @@ -1954,7 +1956,7 @@ public class Manager implements Closeable { // 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()); + logger.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile, e.getMessage()); } } } @@ -2088,16 +2090,18 @@ public class Manager implements Closeable { } } } catch (Exception e) { + logger.warn("Failed to handle received sync groups “{}”, ignoring: {}", + tmpFile, + e.getMessage()); e.printStackTrace(); } finally { if (tmpFile != null) { try { Files.delete(tmpFile.toPath()); } catch (IOException e) { - System.err.println("Failed to delete received groups temp file “" - + tmpFile - + "”: " - + e.getMessage()); + logger.warn("Failed to delete received groups temp file “{}”, ignoring: {}", + tmpFile, + e.getMessage()); } } } @@ -2114,8 +2118,8 @@ public class Manager implements Closeable { try { setGroupBlocked(groupId, true); } catch (GroupNotFoundException e) { - System.err.println("BlockedListMessage contained groupID that was not found in GroupStore: " - + groupId.toBase64()); + logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}", + groupId.toBase64()); } } } @@ -2176,10 +2180,9 @@ public class Manager implements Closeable { try { Files.delete(tmpFile.toPath()); } catch (IOException e) { - System.err.println("Failed to delete received contacts temp file “" - + tmpFile - + "”: " - + e.getMessage()); + logger.warn("Failed to delete received contacts temp file “{}”, ignoring: {}", + tmpFile, + e.getMessage()); } } } @@ -2275,7 +2278,9 @@ public class Manager implements Closeable { try { Files.delete(tmpFile.toPath()); } catch (IOException e) { - System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage()); + logger.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}", + tmpFile, + e.getMessage()); } } return outputFile; @@ -2303,7 +2308,9 @@ public class Manager implements Closeable { try { Files.delete(tmpFile.toPath()); } catch (IOException e) { - System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage()); + logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}", + tmpFile, + e.getMessage()); } } return outputFile; @@ -2343,10 +2350,9 @@ public class Manager implements Closeable { try { Files.delete(tmpFile.toPath()); } catch (IOException e) { - System.err.println("Failed to delete received attachment temp file “" - + tmpFile - + "”: " - + e.getMessage()); + logger.warn("Failed to delete received attachment temp file “{}”, ignoring: {}", + tmpFile, + e.getMessage()); } } return outputFile; @@ -2397,7 +2403,7 @@ public class Manager implements Closeable { try { Files.delete(groupsFile.toPath()); } catch (IOException e) { - System.err.println("Failed to delete groups temp file “" + groupsFile + "”: " + e.getMessage()); + logger.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile, e.getMessage()); } } } @@ -2462,7 +2468,7 @@ public class Manager implements Closeable { try { Files.delete(contactsFile.toPath()); } catch (IOException e) { - System.err.println("Failed to delete contacts temp file “" + contactsFile + "”: " + e.getMessage()); + logger.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile, e.getMessage()); } } } diff --git a/src/main/java/org/asamk/signal/manager/PathConfig.java b/src/main/java/org/asamk/signal/manager/PathConfig.java index c0c9e1e7..ca750931 100644 --- a/src/main/java/org/asamk/signal/manager/PathConfig.java +++ b/src/main/java/org/asamk/signal/manager/PathConfig.java @@ -1,30 +1,34 @@ package org.asamk.signal.manager; +import java.io.File; + public class PathConfig { - private final String dataPath; - private final String attachmentsPath; - private final String avatarsPath; + private final File dataPath; + private final File attachmentsPath; + private final File avatarsPath; - public static PathConfig createDefault(final String settingsPath) { - return new PathConfig(settingsPath + "/data", settingsPath + "/attachments", settingsPath + "/avatars"); + public static PathConfig createDefault(final File settingsPath) { + return new PathConfig(new File(settingsPath, "/data"), + new File(settingsPath, "/attachments"), + new File(settingsPath, "/avatars")); } - private PathConfig(final String dataPath, final String attachmentsPath, final String avatarsPath) { + private PathConfig(final File dataPath, final File attachmentsPath, final File avatarsPath) { this.dataPath = dataPath; this.attachmentsPath = attachmentsPath; this.avatarsPath = avatarsPath; } public String getDataPath() { - return dataPath; + return dataPath.getPath(); } public String getAttachmentsPath() { - return attachmentsPath; + return attachmentsPath.getPath(); } public String getAvatarsPath() { - return avatarsPath; + return avatarsPath.getPath(); } } diff --git a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java index eb70b351..f81cfa49 100644 --- a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java +++ b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -31,6 +31,7 @@ import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; +import java.io.File; import java.io.IOException; import java.util.concurrent.TimeoutException; @@ -45,7 +46,7 @@ public class ProvisioningManager { private final int registrationId; private final String password; - public ProvisioningManager(String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) { + public ProvisioningManager(File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) { this.pathConfig = PathConfig.createDefault(settingsPath); this.serviceConfiguration = serviceConfiguration; this.userAgent = userAgent; diff --git a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 6fd35003..5a88bc66 100644 --- a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -6,6 +6,7 @@ import org.asamk.signal.manager.GroupIdV2; import org.asamk.signal.manager.GroupLinkPassword; import org.asamk.signal.manager.GroupUtils; import org.asamk.signal.storage.groups.GroupInfoV2; +import org.asamk.signal.storage.profiles.SignalProfile; import org.asamk.signal.util.IOUtils; import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.GroupChange; @@ -20,6 +21,8 @@ import org.signal.zkgroup.groups.GroupMasterKey; import org.signal.zkgroup.groups.GroupSecretParams; import org.signal.zkgroup.groups.UuidCiphertext; import org.signal.zkgroup.profiles.ProfileKeyCredential; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; @@ -44,6 +47,8 @@ import java.util.stream.Collectors; public class GroupHelper { + final static Logger logger = LoggerFactory.getLogger(GroupHelper.class); + private final ProfileKeyCredentialProvider profileKeyCredentialProvider; private final ProfileProvider profileProvider; @@ -78,7 +83,7 @@ public class GroupHelper { groupSecretParams); return groupsV2Api.getGroup(groupSecretParams, groupsV2AuthorizationString); } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { - System.err.println("Failed to retrieve Group V2 info, ignoring ..."); + logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage()); return null; } } @@ -111,11 +116,11 @@ public class GroupHelper { groupsV2Api.putNewGroup(newGroup, groupAuthForToday); decryptedGroup = groupsV2Api.getGroup(groupSecretParams, groupAuthForToday); } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { - System.err.println("Failed to create V2 group: " + e.getMessage()); + logger.warn("Failed to create V2 group: {}", e.getMessage()); return null; } if (decryptedGroup == null) { - System.err.println("Failed to create V2 group!"); + logger.warn("Failed to create V2 group, unknown error!"); return null; } @@ -141,7 +146,7 @@ public class GroupHelper { final ProfileKeyCredential profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential( selfAddressProvider.getSelfAddress()); if (profileKeyCredential == null) { - System.err.println("Cannot create a V2 group as self does not have a versioned profile"); + logger.warn("Cannot create a V2 group as self does not have a versioned profile"); return null; } @@ -165,22 +170,23 @@ public class GroupHelper { } private boolean areMembersValid(final Collection members) { - final int noUuidCapability = members.stream() + final Set noUuidCapability = members.stream() .filter(address -> !address.getUuid().isPresent()) - .collect(Collectors.toUnmodifiableSet()) - .size(); - if (noUuidCapability > 0) { - System.err.println("Cannot create a V2 group as " + noUuidCapability + " members don't have a UUID."); + .map(SignalServiceAddress::getLegacyIdentifier) + .collect(Collectors.toSet()); + if (noUuidCapability.size() > 0) { + logger.warn("Cannot create a V2 group as some members don't have a UUID: {}", + String.join(", ", noUuidCapability)); return false; } - final int noGv2Capability = members.stream() + final Set noGv2Capability = members.stream() .map(profileProvider::getProfile) .filter(profile -> profile != null && !profile.getCapabilities().gv2) - .collect(Collectors.toUnmodifiableSet()) - .size(); - if (noGv2Capability > 0) { - System.err.println("Cannot create a V2 group as " + noGv2Capability + " members don't support Groups V2."); + .collect(Collectors.toSet()); + if (noGv2Capability.size() > 0) { + logger.warn("Cannot create a V2 group as some members don't support Groups V2: {}", + noGv2Capability.stream().map(SignalProfile::getName).collect(Collectors.joining(", "))); return false; } @@ -219,7 +225,9 @@ public class GroupHelper { final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams); - if (!areMembersValid(newMembers)) return null; + if (!areMembersValid(newMembers)) { + throw new IOException("Failed to update group"); + } Set candidates = newMembers.stream() .map(member -> new GroupCandidate(member.getUuid().get(), diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index d3145ecd..e697efe3 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -29,6 +29,8 @@ import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.profiles.ProfileKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord; @@ -53,6 +55,8 @@ import java.util.stream.Collectors; public class SignalAccount implements Closeable { + final static Logger logger = LoggerFactory.getLogger(SignalAccount.class); + private final ObjectMapper jsonProcessor = new ObjectMapper(); private final FileChannel fileChannel; private final FileLock lock; @@ -357,7 +361,7 @@ public class SignalAccount implements Closeable { } } } catch (Exception e) { - System.err.println(String.format("Error saving file: %s", e.getMessage())); + logger.error("Error saving file: {}", e.getMessage()); } } @@ -365,9 +369,9 @@ public class SignalAccount implements Closeable { FileChannel fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel(); FileLock lock = fileChannel.tryLock(); if (lock == null) { - System.err.println("Config file is in use by another instance, waiting…"); + logger.info("Config file is in use by another instance, waiting…"); lock = fileChannel.lock(); - System.err.println("Config file lock acquired."); + logger.info("Config file lock acquired."); } return new Pair<>(fileChannel, lock); } diff --git a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java index 6f0f3c04..18bf5ed0 100644 --- a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java +++ b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java @@ -21,6 +21,8 @@ import org.asamk.signal.util.IOUtils; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.groups.GroupMasterKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.util.Base64; import java.io.File; @@ -35,6 +37,8 @@ import java.util.Map; public class JsonGroupStore { + final static Logger logger = LoggerFactory.getLogger(JsonGroupStore.class); + private static final ObjectMapper jsonProcessor = new ObjectMapper(); public File groupCachePath; @@ -63,7 +67,7 @@ public class JsonGroupStore { groupFileLegacy.delete(); } } catch (IOException e) { - System.err.println("Failed to cache group, ignoring ..."); + logger.warn("Failed to cache group, ignoring: {}", e.getMessage()); } } } diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java index 0095e6d2..29160cf1 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java +++ b/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java @@ -10,6 +10,8 @@ import com.fasterxml.jackson.databind.SerializerProvider; import org.asamk.signal.manager.TrustLevel; import org.asamk.signal.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.InvalidKeyException; @@ -27,6 +29,8 @@ import java.util.UUID; public class JsonIdentityKeyStore implements IdentityKeyStore { + final static Logger logger = LoggerFactory.getLogger(JsonIdentityKeyStore.class); + private final List identities = new ArrayList<>(); private final IdentityKeyPair identityKeyPair; @@ -219,7 +223,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { .asLong()) : new Date(); keyStore.saveIdentity(serviceAddress, id, trustLevel, added); } catch (InvalidKeyException | IOException e) { - System.out.println(String.format("Error while decoding key for: %s", trustedKeyName)); + logger.warn("Error while decoding key for {}: {}", trustedKeyName, e.getMessage()); } } } diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java index dea1996d..523809c1 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java +++ b/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java @@ -8,6 +8,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.InvalidKeyIdException; import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.PreKeyStore; @@ -19,6 +21,8 @@ import java.util.Map; class JsonPreKeyStore implements PreKeyStore { + final static Logger logger = LoggerFactory.getLogger(JsonPreKeyStore.class); + private final Map store = new HashMap<>(); public JsonPreKeyStore() { @@ -72,7 +76,7 @@ class JsonPreKeyStore implements PreKeyStore { try { preKeyMap.put(preKeyId, Base64.decode(preKey.get("record").asText())); } catch (IOException e) { - System.err.println(String.format("Error while decoding prekey for: %s", preKeyId)); + logger.warn("Error while decoding prekey for {}: {}", preKeyId, e.getMessage()); } } } diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java index fae72bae..24e4594e 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java +++ b/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.asamk.signal.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.libsignal.state.SessionStore; @@ -24,6 +26,8 @@ import java.util.UUID; class JsonSessionStore implements SessionStore { + final static Logger logger = LoggerFactory.getLogger(JsonSessionStore.class); + private final List sessions = new ArrayList<>(); private SignalServiceAddressResolver resolver; @@ -51,7 +55,7 @@ class JsonSessionStore implements SessionStore { try { return new SessionRecord(info.sessionRecord); } catch (IOException e) { - System.err.println("Failed to load session, resetting session: " + e); + logger.warn("Failed to load session, resetting session: {}", e.getMessage()); final SessionRecord sessionRecord = new SessionRecord(); info.sessionRecord = sessionRecord.serialize(); return sessionRecord; @@ -151,7 +155,7 @@ class JsonSessionStore implements SessionStore { SessionInfo sessionInfo = new SessionInfo(serviceAddress, deviceId, Base64.decode(record)); sessionStore.sessions.add(sessionInfo); } catch (IOException e) { - System.err.println(String.format("Error while decoding session for: %s", sessionName)); + logger.warn("Error while decoding session for {}: {}", sessionName, e.getMessage()); } } } diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java b/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java index 255dd4e0..7accf5a1 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java +++ b/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java @@ -8,6 +8,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.InvalidKeyIdException; import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyStore; @@ -21,6 +23,8 @@ import java.util.Map; class JsonSignedPreKeyStore implements SignedPreKeyStore { + final static Logger logger = LoggerFactory.getLogger(JsonSignedPreKeyStore.class); + private final Map store = new HashMap<>(); public JsonSignedPreKeyStore() { @@ -89,7 +93,7 @@ class JsonSignedPreKeyStore implements SignedPreKeyStore { try { preKeyMap.put(preKeyId, Base64.decode(preKey.get("record").asText())); } catch (IOException e) { - System.err.println(String.format("Error while decoding prekey for: %s", preKeyId)); + logger.warn("Error while decoding prekey for {}: {}", preKeyId, e.getMessage()); } } } -- 2.51.0 From 22f19c406779893d08675c2d06d2b7708cc3f2a8 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 25 Dec 2020 23:07:36 +0100 Subject: [PATCH 11/16] Use File instead of String --- src/main/java/org/asamk/signal/Main.java | 8 ++-- .../commands/UploadStickerPackCommand.java | 3 +- .../org/asamk/signal/manager/Manager.java | 21 +++++----- .../org/asamk/signal/manager/PathConfig.java | 18 ++++---- .../signal/manager/UserAlreadyExists.java | 8 ++-- .../asamk/signal/storage/SignalAccount.java | 42 +++++++++++-------- .../java/org/asamk/signal/util/IOUtils.java | 15 +++---- 7 files changed, 61 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 97e3f9b2..6204778d 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -212,17 +212,19 @@ public class Main { * @return the data directory to be used by signal-cli. */ private static File getDefaultDataPath() { - File dataPath = new File(IOUtils.getDataHomeDir(), "/signal-cli"); + File dataPath = new File(IOUtils.getDataHomeDir(), "signal-cli"); if (dataPath.exists()) { return dataPath; } - File legacySettingsPath = new File(System.getProperty("user.home"), "/.config/signal"); + File configPath = new File(System.getProperty("user.home"), ".config"); + + File legacySettingsPath = new File(configPath, "signal"); if (legacySettingsPath.exists()) { return legacySettingsPath; } - legacySettingsPath = new File(System.getProperty("user.home"), "/.config/textsecure"); + legacySettingsPath = new File(configPath, "textsecure"); if (legacySettingsPath.exists()) { return legacySettingsPath; } diff --git a/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java b/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java index 77df2b22..f9f5d95b 100644 --- a/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java +++ b/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java @@ -6,6 +6,7 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.StickerPackInvalidException; +import java.io.File; import java.io.IOException; public class UploadStickerPackCommand implements LocalCommand { @@ -19,7 +20,7 @@ public class UploadStickerPackCommand implements LocalCommand { @Override public int handleCommand(final Namespace ns, final Manager m) { try { - String path = ns.getString("path"); + File path = new File(ns.getString("path")); String url = m.uploadStickerPack(path); System.out.println(url); return 0; diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index c679449d..761905df 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -259,22 +259,22 @@ public class Manager implements Closeable { return account.getDeviceId(); } - private String getMessageCachePath() { - return pathConfig.getDataPath() + "/" + account.getUsername() + ".d/msg-cache"; + private File getMessageCachePath() { + return SignalAccount.getMessageCachePath(pathConfig.getDataPath(), account.getUsername()); } - private String getMessageCachePath(String sender) { + private File getMessageCachePath(String sender) { if (sender == null || sender.isEmpty()) { return getMessageCachePath(); } - return getMessageCachePath() + "/" + sender.replace("/", "_"); + return new File(getMessageCachePath(), sender.replace("/", "_")); } private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException { - String cachePath = getMessageCachePath(sender); + File cachePath = getMessageCachePath(sender); IOUtils.createPrivateDirectories(cachePath); - return new File(cachePath + "/" + now + "_" + timestamp); + return new File(cachePath, now + "_" + timestamp); } public static Manager init( @@ -1161,7 +1161,7 @@ public class Manager implements Closeable { * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file * @return if successful, returns the URL to install the sticker pack in the signal app */ - public String uploadStickerPack(String path) throws IOException, StickerPackInvalidException { + public String uploadStickerPack(File path) throws IOException, StickerPackInvalidException { SignalServiceStickerManifestUpload manifest = getSignalServiceStickerManifestUpload(path); SignalServiceMessageSender messageSender = createMessageSender(); @@ -1186,12 +1186,11 @@ public class Manager implements Closeable { } private SignalServiceStickerManifestUpload getSignalServiceStickerManifestUpload( - final String path + final File file ) throws IOException, StickerPackInvalidException { ZipFile zip = null; String rootPath = null; - final File file = new File(path); if (file.getName().endsWith(".zip")) { zip = new ZipFile(file); } else if (file.getName().equals("manifest.json")) { @@ -1788,7 +1787,7 @@ public class Manager implements Closeable { private void retryFailedReceivedMessages( ReceiveMessageHandler handler, boolean ignoreAttachments ) { - final File cachePath = new File(getMessageCachePath()); + final File cachePath = getMessageCachePath(); if (!cachePath.exists()) { return; } @@ -1954,7 +1953,7 @@ public class Manager implements Closeable { cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp()); Files.delete(cacheFile.toPath()); // Try to delete directory if empty - new File(getMessageCachePath()).delete(); + getMessageCachePath().delete(); } catch (IOException e) { logger.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile, e.getMessage()); } diff --git a/src/main/java/org/asamk/signal/manager/PathConfig.java b/src/main/java/org/asamk/signal/manager/PathConfig.java index ca750931..d96034df 100644 --- a/src/main/java/org/asamk/signal/manager/PathConfig.java +++ b/src/main/java/org/asamk/signal/manager/PathConfig.java @@ -9,9 +9,9 @@ public class PathConfig { private final File avatarsPath; public static PathConfig createDefault(final File settingsPath) { - return new PathConfig(new File(settingsPath, "/data"), - new File(settingsPath, "/attachments"), - new File(settingsPath, "/avatars")); + return new PathConfig(new File(settingsPath, "data"), + new File(settingsPath, "attachments"), + new File(settingsPath, "avatars")); } private PathConfig(final File dataPath, final File attachmentsPath, final File avatarsPath) { @@ -20,15 +20,15 @@ public class PathConfig { this.avatarsPath = avatarsPath; } - public String getDataPath() { - return dataPath.getPath(); + public File getDataPath() { + return dataPath; } - public String getAttachmentsPath() { - return attachmentsPath.getPath(); + public File getAttachmentsPath() { + return attachmentsPath; } - public String getAvatarsPath() { - return avatarsPath.getPath(); + public File getAvatarsPath() { + return avatarsPath; } } diff --git a/src/main/java/org/asamk/signal/manager/UserAlreadyExists.java b/src/main/java/org/asamk/signal/manager/UserAlreadyExists.java index a07c455b..d506f0c6 100644 --- a/src/main/java/org/asamk/signal/manager/UserAlreadyExists.java +++ b/src/main/java/org/asamk/signal/manager/UserAlreadyExists.java @@ -1,11 +1,13 @@ package org.asamk.signal.manager; +import java.io.File; + public class UserAlreadyExists extends Exception { private final String username; - private final String fileName; + private final File fileName; - public UserAlreadyExists(String username, String fileName) { + public UserAlreadyExists(String username, File fileName) { this.username = username; this.fileName = fileName; } @@ -14,7 +16,7 @@ public class UserAlreadyExists extends Exception { return username; } - public String getFileName() { + public File getFileName() { return fileName; } } diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index e697efe3..3af52708 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -90,8 +90,8 @@ public class SignalAccount implements Closeable { jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); } - public static SignalAccount load(String dataPath, String username) throws IOException { - final String fileName = getFileName(dataPath, username); + public static SignalAccount load(File dataPath, String username) throws IOException { + final File fileName = getFileName(dataPath, username); final Pair pair = openFileChannel(fileName); try { SignalAccount account = new SignalAccount(pair.first(), pair.second()); @@ -105,11 +105,11 @@ public class SignalAccount implements Closeable { } public static SignalAccount create( - String dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey + File dataPath, String username, IdentityKeyPair identityKey, int registrationId, ProfileKey profileKey ) throws IOException { IOUtils.createPrivateDirectories(dataPath); - String fileName = getFileName(dataPath, username); - if (!new File(fileName).exists()) { + File fileName = getFileName(dataPath, username); + if (!fileName.exists()) { IOUtils.createPrivateFile(fileName); } @@ -130,7 +130,7 @@ public class SignalAccount implements Closeable { } public static SignalAccount createLinkedAccount( - String dataPath, + File dataPath, String username, UUID uuid, String password, @@ -141,8 +141,8 @@ public class SignalAccount implements Closeable { ProfileKey profileKey ) throws IOException { IOUtils.createPrivateDirectories(dataPath); - String fileName = getFileName(dataPath, username); - if (!new File(fileName).exists()) { + File fileName = getFileName(dataPath, username); + if (!fileName.exists()) { IOUtils.createPrivateFile(fileName); } @@ -167,23 +167,31 @@ public class SignalAccount implements Closeable { return account; } - public static String getFileName(String dataPath, String username) { - return dataPath + "/" + username; + public static File getFileName(File dataPath, String username) { + return new File(dataPath, username); } - private static File getGroupCachePath(String dataPath, String username) { - return new File(new File(dataPath, username + ".d"), "group-cache"); + private static File getUserPath(final File dataPath, final String username) { + return new File(dataPath, username + ".d"); } - public static boolean userExists(String dataPath, String username) { + public static File getMessageCachePath(File dataPath, String username) { + return new File(getUserPath(dataPath, username), "msg-cache"); + } + + private static File getGroupCachePath(File dataPath, String username) { + return new File(getUserPath(dataPath, username), "group-cache"); + } + + public static boolean userExists(File dataPath, String username) { if (username == null) { return false; } - File f = new File(getFileName(dataPath, username)); + File f = getFileName(dataPath, username); return !(!f.exists() || f.isDirectory()); } - private void load(String dataPath) throws IOException { + private void load(File dataPath) throws IOException { JsonNode rootNode; synchronized (fileChannel) { fileChannel.position(0); @@ -365,8 +373,8 @@ public class SignalAccount implements Closeable { } } - private static Pair openFileChannel(String fileName) throws IOException { - FileChannel fileChannel = new RandomAccessFile(new File(fileName), "rw").getChannel(); + private static Pair openFileChannel(File fileName) throws IOException { + FileChannel fileChannel = new RandomAccessFile(fileName, "rw").getChannel(); FileLock lock = fileChannel.tryLock(); if (lock == null) { logger.info("Config file is in use by another instance, waiting…"); diff --git a/src/main/java/org/asamk/signal/util/IOUtils.java b/src/main/java/org/asamk/signal/util/IOUtils.java index 4d8adea6..59727a9a 100644 --- a/src/main/java/org/asamk/signal/util/IOUtils.java +++ b/src/main/java/org/asamk/signal/util/IOUtils.java @@ -46,11 +46,6 @@ public class IOUtils { return baos.toByteArray(); } - public static void createPrivateDirectories(String directoryPath) throws IOException { - final File file = new File(directoryPath); - createPrivateDirectories(file); - } - public static void createPrivateDirectories(File file) throws IOException { if (file.exists()) { return; @@ -65,8 +60,8 @@ public class IOUtils { } } - public static void createPrivateFile(String path) throws IOException { - final Path file = new File(path).toPath(); + public static void createPrivateFile(File path) throws IOException { + final Path file = path.toPath(); try { Set perms = EnumSet.of(OWNER_READ, OWNER_WRITE); Files.createFile(file, PosixFilePermissions.asFileAttribute(perms)); @@ -75,13 +70,13 @@ public class IOUtils { } } - public static String getDataHomeDir() { + public static File getDataHomeDir() { String dataHome = System.getenv("XDG_DATA_HOME"); if (dataHome != null) { - return dataHome; + return new File(dataHome); } - return 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 { -- 2.51.0 From 9e6a3534275d5bac8454792e05280f13fa5ef13c Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 29 Dec 2020 22:09:06 +0100 Subject: [PATCH 12/16] Move group classes to separate package --- .../asamk/signal/JsonDbusReceiveMessageHandler.java | 2 +- .../java/org/asamk/signal/ReceiveMessageHandler.java | 4 ++-- .../java/org/asamk/signal/commands/BlockCommand.java | 6 +++--- .../org/asamk/signal/commands/JoinGroupCommand.java | 4 ++-- .../org/asamk/signal/commands/ListGroupsCommand.java | 2 +- .../org/asamk/signal/commands/QuitGroupCommand.java | 8 ++++---- .../java/org/asamk/signal/commands/SendCommand.java | 2 +- .../org/asamk/signal/commands/SendReactionCommand.java | 8 ++++---- .../java/org/asamk/signal/commands/UnblockCommand.java | 6 +++--- .../org/asamk/signal/commands/UpdateGroupCommand.java | 2 +- .../java/org/asamk/signal/dbus/DbusSignalImpl.java | 6 +++--- src/main/java/org/asamk/signal/json/JsonGroupInfo.java | 2 +- .../java/org/asamk/signal/manager/HandleAction.java | 1 + src/main/java/org/asamk/signal/manager/KeyUtils.java | 4 ++-- src/main/java/org/asamk/signal/manager/Manager.java | 7 +++++++ .../org/asamk/signal/manager/{ => groups}/GroupId.java | 2 +- .../manager/{ => groups}/GroupIdFormatException.java | 2 +- .../asamk/signal/manager/{ => groups}/GroupIdV1.java | 2 +- .../asamk/signal/manager/{ => groups}/GroupIdV2.java | 2 +- .../manager/{ => groups}/GroupInviteLinkUrl.java | 2 +- .../signal/manager/{ => groups}/GroupLinkPassword.java | 4 +++- .../manager/{ => groups}/GroupNotFoundException.java | 2 +- .../asamk/signal/manager/{ => groups}/GroupUtils.java | 2 +- .../manager/{ => groups}/NotAGroupMemberException.java | 2 +- .../org/asamk/signal/manager/helper/GroupHelper.java | 6 +++--- .../java/org/asamk/signal/storage/SignalAccount.java | 2 +- .../org/asamk/signal/storage/groups/GroupInfo.java | 4 ++-- .../org/asamk/signal/storage/groups/GroupInfoV1.java | 10 +++++----- .../org/asamk/signal/storage/groups/GroupInfoV2.java | 4 ++-- .../asamk/signal/storage/groups/JsonGroupStore.java | 8 ++++---- src/main/java/org/asamk/signal/util/ErrorUtils.java | 6 +++--- src/main/java/org/asamk/signal/util/Util.java | 4 ++-- 32 files changed, 69 insertions(+), 59 deletions(-) rename src/main/java/org/asamk/signal/manager/{ => groups}/GroupId.java (97%) rename src/main/java/org/asamk/signal/manager/{ => groups}/GroupIdFormatException.java (85%) rename src/main/java/org/asamk/signal/manager/{ => groups}/GroupIdV1.java (87%) rename src/main/java/org/asamk/signal/manager/{ => groups}/GroupIdV2.java (86%) rename src/main/java/org/asamk/signal/manager/{ => groups}/GroupInviteLinkUrl.java (99%) rename src/main/java/org/asamk/signal/manager/{ => groups}/GroupLinkPassword.java (90%) rename src/main/java/org/asamk/signal/manager/{ => groups}/GroupNotFoundException.java (81%) rename src/main/java/org/asamk/signal/manager/{ => groups}/GroupUtils.java (98%) rename src/main/java/org/asamk/signal/manager/{ => groups}/NotAGroupMemberException.java (85%) diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index 50eb9f9b..0cffd7b1 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -1,8 +1,8 @@ package org.asamk.signal; import org.asamk.Signal; -import org.asamk.signal.manager.GroupUtils; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.groups.GroupUtils; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 99010e13..8dc38e4f 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -1,8 +1,8 @@ package org.asamk.signal; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupUtils; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.util.DateUtils; diff --git a/src/main/java/org/asamk/signal/commands/BlockCommand.java b/src/main/java/org/asamk/signal/commands/BlockCommand.java index 2a9bc4e9..627be5a9 100644 --- a/src/main/java/org/asamk/signal/commands/BlockCommand.java +++ b/src/main/java/org/asamk/signal/commands/BlockCommand.java @@ -3,10 +3,10 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdFormatException; -import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.util.Util; import org.whispersystems.signalservice.api.util.InvalidNumberException; diff --git a/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java b/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java index 8438e1fa..305bf55b 100644 --- a/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java @@ -4,9 +4,9 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.Signal; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupInviteLinkUrl; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; diff --git a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java index 4d1032a2..66ff3a00 100644 --- a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java @@ -4,8 +4,8 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.signal.manager.GroupInviteLinkUrl; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.asamk.signal.storage.groups.GroupInfo; import org.whispersystems.signalservice.api.push.SignalServiceAddress; diff --git a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java index efc63f8f..c1f6bfbf 100644 --- a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java @@ -3,11 +3,11 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdFormatException; -import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; -import org.asamk.signal.manager.NotAGroupMemberException; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupNotFoundException; +import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.util.Util; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.messages.SendMessageResult; diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 04b06434..ee51dd91 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -5,7 +5,7 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.Signal; -import org.asamk.signal.manager.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupIdFormatException; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; import org.freedesktop.dbus.exceptions.DBusExecutionException; diff --git a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java index 345c9180..c680bfd7 100644 --- a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java @@ -4,11 +4,11 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdFormatException; -import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; -import org.asamk.signal.manager.NotAGroupMemberException; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupNotFoundException; +import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.util.Util; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.api.messages.SendMessageResult; diff --git a/src/main/java/org/asamk/signal/commands/UnblockCommand.java b/src/main/java/org/asamk/signal/commands/UnblockCommand.java index 73e578ac..14ea2996 100644 --- a/src/main/java/org/asamk/signal/commands/UnblockCommand.java +++ b/src/main/java/org/asamk/signal/commands/UnblockCommand.java @@ -3,10 +3,10 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdFormatException; -import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.util.Util; import org.whispersystems.signalservice.api.util.InvalidNumberException; diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java index dae06b86..a6f40ef2 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -4,7 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.Signal; -import org.asamk.signal.manager.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupIdFormatException; import org.asamk.signal.util.Util; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.whispersystems.util.Base64; diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index cbb72835..df3f12f2 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -2,10 +2,10 @@ package org.asamk.signal.dbus; import org.asamk.Signal; import org.asamk.signal.manager.AttachmentInvalidException; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupNotFoundException; import org.asamk.signal.manager.Manager; -import org.asamk.signal.manager.NotAGroupMemberException; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupNotFoundException; +import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.util.ErrorUtils; import org.freedesktop.dbus.exceptions.DBusExecutionException; diff --git a/src/main/java/org/asamk/signal/json/JsonGroupInfo.java b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java index 9709be20..79967955 100644 --- a/src/main/java/org/asamk/signal/json/JsonGroupInfo.java +++ b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java @@ -1,6 +1,6 @@ package org.asamk.signal.json; -import org.asamk.signal.manager.GroupUtils; +import org.asamk.signal.manager.groups.GroupUtils; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.push.SignalServiceAddress; diff --git a/src/main/java/org/asamk/signal/manager/HandleAction.java b/src/main/java/org/asamk/signal/manager/HandleAction.java index aa25d8c5..0dd151a9 100644 --- a/src/main/java/org/asamk/signal/manager/HandleAction.java +++ b/src/main/java/org/asamk/signal/manager/HandleAction.java @@ -1,5 +1,6 @@ package org.asamk.signal.manager; +import org.asamk.signal.manager.groups.GroupIdV1; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.Objects; diff --git a/src/main/java/org/asamk/signal/manager/KeyUtils.java b/src/main/java/org/asamk/signal/manager/KeyUtils.java index 21f6037f..6ac093db 100644 --- a/src/main/java/org/asamk/signal/manager/KeyUtils.java +++ b/src/main/java/org/asamk/signal/manager/KeyUtils.java @@ -5,7 +5,7 @@ import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.profiles.ProfileKey; import org.whispersystems.util.Base64; -class KeyUtils { +public class KeyUtils { private KeyUtils() { } @@ -35,7 +35,7 @@ class KeyUtils { return Base64.encodeBytes(secret); } - static byte[] getSecretBytes(int size) { + public static byte[] getSecretBytes(int size) { byte[] secret = new byte[size]; RandomUtils.getSecureRandom().nextBytes(secret); return secret; diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 761905df..c958e0a4 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -18,6 +18,13 @@ package org.asamk.signal.manager; import com.fasterxml.jackson.databind.ObjectMapper; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupIdV1; +import org.asamk.signal.manager.groups.GroupIdV2; +import org.asamk.signal.manager.groups.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupNotFoundException; +import org.asamk.signal.manager.groups.GroupUtils; +import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.helper.GroupHelper; import org.asamk.signal.manager.helper.ProfileHelper; import org.asamk.signal.manager.helper.UnidentifiedAccessHelper; diff --git a/src/main/java/org/asamk/signal/manager/GroupId.java b/src/main/java/org/asamk/signal/manager/groups/GroupId.java similarity index 97% rename from src/main/java/org/asamk/signal/manager/GroupId.java rename to src/main/java/org/asamk/signal/manager/groups/GroupId.java index 34e18e8e..9a15de65 100644 --- a/src/main/java/org/asamk/signal/manager/GroupId.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupId.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; import org.whispersystems.util.Base64; diff --git a/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java b/src/main/java/org/asamk/signal/manager/groups/GroupIdFormatException.java similarity index 85% rename from src/main/java/org/asamk/signal/manager/GroupIdFormatException.java rename to src/main/java/org/asamk/signal/manager/groups/GroupIdFormatException.java index 83afd15b..8050da22 100644 --- a/src/main/java/org/asamk/signal/manager/GroupIdFormatException.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupIdFormatException.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; public class GroupIdFormatException extends Exception { diff --git a/src/main/java/org/asamk/signal/manager/GroupIdV1.java b/src/main/java/org/asamk/signal/manager/groups/GroupIdV1.java similarity index 87% rename from src/main/java/org/asamk/signal/manager/GroupIdV1.java rename to src/main/java/org/asamk/signal/manager/groups/GroupIdV1.java index 40862f07..d865356e 100644 --- a/src/main/java/org/asamk/signal/manager/GroupIdV1.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupIdV1.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; import static org.asamk.signal.manager.KeyUtils.getSecretBytes; diff --git a/src/main/java/org/asamk/signal/manager/GroupIdV2.java b/src/main/java/org/asamk/signal/manager/groups/GroupIdV2.java similarity index 86% rename from src/main/java/org/asamk/signal/manager/GroupIdV2.java rename to src/main/java/org/asamk/signal/manager/groups/GroupIdV2.java index b329be1d..913a9e93 100644 --- a/src/main/java/org/asamk/signal/manager/GroupIdV2.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupIdV2.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; import java.util.Base64; diff --git a/src/main/java/org/asamk/signal/manager/GroupInviteLinkUrl.java b/src/main/java/org/asamk/signal/manager/groups/GroupInviteLinkUrl.java similarity index 99% rename from src/main/java/org/asamk/signal/manager/GroupInviteLinkUrl.java rename to src/main/java/org/asamk/signal/manager/groups/GroupInviteLinkUrl.java index 67ce7892..bf9e0e55 100644 --- a/src/main/java/org/asamk/signal/manager/GroupInviteLinkUrl.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupInviteLinkUrl.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; import com.google.protobuf.ByteString; diff --git a/src/main/java/org/asamk/signal/manager/GroupLinkPassword.java b/src/main/java/org/asamk/signal/manager/groups/GroupLinkPassword.java similarity index 90% rename from src/main/java/org/asamk/signal/manager/GroupLinkPassword.java rename to src/main/java/org/asamk/signal/manager/groups/GroupLinkPassword.java index 38e2aaf4..41be672a 100644 --- a/src/main/java/org/asamk/signal/manager/GroupLinkPassword.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupLinkPassword.java @@ -1,4 +1,6 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; + +import org.asamk.signal.manager.KeyUtils; import java.util.Arrays; diff --git a/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java b/src/main/java/org/asamk/signal/manager/groups/GroupNotFoundException.java similarity index 81% rename from src/main/java/org/asamk/signal/manager/GroupNotFoundException.java rename to src/main/java/org/asamk/signal/manager/groups/GroupNotFoundException.java index d7efa923..0fc0c444 100644 --- a/src/main/java/org/asamk/signal/manager/GroupNotFoundException.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupNotFoundException.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; public class GroupNotFoundException extends Exception { diff --git a/src/main/java/org/asamk/signal/manager/GroupUtils.java b/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java similarity index 98% rename from src/main/java/org/asamk/signal/manager/GroupUtils.java rename to src/main/java/org/asamk/signal/manager/groups/GroupUtils.java index d86dfbe9..c5f727e1 100644 --- a/src/main/java/org/asamk/signal/manager/GroupUtils.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.storage.groups.GroupInfoV1; diff --git a/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java b/src/main/java/org/asamk/signal/manager/groups/NotAGroupMemberException.java similarity index 85% rename from src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java rename to src/main/java/org/asamk/signal/manager/groups/NotAGroupMemberException.java index 2c9b3f33..08cbcacd 100644 --- a/src/main/java/org/asamk/signal/manager/NotAGroupMemberException.java +++ b/src/main/java/org/asamk/signal/manager/groups/NotAGroupMemberException.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.groups; public class NotAGroupMemberException extends Exception { diff --git a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 5a88bc66..6a52e00e 100644 --- a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -2,9 +2,9 @@ package org.asamk.signal.manager.helper; import com.google.protobuf.InvalidProtocolBufferException; -import org.asamk.signal.manager.GroupIdV2; -import org.asamk.signal.manager.GroupLinkPassword; -import org.asamk.signal.manager.GroupUtils; +import org.asamk.signal.manager.groups.GroupIdV2; +import org.asamk.signal.manager.groups.GroupLinkPassword; +import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.storage.groups.GroupInfoV2; import org.asamk.signal.storage.profiles.SignalProfile; import org.asamk.signal.util.IOUtils; diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index 3af52708..393d0449 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -10,7 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.asamk.signal.manager.GroupId; +import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.contacts.JsonContactsStore; import org.asamk.signal.storage.groups.GroupInfo; diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java index 40b8c884..fe725141 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java +++ b/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java @@ -2,8 +2,8 @@ package org.asamk.signal.storage.groups; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.Set; diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java index 90b26b81..e48fe297 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java +++ b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java @@ -13,11 +13,11 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdV1; -import org.asamk.signal.manager.GroupIdV2; -import org.asamk.signal.manager.GroupInviteLinkUrl; -import org.asamk.signal.manager.GroupUtils; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupIdV1; +import org.asamk.signal.manager.groups.GroupIdV2; +import org.asamk.signal.manager.groups.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupUtils; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.io.IOException; diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java index 1b00caaa..0139f879 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java +++ b/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java @@ -1,7 +1,7 @@ package org.asamk.signal.storage.groups; -import org.asamk.signal.manager.GroupIdV2; -import org.asamk.signal.manager.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupIdV2; +import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.zkgroup.groups.GroupMasterKey; diff --git a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java index 18bf5ed0..1aae49f7 100644 --- a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java +++ b/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java @@ -12,10 +12,10 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdV1; -import org.asamk.signal.manager.GroupIdV2; -import org.asamk.signal.manager.GroupUtils; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupIdV1; +import org.asamk.signal.manager.groups.GroupIdV2; +import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.util.Hex; import org.asamk.signal.util.IOUtils; import org.signal.storageservice.protos.groups.local.DecryptedGroup; diff --git a/src/main/java/org/asamk/signal/util/ErrorUtils.java b/src/main/java/org/asamk/signal/util/ErrorUtils.java index 44d505be..e9553f98 100644 --- a/src/main/java/org/asamk/signal/util/ErrorUtils.java +++ b/src/main/java/org/asamk/signal/util/ErrorUtils.java @@ -1,8 +1,8 @@ package org.asamk.signal.util; -import org.asamk.signal.manager.GroupIdFormatException; -import org.asamk.signal.manager.GroupNotFoundException; -import org.asamk.signal.manager.NotAGroupMemberException; +import org.asamk.signal.manager.groups.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupNotFoundException; +import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.util.InvalidNumberException; diff --git a/src/main/java/org/asamk/signal/util/Util.java b/src/main/java/org/asamk/signal/util/Util.java index 3cd5619a..79a6587b 100644 --- a/src/main/java/org/asamk/signal/util/Util.java +++ b/src/main/java/org/asamk/signal/util/Util.java @@ -2,8 +2,8 @@ package org.asamk.signal.util; import com.fasterxml.jackson.databind.JsonNode; -import org.asamk.signal.manager.GroupId; -import org.asamk.signal.manager.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupId; +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; -- 2.51.0 From b738f5740c94fe7a5df9e322e1345a99ef0c5ce5 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 29 Dec 2020 22:15:38 +0100 Subject: [PATCH 13/16] Move storage package to manager --- .../asamk/signal/ReceiveMessageHandler.java | 4 +- .../signal/commands/ListContactsCommand.java | 2 +- .../signal/commands/ListGroupsCommand.java | 2 +- .../commands/ListIdentitiesCommand.java | 10 +-- .../org/asamk/signal/dbus/DbusSignalImpl.java | 2 +- .../org/asamk/signal/manager/Manager.java | 37 +++++---- .../signal/manager/ProvisioningManager.java | 2 +- .../signal/manager/groups/GroupUtils.java | 6 +- .../signal/manager/helper/GroupHelper.java | 4 +- .../manager/helper/ProfileProvider.java | 2 +- .../helper/UnidentifiedAccessHelper.java | 2 +- .../{ => manager}/storage/SignalAccount.java | 32 ++++---- .../storage/contacts/ContactInfo.java | 2 +- .../storage/contacts/JsonContactsStore.java | 2 +- .../storage/groups/GroupInfo.java | 2 +- .../storage/groups/GroupInfoV1.java | 2 +- .../storage/groups/GroupInfoV2.java | 2 +- .../storage/groups/JsonGroupStore.java | 2 +- .../storage/profiles/ProfileStore.java | 2 +- .../storage/profiles/SignalProfile.java | 2 +- .../storage/profiles/SignalProfileEntry.java | 2 +- .../storage/protocol/IdentityInfo.java | 57 +++++++++++++ .../protocol/JsonIdentityKeyStore.java | 81 ++++--------------- .../storage/protocol/JsonPreKeyStore.java | 2 +- .../storage/protocol/JsonSessionStore.java | 2 +- .../protocol/JsonSignalProtocolStore.java | 8 +- .../protocol/JsonSignedPreKeyStore.java | 2 +- .../storage/protocol/RecipientStore.java | 2 +- .../storage/protocol/SessionInfo.java | 2 +- .../SignalServiceAddressResolver.java | 2 +- .../storage/stickers/Sticker.java | 2 +- .../storage/stickers/StickerStore.java | 2 +- .../threads/LegacyJsonThreadStore.java | 2 +- .../storage/threads/ThreadInfo.java | 2 +- 34 files changed, 148 insertions(+), 141 deletions(-) rename src/main/java/org/asamk/signal/{ => manager}/storage/SignalAccount.java (94%) rename src/main/java/org/asamk/signal/{ => manager}/storage/contacts/ContactInfo.java (95%) rename src/main/java/org/asamk/signal/{ => manager}/storage/contacts/JsonContactsStore.java (96%) rename src/main/java/org/asamk/signal/{ => manager}/storage/groups/GroupInfo.java (97%) rename src/main/java/org/asamk/signal/{ => manager}/storage/groups/GroupInfoV1.java (99%) rename src/main/java/org/asamk/signal/{ => manager}/storage/groups/GroupInfoV2.java (98%) rename src/main/java/org/asamk/signal/{ => manager}/storage/groups/JsonGroupStore.java (99%) rename src/main/java/org/asamk/signal/{ => manager}/storage/profiles/ProfileStore.java (99%) rename src/main/java/org/asamk/signal/{ => manager}/storage/profiles/SignalProfile.java (98%) rename src/main/java/org/asamk/signal/{ => manager}/storage/profiles/SignalProfileEntry.java (96%) create mode 100644 src/main/java/org/asamk/signal/manager/storage/protocol/IdentityInfo.java rename src/main/java/org/asamk/signal/{ => manager}/storage/protocol/JsonIdentityKeyStore.java (80%) rename src/main/java/org/asamk/signal/{ => manager}/storage/protocol/JsonPreKeyStore.java (98%) rename src/main/java/org/asamk/signal/{ => manager}/storage/protocol/JsonSessionStore.java (99%) rename src/main/java/org/asamk/signal/{ => manager}/storage/protocol/JsonSignalProtocolStore.java (95%) rename src/main/java/org/asamk/signal/{ => manager}/storage/protocol/JsonSignedPreKeyStore.java (98%) rename src/main/java/org/asamk/signal/{ => manager}/storage/protocol/RecipientStore.java (98%) rename src/main/java/org/asamk/signal/{ => manager}/storage/protocol/SessionInfo.java (89%) rename src/main/java/org/asamk/signal/{ => manager}/storage/protocol/SignalServiceAddressResolver.java (88%) rename src/main/java/org/asamk/signal/{ => manager}/storage/stickers/Sticker.java (93%) rename src/main/java/org/asamk/signal/{ => manager}/storage/stickers/StickerStore.java (98%) rename src/main/java/org/asamk/signal/{ => manager}/storage/threads/LegacyJsonThreadStore.java (97%) rename src/main/java/org/asamk/signal/{ => manager}/storage/threads/ThreadInfo.java (78%) diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 8dc38e4f..db78f454 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -3,8 +3,8 @@ package org.asamk.signal; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupUtils; -import org.asamk.signal.storage.contacts.ContactInfo; -import org.asamk.signal.storage.groups.GroupInfo; +import org.asamk.signal.manager.storage.contacts.ContactInfo; +import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.util.DateUtils; import org.asamk.signal.util.Util; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; diff --git a/src/main/java/org/asamk/signal/commands/ListContactsCommand.java b/src/main/java/org/asamk/signal/commands/ListContactsCommand.java index 24d6898c..2c98ec6b 100644 --- a/src/main/java/org/asamk/signal/commands/ListContactsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListContactsCommand.java @@ -4,7 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.manager.Manager; -import org.asamk.signal.storage.contacts.ContactInfo; +import org.asamk.signal.manager.storage.contacts.ContactInfo; import java.util.List; diff --git a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java index 66ff3a00..b4be4ad0 100644 --- a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java @@ -6,7 +6,7 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; -import org.asamk.signal.storage.groups.GroupInfo; +import org.asamk.signal.manager.storage.groups.GroupInfo; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.List; diff --git a/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java b/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java index a75e4328..3f422cbd 100644 --- a/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java @@ -4,7 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.manager.Manager; -import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; +import org.asamk.signal.manager.storage.protocol.IdentityInfo; import org.asamk.signal.util.Hex; import org.asamk.signal.util.Util; import org.whispersystems.signalservice.api.util.InvalidNumberException; @@ -13,7 +13,7 @@ import java.util.List; public class ListIdentitiesCommand implements LocalCommand { - private static void printIdentityFingerprint(Manager m, JsonIdentityKeyStore.Identity theirId) { + private static void printIdentityFingerprint(Manager m, IdentityInfo theirId) { String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirId.getAddress(), theirId.getIdentityKey())); System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirId.getAddress().getNumber().orNull(), @@ -35,14 +35,14 @@ public class ListIdentitiesCommand implements LocalCommand { return 1; } if (ns.get("number") == null) { - for (JsonIdentityKeyStore.Identity identity : m.getIdentities()) { + for (IdentityInfo identity : m.getIdentities()) { printIdentityFingerprint(m, identity); } } else { String number = ns.getString("number"); try { - List identities = m.getIdentities(number); - for (JsonIdentityKeyStore.Identity id : identities) { + List identities = m.getIdentities(number); + for (IdentityInfo id : identities) { printIdentityFingerprint(m, id); } } catch (InvalidNumberException e) { diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index df3f12f2..d19116a4 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -6,7 +6,7 @@ import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.NotAGroupMemberException; -import org.asamk.signal.storage.groups.GroupInfo; +import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.util.ErrorUtils; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.whispersystems.libsignal.util.Pair; diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index c958e0a4..e00f44ac 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -28,15 +28,15 @@ import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.helper.GroupHelper; import org.asamk.signal.manager.helper.ProfileHelper; import org.asamk.signal.manager.helper.UnidentifiedAccessHelper; -import org.asamk.signal.storage.SignalAccount; -import org.asamk.signal.storage.contacts.ContactInfo; -import org.asamk.signal.storage.groups.GroupInfo; -import org.asamk.signal.storage.groups.GroupInfoV1; -import org.asamk.signal.storage.groups.GroupInfoV2; -import org.asamk.signal.storage.profiles.SignalProfile; -import org.asamk.signal.storage.profiles.SignalProfileEntry; -import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; -import org.asamk.signal.storage.stickers.Sticker; +import org.asamk.signal.manager.storage.SignalAccount; +import org.asamk.signal.manager.storage.contacts.ContactInfo; +import org.asamk.signal.manager.storage.groups.GroupInfo; +import org.asamk.signal.manager.storage.groups.GroupInfoV1; +import org.asamk.signal.manager.storage.groups.GroupInfoV2; +import org.asamk.signal.manager.storage.profiles.SignalProfile; +import org.asamk.signal.manager.storage.profiles.SignalProfileEntry; +import org.asamk.signal.manager.storage.protocol.IdentityInfo; +import org.asamk.signal.manager.storage.stickers.Sticker; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; import org.signal.libsignal.metadata.InvalidMetadataMessageException; @@ -2422,8 +2422,7 @@ public class Manager implements Closeable { DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos); for (ContactInfo record : account.getContactStore().getContacts()) { VerifiedMessage verifiedMessage = null; - JsonIdentityKeyStore.Identity currentIdentity = account.getSignalProtocolStore() - .getIdentity(record.getAddress()); + IdentityInfo currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress()); if (currentIdentity != null) { verifiedMessage = new VerifiedMessage(record.getAddress(), currentIdentity.getIdentityKey(), @@ -2517,11 +2516,11 @@ public class Manager implements Closeable { return account.getGroupStore().getGroup(groupId); } - public List getIdentities() { + public List getIdentities() { return account.getSignalProtocolStore().getIdentities(); } - public List getIdentities(String number) throws InvalidNumberException { + public List getIdentities(String number) throws InvalidNumberException { return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number)); } @@ -2533,11 +2532,11 @@ public class Manager implements Closeable { */ public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException { SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name); - List ids = account.getSignalProtocolStore().getIdentities(address); + List ids = account.getSignalProtocolStore().getIdentities(address); if (ids == null) { return false; } - for (JsonIdentityKeyStore.Identity id : ids) { + for (IdentityInfo id : ids) { if (!Arrays.equals(id.getIdentityKey().serialize(), fingerprint)) { continue; } @@ -2563,11 +2562,11 @@ public class Manager implements Closeable { */ public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException { SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name); - List ids = account.getSignalProtocolStore().getIdentities(address); + List ids = account.getSignalProtocolStore().getIdentities(address); if (ids == null) { return false; } - for (JsonIdentityKeyStore.Identity id : ids) { + for (IdentityInfo id : ids) { if (!safetyNumber.equals(computeSafetyNumber(address, id.getIdentityKey()))) { continue; } @@ -2592,11 +2591,11 @@ public class Manager implements Closeable { */ public boolean trustIdentityAllKeys(String name) { SignalServiceAddress address = resolveSignalServiceAddress(name); - List ids = account.getSignalProtocolStore().getIdentities(address); + List ids = account.getSignalProtocolStore().getIdentities(address); if (ids == null) { return false; } - for (JsonIdentityKeyStore.Identity id : ids) { + for (IdentityInfo id : ids) { if (id.getTrustLevel() == TrustLevel.UNTRUSTED) { account.getSignalProtocolStore() .setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED); diff --git a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java index f81cfa49..95e92c7a 100644 --- a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java +++ b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -16,7 +16,7 @@ */ package org.asamk.signal.manager; -import org.asamk.signal.storage.SignalAccount; +import org.asamk.signal.manager.storage.SignalAccount; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.profiles.ProfileKey; import org.whispersystems.libsignal.IdentityKeyPair; diff --git a/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java b/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java index c5f727e1..f56639e3 100644 --- a/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java @@ -1,8 +1,8 @@ package org.asamk.signal.manager.groups; -import org.asamk.signal.storage.groups.GroupInfo; -import org.asamk.signal.storage.groups.GroupInfoV1; -import org.asamk.signal.storage.groups.GroupInfoV2; +import org.asamk.signal.manager.storage.groups.GroupInfo; +import org.asamk.signal.manager.storage.groups.GroupInfoV1; +import org.asamk.signal.manager.storage.groups.GroupInfoV2; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.groups.GroupMasterKey; import org.signal.zkgroup.groups.GroupSecretParams; diff --git a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 6a52e00e..394eba57 100644 --- a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -5,8 +5,8 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.asamk.signal.manager.groups.GroupIdV2; import org.asamk.signal.manager.groups.GroupLinkPassword; import org.asamk.signal.manager.groups.GroupUtils; -import org.asamk.signal.storage.groups.GroupInfoV2; -import org.asamk.signal.storage.profiles.SignalProfile; +import org.asamk.signal.manager.storage.groups.GroupInfoV2; +import org.asamk.signal.manager.storage.profiles.SignalProfile; import org.asamk.signal.util.IOUtils; import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.GroupChange; diff --git a/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java b/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java index 1ff4cb05..c16b5e0d 100644 --- a/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java +++ b/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java @@ -1,6 +1,6 @@ package org.asamk.signal.manager.helper; -import org.asamk.signal.storage.profiles.SignalProfile; +import org.asamk.signal.manager.storage.profiles.SignalProfile; import org.whispersystems.signalservice.api.push.SignalServiceAddress; public interface ProfileProvider { diff --git a/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java b/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java index 97331cf3..a994c40a 100644 --- a/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java +++ b/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java @@ -1,6 +1,6 @@ package org.asamk.signal.manager.helper; -import org.asamk.signal.storage.profiles.SignalProfile; +import org.asamk.signal.manager.storage.profiles.SignalProfile; import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.zkgroup.profiles.ProfileKey; import org.whispersystems.libsignal.util.guava.Optional; diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java similarity index 94% rename from src/main/java/org/asamk/signal/storage/SignalAccount.java rename to src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index 393d0449..c3573209 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage; +package org.asamk.signal.manager.storage; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; @@ -11,20 +11,20 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; import org.asamk.signal.manager.groups.GroupId; -import org.asamk.signal.storage.contacts.ContactInfo; -import org.asamk.signal.storage.contacts.JsonContactsStore; -import org.asamk.signal.storage.groups.GroupInfo; -import org.asamk.signal.storage.groups.GroupInfoV1; -import org.asamk.signal.storage.groups.JsonGroupStore; -import org.asamk.signal.storage.profiles.ProfileStore; -import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; -import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; -import org.asamk.signal.storage.protocol.RecipientStore; -import org.asamk.signal.storage.protocol.SessionInfo; -import org.asamk.signal.storage.protocol.SignalServiceAddressResolver; -import org.asamk.signal.storage.stickers.StickerStore; -import org.asamk.signal.storage.threads.LegacyJsonThreadStore; -import org.asamk.signal.storage.threads.ThreadInfo; +import org.asamk.signal.manager.storage.contacts.ContactInfo; +import org.asamk.signal.manager.storage.contacts.JsonContactsStore; +import org.asamk.signal.manager.storage.groups.GroupInfo; +import org.asamk.signal.manager.storage.groups.GroupInfoV1; +import org.asamk.signal.manager.storage.groups.JsonGroupStore; +import org.asamk.signal.manager.storage.profiles.ProfileStore; +import org.asamk.signal.manager.storage.protocol.IdentityInfo; +import org.asamk.signal.manager.storage.protocol.JsonSignalProtocolStore; +import org.asamk.signal.manager.storage.protocol.RecipientStore; +import org.asamk.signal.manager.storage.protocol.SessionInfo; +import org.asamk.signal.manager.storage.protocol.SignalServiceAddressResolver; +import org.asamk.signal.manager.storage.stickers.StickerStore; +import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore; +import org.asamk.signal.manager.storage.threads.ThreadInfo; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; import org.signal.zkgroup.InvalidInputException; @@ -286,7 +286,7 @@ public class SignalAccount implements Closeable { session.address = recipientStore.resolveServiceAddress(session.address); } - for (JsonIdentityKeyStore.Identity identity : signalProtocolStore.getIdentities()) { + for (IdentityInfo identity : signalProtocolStore.getIdentities()) { identity.setAddress(recipientStore.resolveServiceAddress(identity.getAddress())); } } diff --git a/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java b/src/main/java/org/asamk/signal/manager/storage/contacts/ContactInfo.java similarity index 95% rename from src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java rename to src/main/java/org/asamk/signal/manager/storage/contacts/ContactInfo.java index 3b155210..4dd132f7 100644 --- a/src/main/java/org/asamk/signal/storage/contacts/ContactInfo.java +++ b/src/main/java/org/asamk/signal/manager/storage/contacts/ContactInfo.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.contacts; +package org.asamk.signal.manager.storage.contacts; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/org/asamk/signal/storage/contacts/JsonContactsStore.java b/src/main/java/org/asamk/signal/manager/storage/contacts/JsonContactsStore.java similarity index 96% rename from src/main/java/org/asamk/signal/storage/contacts/JsonContactsStore.java rename to src/main/java/org/asamk/signal/manager/storage/contacts/JsonContactsStore.java index bb81b0c9..d2859f3f 100644 --- a/src/main/java/org/asamk/signal/storage/contacts/JsonContactsStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/contacts/JsonContactsStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.contacts; +package org.asamk.signal.manager.storage.contacts; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java b/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java similarity index 97% rename from src/main/java/org/asamk/signal/storage/groups/GroupInfo.java rename to src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java index fe725141..a644b620 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfo.java +++ b/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.groups; +package org.asamk.signal.manager.storage.groups; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java b/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java similarity index 99% rename from src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java rename to src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java index e48fe297..39591647 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV1.java +++ b/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV1.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.groups; +package org.asamk.signal.manager.storage.groups; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java b/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java similarity index 98% rename from src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java rename to src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java index 0139f879..17c23925 100644 --- a/src/main/java/org/asamk/signal/storage/groups/GroupInfoV2.java +++ b/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.groups; +package org.asamk.signal.manager.storage.groups; import org.asamk.signal.manager.groups.GroupIdV2; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; diff --git a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java b/src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java similarity index 99% rename from src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java rename to src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java index 1aae49f7..2b4dbcf5 100644 --- a/src/main/java/org/asamk/signal/storage/groups/JsonGroupStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.groups; +package org.asamk.signal.manager.storage.groups; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; diff --git a/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java b/src/main/java/org/asamk/signal/manager/storage/profiles/ProfileStore.java similarity index 99% rename from src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java rename to src/main/java/org/asamk/signal/manager/storage/profiles/ProfileStore.java index 3b3d3f9f..bff2f17e 100644 --- a/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/profiles/ProfileStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.profiles; +package org.asamk.signal.manager.storage.profiles; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; diff --git a/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java b/src/main/java/org/asamk/signal/manager/storage/profiles/SignalProfile.java similarity index 98% rename from src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java rename to src/main/java/org/asamk/signal/manager/storage/profiles/SignalProfile.java index 023458ed..48a38578 100644 --- a/src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java +++ b/src/main/java/org/asamk/signal/manager/storage/profiles/SignalProfile.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.profiles; +package org.asamk.signal.manager.storage.profiles; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java b/src/main/java/org/asamk/signal/manager/storage/profiles/SignalProfileEntry.java similarity index 96% rename from src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java rename to src/main/java/org/asamk/signal/manager/storage/profiles/SignalProfileEntry.java index e6acf30d..a81fbcb5 100644 --- a/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java +++ b/src/main/java/org/asamk/signal/manager/storage/profiles/SignalProfileEntry.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.profiles; +package org.asamk.signal.manager.storage.profiles; import org.signal.zkgroup.profiles.ProfileKey; import org.signal.zkgroup.profiles.ProfileKeyCredential; diff --git a/src/main/java/org/asamk/signal/manager/storage/protocol/IdentityInfo.java b/src/main/java/org/asamk/signal/manager/storage/protocol/IdentityInfo.java new file mode 100644 index 00000000..d4af11f2 --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/IdentityInfo.java @@ -0,0 +1,57 @@ +package org.asamk.signal.manager.storage.protocol; + +import org.asamk.signal.manager.TrustLevel; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; + +import java.util.Date; + +public class IdentityInfo { + + SignalServiceAddress address; + IdentityKey identityKey; + TrustLevel trustLevel; + Date added; + + public IdentityInfo(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel) { + this.address = address; + this.identityKey = identityKey; + this.trustLevel = trustLevel; + this.added = new Date(); + } + + IdentityInfo(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) { + this.address = address; + this.identityKey = identityKey; + this.trustLevel = trustLevel; + this.added = added; + } + + public SignalServiceAddress getAddress() { + return address; + } + + public void setAddress(final SignalServiceAddress address) { + this.address = address; + } + + boolean isTrusted() { + return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED; + } + + public IdentityKey getIdentityKey() { + return this.identityKey; + } + + public TrustLevel getTrustLevel() { + return this.trustLevel; + } + + public Date getDateAdded() { + return this.added; + } + + public byte[] getFingerprint() { + return identityKey.getPublicKey().serialize(); + } +} diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java similarity index 80% rename from src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java rename to src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java index 29160cf1..517b384e 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonIdentityKeyStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.protocol; +package org.asamk.signal.manager.storage.protocol; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; @@ -31,7 +31,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { final static Logger logger = LoggerFactory.getLogger(JsonIdentityKeyStore.class); - private final List identities = new ArrayList<>(); + private final List identities = new ArrayList<>(); private final IdentityKeyPair identityKeyPair; private final int localRegistrationId; @@ -85,7 +85,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { public boolean saveIdentity( SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel, Date added ) { - for (Identity id : identities) { + for (IdentityInfo id : identities) { if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) { continue; } @@ -97,7 +97,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { return true; } - identities.add(new Identity(serviceAddress, identityKey, trustLevel, added != null ? added : new Date())); + identities.add(new IdentityInfo(serviceAddress, identityKey, trustLevel, added != null ? added : new Date())); return false; } @@ -111,7 +111,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { public void setIdentityTrustLevel( SignalServiceAddress serviceAddress, IdentityKey identityKey, TrustLevel trustLevel ) { - for (Identity id : identities) { + for (IdentityInfo id : identities) { if (!id.address.matches(serviceAddress) || !id.identityKey.equals(identityKey)) { continue; } @@ -123,7 +123,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { return; } - identities.add(new Identity(serviceAddress, identityKey, trustLevel, new Date())); + identities.add(new IdentityInfo(serviceAddress, identityKey, trustLevel, new Date())); } @Override @@ -132,7 +132,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); boolean trustOnFirstUse = true; - for (Identity id : identities) { + for (IdentityInfo id : identities) { if (!id.address.matches(serviceAddress)) { continue; } @@ -150,14 +150,14 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { @Override public IdentityKey getIdentity(SignalProtocolAddress address) { SignalServiceAddress serviceAddress = resolveSignalServiceAddress(address.getName()); - Identity identity = getIdentity(serviceAddress); + IdentityInfo identity = getIdentity(serviceAddress); return identity == null ? null : identity.getIdentityKey(); } - public Identity getIdentity(SignalServiceAddress serviceAddress) { + public IdentityInfo getIdentity(SignalServiceAddress serviceAddress) { long maxDate = 0; - Identity maxIdentity = null; - for (Identity id : this.identities) { + IdentityInfo maxIdentity = null; + for (IdentityInfo id : this.identities) { if (!id.address.matches(serviceAddress)) { continue; } @@ -171,14 +171,14 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { return maxIdentity; } - public List getIdentities() { + public List getIdentities() { // TODO deep copy return identities; } - public List getIdentities(SignalServiceAddress serviceAddress) { - List identities = new ArrayList<>(); - for (Identity identity : this.identities) { + public List getIdentities(SignalServiceAddress serviceAddress) { + List identities = new ArrayList<>(); + for (IdentityInfo identity : this.identities) { if (identity.address.matches(serviceAddress)) { identities.add(identity); } @@ -246,7 +246,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { json.writeStringField("identityKey", Base64.encodeBytes(jsonIdentityKeyStore.getIdentityKeyPair().serialize())); json.writeArrayFieldStart("trustedKeys"); - for (Identity trustedKey : jsonIdentityKeyStore.identities) { + for (IdentityInfo trustedKey : jsonIdentityKeyStore.identities) { json.writeStartObject(); if (trustedKey.getAddress().getNumber().isPresent()) { json.writeStringField("name", trustedKey.getAddress().getNumber().get()); @@ -264,53 +264,4 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { } } - public static class Identity { - - SignalServiceAddress address; - IdentityKey identityKey; - TrustLevel trustLevel; - Date added; - - public Identity(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel) { - this.address = address; - this.identityKey = identityKey; - this.trustLevel = trustLevel; - this.added = new Date(); - } - - Identity(SignalServiceAddress address, IdentityKey identityKey, TrustLevel trustLevel, Date added) { - this.address = address; - this.identityKey = identityKey; - this.trustLevel = trustLevel; - this.added = added; - } - - public SignalServiceAddress getAddress() { - return address; - } - - public void setAddress(final SignalServiceAddress address) { - this.address = address; - } - - boolean isTrusted() { - return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED; - } - - public IdentityKey getIdentityKey() { - return this.identityKey; - } - - public TrustLevel getTrustLevel() { - return this.trustLevel; - } - - public Date getDateAdded() { - return this.added; - } - - public byte[] getFingerprint() { - return identityKey.getPublicKey().serialize(); - } - } } diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonPreKeyStore.java similarity index 98% rename from src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java rename to src/main/java/org/asamk/signal/manager/storage/protocol/JsonPreKeyStore.java index 523809c1..4d884c3e 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonPreKeyStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonPreKeyStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.protocol; +package org.asamk.signal.manager.storage.protocol; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java similarity index 99% rename from src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java rename to src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java index 24e4594e..f55aff14 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonSessionStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.protocol; +package org.asamk.signal.manager.storage.protocol; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonSignalProtocolStore.java b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSignalProtocolStore.java similarity index 95% rename from src/main/java/org/asamk/signal/storage/protocol/JsonSignalProtocolStore.java rename to src/main/java/org/asamk/signal/manager/storage/protocol/JsonSignalProtocolStore.java index 5939749d..41a63013 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonSignalProtocolStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSignalProtocolStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.protocol; +package org.asamk.signal.manager.storage.protocol; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -91,11 +91,11 @@ public class JsonSignalProtocolStore implements SignalProtocolStore { identityKeyStore.setIdentityTrustLevel(serviceAddress, identityKey, trustLevel); } - public List getIdentities() { + public List getIdentities() { return identityKeyStore.getIdentities(); } - public List getIdentities(SignalServiceAddress serviceAddress) { + public List getIdentities(SignalServiceAddress serviceAddress) { return identityKeyStore.getIdentities(serviceAddress); } @@ -109,7 +109,7 @@ public class JsonSignalProtocolStore implements SignalProtocolStore { return identityKeyStore.getIdentity(address); } - public JsonIdentityKeyStore.Identity getIdentity(SignalServiceAddress serviceAddress) { + public IdentityInfo getIdentity(SignalServiceAddress serviceAddress) { return identityKeyStore.getIdentity(serviceAddress); } diff --git a/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSignedPreKeyStore.java similarity index 98% rename from src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java rename to src/main/java/org/asamk/signal/manager/storage/protocol/JsonSignedPreKeyStore.java index 7accf5a1..5eae4500 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/JsonSignedPreKeyStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSignedPreKeyStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.protocol; +package org.asamk.signal.manager.storage.protocol; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; diff --git a/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java b/src/main/java/org/asamk/signal/manager/storage/protocol/RecipientStore.java similarity index 98% rename from src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java rename to src/main/java/org/asamk/signal/manager/storage/protocol/RecipientStore.java index 701eca34..60634ae5 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/RecipientStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.protocol; +package org.asamk.signal.manager.storage.protocol; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; diff --git a/src/main/java/org/asamk/signal/storage/protocol/SessionInfo.java b/src/main/java/org/asamk/signal/manager/storage/protocol/SessionInfo.java similarity index 89% rename from src/main/java/org/asamk/signal/storage/protocol/SessionInfo.java rename to src/main/java/org/asamk/signal/manager/storage/protocol/SessionInfo.java index 00221233..802b896b 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/SessionInfo.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/SessionInfo.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.protocol; +package org.asamk.signal.manager.storage.protocol; import org.whispersystems.signalservice.api.push.SignalServiceAddress; diff --git a/src/main/java/org/asamk/signal/storage/protocol/SignalServiceAddressResolver.java b/src/main/java/org/asamk/signal/manager/storage/protocol/SignalServiceAddressResolver.java similarity index 88% rename from src/main/java/org/asamk/signal/storage/protocol/SignalServiceAddressResolver.java rename to src/main/java/org/asamk/signal/manager/storage/protocol/SignalServiceAddressResolver.java index b1c5fb38..86eea05e 100644 --- a/src/main/java/org/asamk/signal/storage/protocol/SignalServiceAddressResolver.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/SignalServiceAddressResolver.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.protocol; +package org.asamk.signal.manager.storage.protocol; import org.whispersystems.signalservice.api.push.SignalServiceAddress; diff --git a/src/main/java/org/asamk/signal/storage/stickers/Sticker.java b/src/main/java/org/asamk/signal/manager/storage/stickers/Sticker.java similarity index 93% rename from src/main/java/org/asamk/signal/storage/stickers/Sticker.java rename to src/main/java/org/asamk/signal/manager/storage/stickers/Sticker.java index 386924c4..54e95d0a 100644 --- a/src/main/java/org/asamk/signal/storage/stickers/Sticker.java +++ b/src/main/java/org/asamk/signal/manager/storage/stickers/Sticker.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.stickers; +package org.asamk.signal.manager.storage.stickers; public class Sticker { diff --git a/src/main/java/org/asamk/signal/storage/stickers/StickerStore.java b/src/main/java/org/asamk/signal/manager/storage/stickers/StickerStore.java similarity index 98% rename from src/main/java/org/asamk/signal/storage/stickers/StickerStore.java rename to src/main/java/org/asamk/signal/manager/storage/stickers/StickerStore.java index e5d817d2..10cd2e99 100644 --- a/src/main/java/org/asamk/signal/storage/stickers/StickerStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/stickers/StickerStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.stickers; +package org.asamk.signal.manager.storage.stickers; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; diff --git a/src/main/java/org/asamk/signal/storage/threads/LegacyJsonThreadStore.java b/src/main/java/org/asamk/signal/manager/storage/threads/LegacyJsonThreadStore.java similarity index 97% rename from src/main/java/org/asamk/signal/storage/threads/LegacyJsonThreadStore.java rename to src/main/java/org/asamk/signal/manager/storage/threads/LegacyJsonThreadStore.java index 24463933..f37360a2 100644 --- a/src/main/java/org/asamk/signal/storage/threads/LegacyJsonThreadStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/threads/LegacyJsonThreadStore.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.threads; +package org.asamk.signal.manager.storage.threads; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; diff --git a/src/main/java/org/asamk/signal/storage/threads/ThreadInfo.java b/src/main/java/org/asamk/signal/manager/storage/threads/ThreadInfo.java similarity index 78% rename from src/main/java/org/asamk/signal/storage/threads/ThreadInfo.java rename to src/main/java/org/asamk/signal/manager/storage/threads/ThreadInfo.java index 67e6b474..b81a0051 100644 --- a/src/main/java/org/asamk/signal/storage/threads/ThreadInfo.java +++ b/src/main/java/org/asamk/signal/manager/storage/threads/ThreadInfo.java @@ -1,4 +1,4 @@ -package org.asamk.signal.storage.threads; +package org.asamk.signal.manager.storage.threads; import com.fasterxml.jackson.annotation.JsonProperty; -- 2.51.0 From bbdd6a89102f200f284a01a41ac2809c0759ae50 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 29 Dec 2020 22:48:39 +0100 Subject: [PATCH 14/16] Cleanup utils --- .../asamk/signal/manager/DeviceLinkInfo.java | 60 ++++ .../org/asamk/signal/manager/Manager.java | 43 +-- .../signal/manager/ProvisioningManager.java | 4 +- .../asamk/signal/manager/ServiceConfig.java | 11 + .../java/org/asamk/signal/manager/Utils.java | 304 ------------------ .../signal/manager/groups/GroupIdV1.java | 2 +- .../manager/groups/GroupLinkPassword.java | 2 +- .../signal/manager/helper/GroupHelper.java | 2 +- .../signal/manager/storage/SignalAccount.java | 22 +- .../storage/groups/JsonGroupStore.java | 2 +- .../protocol/JsonIdentityKeyStore.java | 6 +- .../storage/protocol/JsonSessionStore.java | 6 +- .../signal/manager/util/AttachmentUtils.java | 79 +++++ .../asamk/signal/manager/util/IOUtils.java | 72 +++++ .../signal/manager/{ => util}/KeyUtils.java | 10 +- .../manager/util/MessageCacheUtils.java | 105 ++++++ .../org/asamk/signal/manager/util/Utils.java | 97 ++++++ .../java/org/asamk/signal/util/IOUtils.java | 64 ---- src/main/java/org/asamk/signal/util/Util.java | 42 --- 19 files changed, 477 insertions(+), 456 deletions(-) create mode 100644 src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java delete mode 100644 src/main/java/org/asamk/signal/manager/Utils.java create mode 100644 src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java create mode 100644 src/main/java/org/asamk/signal/manager/util/IOUtils.java rename src/main/java/org/asamk/signal/manager/{ => util}/KeyUtils.java (79%) create mode 100644 src/main/java/org/asamk/signal/manager/util/MessageCacheUtils.java create mode 100644 src/main/java/org/asamk/signal/manager/util/Utils.java diff --git a/src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java b/src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java new file mode 100644 index 00000000..5b9fbe28 --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java @@ -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 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 getQueryMap(String query) { + String[] params = query.split("&"); + Map 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); + } +} diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index e00f44ac..5ed8fc4e 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -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.protocol.IdentityInfo; import org.asamk.signal.manager.storage.stickers.Sticker; -import org.asamk.signal.util.IOUtils; -import org.asamk.signal.util.Util; +import org.asamk.signal.manager.util.AttachmentUtils; +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.InvalidMetadataVersionException; 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.ProtocolUntrustedIdentityException; 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.local.DecryptedGroup; 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.exceptions.MissingConfigurationException; 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.StreamDetails; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; @@ -185,6 +190,7 @@ public class Manager implements Closeable { final static Logger logger = LoggerFactory.getLogger(Manager.class); private final SleepTimer timer = new UptimeSleepTimer(); + private final CertificateValidator certificateValidator = new CertificateValidator(ServiceConfig.getUnidentifiedSenderTrustRoot()); private final SignalServiceConfiguration serviceConfiguration; private final String userAgent; @@ -419,7 +425,7 @@ public class Manager implements Closeable { } public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException { - Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri); + DeviceLinkInfo info = DeviceLinkInfo.parseDeviceLinkUri(linkUri); addDevice(info.deviceIdentifier, info.deviceKey); } @@ -696,7 +702,7 @@ public class Manager implements Closeable { return Optional.absent(); } - return Optional.of(Utils.createAttachment(file)); + return Optional.of(AttachmentUtils.createAttachment(file)); } private Optional createContactAvatarAttachment(String number) throws IOException { @@ -705,7 +711,7 @@ public class Manager implements Closeable { return Optional.absent(); } - return Optional.of(Utils.createAttachment(file)); + return Optional.of(AttachmentUtils.createAttachment(file)); } private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { @@ -751,7 +757,7 @@ public class Manager implements Closeable { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .withBody(messageText); if (attachments != null) { - messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments)); + messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments)); } return sendGroupMessage(messageBuilder, groupId); @@ -928,7 +934,7 @@ public class Manager implements Closeable { newE164Members.remove(contact.getNumber()); } throw new IOException("Failed to add members " - + Util.join(", ", newE164Members) + + String.join(", ", newE164Members) + " to group: Not registered on Signal"); } @@ -971,7 +977,7 @@ public class Manager implements Closeable { File aFile = getGroupAvatarFile(g.getGroupId()); if (aFile.exists()) { try { - group.withAvatar(Utils.createAttachment(aFile)); + group.withAvatar(AttachmentUtils.createAttachment(aFile)); } catch (IOException e) { throw new AttachmentInvalidException(aFile.toString(), e); } @@ -1022,7 +1028,7 @@ public class Manager implements Closeable { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .withBody(messageText); if (attachments != null) { - List attachmentStreams = Utils.getSignalServiceAttachments(attachments); + List attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments); // Upload attachments here, so we only upload once even for multiple recipients 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 { SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(), account.getSignalProtocolStore(), - Utils.getCertificateValidator()); + certificateValidator); try { return cipher.decrypt(envelope); } catch (ProtocolUntrustedIdentityException e) { @@ -1820,7 +1826,7 @@ public class Manager implements Closeable { ) { SignalServiceEnvelope envelope; try { - envelope = Utils.loadEnvelope(fileEntry); + envelope = MessageCacheUtils.loadEnvelope(fileEntry); if (envelope == null) { return; } @@ -1887,7 +1893,7 @@ public class Manager implements Closeable { try { String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : ""; File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp()); - Utils.storeEnvelope(envelope1, cacheFile); + MessageCacheUtils.storeEnvelope(envelope1, cacheFile); } catch (IOException e) { 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); } else { 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); } else { 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) { - return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number)); + return account.getContactStore().getContact(Utils.getSignalServiceAddressFromIdentifier(number)); } public GroupInfo getGroup(GroupId groupId) { @@ -2613,7 +2619,8 @@ public class Manager implements Closeable { public String computeSafetyNumber( SignalServiceAddress theirAddress, IdentityKey theirIdentityKey ) { - return Utils.computeSafetyNumber(account.getSelfAddress(), + return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(), + account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress, theirIdentityKey); @@ -2626,12 +2633,12 @@ public class Manager implements Closeable { public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException { String canonicalizedNumber = UuidUtil.isUuid(identifier) ? identifier - : Util.canonicalizeNumber(identifier, account.getUsername()); + : PhoneNumberFormatter.formatNumber(identifier, account.getUsername()); return resolveSignalServiceAddress(canonicalizedNumber); } public SignalServiceAddress resolveSignalServiceAddress(String identifier) { - SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier); + SignalServiceAddress address = Utils.getSignalServiceAddressFromIdentifier(identifier); return resolveSignalServiceAddress(address); } diff --git a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java index 95e92c7a..8b3f0eb4 100644 --- a/src/main/java/org/asamk/signal/manager/ProvisioningManager.java +++ b/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -17,6 +17,7 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.storage.SignalAccount; +import org.asamk.signal.manager.util.KeyUtils; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.profiles.ProfileKey; import org.whispersystems.libsignal.IdentityKeyPair; @@ -71,8 +72,7 @@ public class ProvisioningManager { public String getDeviceLinkUri() throws TimeoutException, IOException { String deviceUuid = accountManager.getNewDeviceUuid(); - return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(deviceUuid, - identityKey.getPublicKey().getPublicKey())); + return new DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()).createDeviceLinkUri(); } public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists { diff --git a/src/main/java/org/asamk/signal/manager/ServiceConfig.java b/src/main/java/org/asamk/signal/manager/ServiceConfig.java index 353670ae..939d5b5b 100644 --- a/src/main/java/org/asamk/signal/manager/ServiceConfig.java +++ b/src/main/java/org/asamk/signal/manager/ServiceConfig.java @@ -1,6 +1,9 @@ package org.asamk.signal.manager; 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.signalservice.api.account.AccountAttributes; 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 makeSignalCdnUrlMapFor( SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls ) { diff --git a/src/main/java/org/asamk/signal/manager/Utils.java b/src/main/java/org/asamk/signal/manager/Utils.java deleted file mode 100644 index 0a815ea9..00000000 --- a/src/main/java/org/asamk/signal/manager/Utils.java +++ /dev/null @@ -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 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; - } - - 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 preview = Optional.absent(); - Optional caption = Optional.absent(); - Optional blurHash = Optional.absent(); - final Optional 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 getQueryMap(String query) { - String[] params = query.split("&"); - Map 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 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 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; - } - } -} diff --git a/src/main/java/org/asamk/signal/manager/groups/GroupIdV1.java b/src/main/java/org/asamk/signal/manager/groups/GroupIdV1.java index d865356e..237a34b6 100644 --- a/src/main/java/org/asamk/signal/manager/groups/GroupIdV1.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupIdV1.java @@ -1,6 +1,6 @@ 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 { diff --git a/src/main/java/org/asamk/signal/manager/groups/GroupLinkPassword.java b/src/main/java/org/asamk/signal/manager/groups/GroupLinkPassword.java index 41be672a..7edc7afb 100644 --- a/src/main/java/org/asamk/signal/manager/groups/GroupLinkPassword.java +++ b/src/main/java/org/asamk/signal/manager/groups/GroupLinkPassword.java @@ -1,6 +1,6 @@ package org.asamk.signal.manager.groups; -import org.asamk.signal.manager.KeyUtils; +import org.asamk.signal.manager.util.KeyUtils; import java.util.Arrays; diff --git a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 394eba57..8a2320e0 100644 --- a/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -7,7 +7,7 @@ import org.asamk.signal.manager.groups.GroupLinkPassword; import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.storage.groups.GroupInfoV2; 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.GroupChange; import org.signal.storageservice.protos.groups.Member; diff --git a/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index c3573209..c787471f 100644 --- a/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -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.threads.LegacyJsonThreadStore; import org.asamk.signal.manager.storage.threads.ThreadInfo; -import org.asamk.signal.util.IOUtils; -import org.asamk.signal.util.Util; +import org.asamk.signal.manager.util.IOUtils; +import org.asamk.signal.manager.util.Utils; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; @@ -211,28 +211,28 @@ public class SignalAccount implements Closeable { deviceId = node.asInt(); } if (rootNode.has("isMultiDevice")) { - isMultiDevice = Util.getNotNullNode(rootNode, "isMultiDevice").asBoolean(); + isMultiDevice = Utils.getNotNullNode(rootNode, "isMultiDevice").asBoolean(); } - username = Util.getNotNullNode(rootNode, "username").asText(); - password = Util.getNotNullNode(rootNode, "password").asText(); + username = Utils.getNotNullNode(rootNode, "username").asText(); + password = Utils.getNotNullNode(rootNode, "password").asText(); JsonNode pinNode = rootNode.get("registrationLockPin"); registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText(); if (rootNode.has("signalingKey")) { - signalingKey = Util.getNotNullNode(rootNode, "signalingKey").asText(); + signalingKey = Utils.getNotNullNode(rootNode, "signalingKey").asText(); } if (rootNode.has("preKeyIdOffset")) { - preKeyIdOffset = Util.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0); + preKeyIdOffset = Utils.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0); } else { preKeyIdOffset = 0; } if (rootNode.has("nextSignedPreKeyId")) { - nextSignedPreKeyId = Util.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt(); + nextSignedPreKeyId = Utils.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt(); } else { nextSignedPreKeyId = 0; } if (rootNode.has("profileKey")) { try { - profileKey = new ProfileKey(Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText())); + profileKey = new ProfileKey(Base64.decode(Utils.getNotNullNode(rootNode, "profileKey").asText())); } catch (InvalidInputException e) { throw new IOException( "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); - registered = Util.getNotNullNode(rootNode, "registered").asBoolean(); + registered = Utils.getNotNullNode(rootNode, "registered").asBoolean(); JsonNode groupStoreNode = rootNode.get("groupStore"); if (groupStoreNode != null) { groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class); diff --git a/src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java b/src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java index 2b4dbcf5..fdcd28a3 100644 --- a/src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java @@ -16,8 +16,8 @@ import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupIdV1; import org.asamk.signal.manager.groups.GroupIdV2; 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.IOUtils; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.groups.GroupMasterKey; diff --git a/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java index 517b384e..5bc1c11f 100644 --- a/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java @@ -9,7 +9,7 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; 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.LoggerFactory; import org.whispersystems.libsignal.IdentityKey; @@ -51,7 +51,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore { if (resolver != null) { return resolver.resolveSignalServiceAddress(identifier); } 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") .asText()) : null; final SignalServiceAddress serviceAddress = uuid == null - ? Util.getSignalServiceAddressFromIdentifier(trustedKeyName) + ? Utils.getSignalServiceAddressFromIdentifier(trustedKeyName) : new SignalServiceAddress(uuid, trustedKeyName); try { IdentityKey id = new IdentityKey(Base64.decode(trustedKey.get("identityKey").asText()), 0); diff --git a/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java index f55aff14..6e300214 100644 --- a/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java +++ b/src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; 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.LoggerFactory; import org.whispersystems.libsignal.SignalProtocolAddress; @@ -43,7 +43,7 @@ class JsonSessionStore implements SessionStore { if (resolver != null) { return resolver.resolveSignalServiceAddress(identifier); } 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; final SignalServiceAddress serviceAddress = uuid == null - ? Util.getSignalServiceAddressFromIdentifier(sessionName) + ? Utils.getSignalServiceAddressFromIdentifier(sessionName) : new SignalServiceAddress(uuid, sessionName); final int deviceId = session.get("deviceId").asInt(); final String record = session.get("record").asText(); diff --git a/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java b/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java new file mode 100644 index 00000000..b9a97073 --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java @@ -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 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; + } + + 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 preview = Optional.absent(); + Optional caption = Optional.absent(); + Optional blurHash = Optional.absent(); + final Optional 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; + } +} diff --git a/src/main/java/org/asamk/signal/manager/util/IOUtils.java b/src/main/java/org/asamk/signal/manager/util/IOUtils.java new file mode 100644 index 00000000..06f8aa22 --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/util/IOUtils.java @@ -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 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 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); + } + } + } +} diff --git a/src/main/java/org/asamk/signal/manager/KeyUtils.java b/src/main/java/org/asamk/signal/manager/util/KeyUtils.java similarity index 79% rename from src/main/java/org/asamk/signal/manager/KeyUtils.java rename to src/main/java/org/asamk/signal/manager/util/KeyUtils.java index 6ac093db..2b4bc371 100644 --- a/src/main/java/org/asamk/signal/manager/KeyUtils.java +++ b/src/main/java/org/asamk/signal/manager/util/KeyUtils.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.util; import org.asamk.signal.util.RandomUtils; import org.signal.zkgroup.InvalidInputException; @@ -10,11 +10,11 @@ public class KeyUtils { private KeyUtils() { } - static String createSignalingKey() { + public static String createSignalingKey() { return getSecret(52); } - static ProfileKey createProfileKey() { + public static ProfileKey createProfileKey() { try { return new ProfileKey(getSecretBytes(32)); } catch (InvalidInputException e) { @@ -22,11 +22,11 @@ public class KeyUtils { } } - static String createPassword() { + public static String createPassword() { return getSecret(18); } - static byte[] createStickerUploadKey() { + public static byte[] createStickerUploadKey() { return getSecretBytes(32); } diff --git a/src/main/java/org/asamk/signal/manager/util/MessageCacheUtils.java b/src/main/java/org/asamk/signal/manager/util/MessageCacheUtils.java new file mode 100644 index 00000000..8661c10b --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/util/MessageCacheUtils.java @@ -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 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()); + } + } + } +} diff --git a/src/main/java/org/asamk/signal/manager/util/Utils.java b/src/main/java/org/asamk/signal/manager/util/Utils.java new file mode 100644 index 00000000..e68b5ce3 --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -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; + } +} diff --git a/src/main/java/org/asamk/signal/util/IOUtils.java b/src/main/java/org/asamk/signal/util/IOUtils.java index 59727a9a..766d1905 100644 --- a/src/main/java/org/asamk/signal/util/IOUtils.java +++ b/src/main/java/org/asamk/signal/util/IOUtils.java @@ -1,35 +1,16 @@ package org.asamk.signal.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.io.StringWriter; 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 { private IOUtils() { } - public static File createTempFile() throws IOException { - return File.createTempFile("signal_tmp_", ".tmp"); - } - public static String readAll(InputStream in, Charset charset) throws IOException { StringWriter output = new StringWriter(); byte[] buffer = new byte[4096]; @@ -40,36 +21,6 @@ public class IOUtils { 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 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 perms = EnumSet.of(OWNER_READ, OWNER_WRITE); - Files.createFile(file, PosixFilePermissions.asFileAttribute(perms)); - } catch (UnsupportedOperationException e) { - Files.createFile(file); - } - } - public static File getDataHomeDir() { String dataHome = System.getenv("XDG_DATA_HOME"); if (dataHome != null) { @@ -78,19 +29,4 @@ public class IOUtils { 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); - } - } - } } diff --git a/src/main/java/org/asamk/signal/util/Util.java b/src/main/java/org/asamk/signal/util/Util.java index 79a6587b..92bfae7b 100644 --- a/src/main/java/org/asamk/signal/util/Util.java +++ b/src/main/java/org/asamk/signal/util/Util.java @@ -1,15 +1,7 @@ package org.asamk.signal.util; -import com.fasterxml.jackson.databind.JsonNode; - import org.asamk.signal.manager.groups.GroupId; 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 { @@ -26,41 +18,7 @@ public class Util { return f.toString(); } - public static String join(CharSequence separator, Iterable 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 { 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); - } - } } -- 2.51.0 From a52f6a6657585fbad5afa4c57ce37752118317e9 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 30 Dec 2020 11:59:24 +0100 Subject: [PATCH 15/16] Replace Collections with Set.of/Map.of/List.of --- .../org/asamk/signal/dbus/DbusSignalImpl.java | 3 +-- .../org/asamk/signal/manager/Manager.java | 19 +++++++++---------- .../asamk/signal/manager/ServiceConfig.java | 3 +-- .../manager/storage/groups/GroupInfoV2.java | 7 +++---- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index d19116a4..278fbbd4 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -16,7 +16,6 @@ import org.whispersystems.signalservice.api.util.InvalidNumberException; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -167,7 +166,7 @@ public class DbusSignalImpl implements Signal { public List getGroupMembers(final byte[] groupId) { GroupInfo group = m.getGroup(GroupId.unknownVersion(groupId)); if (group == null) { - return Collections.emptyList(); + return List.of(); } else { return group.getMembers() .stream() diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 5ed8fc4e..25b2ec54 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -164,7 +164,6 @@ import java.security.SignatureException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -211,7 +210,7 @@ public class Manager implements Closeable { private final ProfileHelper profileHelper; private final GroupHelper groupHelper; - public Manager( + Manager( SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, @@ -810,7 +809,7 @@ public class Manager implements Closeable { GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile); if (gv2 == null) { GroupInfoV1 gv1 = new GroupInfoV1(GroupIdV1.createRandom()); - gv1.addMembers(Collections.singleton(account.getSelfAddress())); + gv1.addMembers(List.of(account.getSelfAddress())); updateGroupV1(gv1, name, members, avatarFile); messageBuilder = getGroupUpdateMessageBuilder(gv1); g = gv1; @@ -965,7 +964,7 @@ public class Manager implements Closeable { SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g); // Send group message only to the recipient who requested it - return sendMessage(messageBuilder, Collections.singleton(recipient)); + return sendMessage(messageBuilder, List.of(recipient)); } private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException { @@ -1007,14 +1006,14 @@ public class Manager implements Closeable { .asGroupMessage(group.build()); // Send group info request message to the recipient who sent us a message with this groupId - return sendMessage(messageBuilder, Collections.singleton(recipient)); + return sendMessage(messageBuilder, List.of(recipient)); } void sendReceipt( SignalServiceAddress remoteAddress, long messageId ) throws IOException, UntrustedIdentityException { SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY, - Collections.singletonList(messageId), + List.of(messageId), System.currentTimeMillis()); createMessageSender().sendReceipt(remoteAddress, @@ -1141,7 +1140,7 @@ public class Manager implements Closeable { private void sendExpirationTimerUpdate(SignalServiceAddress address) throws IOException { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .asExpirationUpdate(); - sendMessage(messageBuilder, Collections.singleton(address)); + sendMessage(messageBuilder, List.of(address)); } /** @@ -1434,7 +1433,7 @@ public class Manager implements Closeable { .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); - return new Pair<>(timestamp, Collections.emptyList()); + return new Pair<>(timestamp, List.of()); } } else { // Send to all individually, so sync messages are sent correctly @@ -1477,7 +1476,7 @@ public class Manager implements Closeable { message.getTimestamp(), message, message.getExpiresInSeconds(), - Collections.singletonMap(recipient, unidentifiedAccess.isPresent()), + Map.of(recipient, unidentifiedAccess.isPresent()), false); SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript); @@ -2085,7 +2084,7 @@ public class Manager implements Closeable { syncGroup.removeMember(account.getSelfAddress()); } else { // Add ourself to the member set as it's marked as active - syncGroup.addMembers(Collections.singleton(account.getSelfAddress())); + syncGroup.addMembers(List.of(account.getSelfAddress())); } syncGroup.blocked = g.isBlocked(); if (g.getColor().isPresent()) { diff --git a/src/main/java/org/asamk/signal/manager/ServiceConfig.java b/src/main/java/org/asamk/signal/manager/ServiceConfig.java index 939d5b5b..0ccd826a 100644 --- a/src/main/java/org/asamk/signal/manager/ServiceConfig.java +++ b/src/main/java/org/asamk/signal/manager/ServiceConfig.java @@ -20,7 +20,6 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -77,7 +76,7 @@ public class ServiceConfig { .header("User-Agent", userAgent) .build()); - final List interceptors = Collections.singletonList(userAgentInterceptor); + final List interceptors = List.of(userAgentInterceptor); return new SignalServiceConfiguration(new SignalServiceUrl[]{new SignalServiceUrl(URL, TRUST_STORE)}, makeSignalCdnUrlMapFor(new SignalCdnUrl[]{new SignalCdnUrl(CDN_URL, TRUST_STORE)}, diff --git a/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java b/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java index 17c23925..2092c03a 100644 --- a/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java +++ b/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java @@ -8,7 +8,6 @@ import org.signal.zkgroup.groups.GroupMasterKey; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.util.UuidUtil; -import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; @@ -66,7 +65,7 @@ public class GroupInfoV2 extends GroupInfo { @Override public Set getMembers() { if (this.group == null) { - return Collections.emptySet(); + return Set.of(); } return group.getMembersList() .stream() @@ -77,7 +76,7 @@ public class GroupInfoV2 extends GroupInfo { @Override public Set getPendingMembers() { if (this.group == null) { - return Collections.emptySet(); + return Set.of(); } return group.getPendingMembersList() .stream() @@ -88,7 +87,7 @@ public class GroupInfoV2 extends GroupInfo { @Override public Set getRequestingMembers() { if (this.group == null) { - return Collections.emptySet(); + return Set.of(); } return group.getRequestingMembersList() .stream() -- 2.51.0 From 425626ef9475cbc90ef8ada95dee172389baf521 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 22 Mar 2020 16:02:31 +0100 Subject: [PATCH 16/16] Implement registration pin lock with KBS Fixes #323 Fixes #268 --- build.gradle | 2 +- .../signal/commands/RemovePinCommand.java | 3 +- .../asamk/signal/commands/SetPinCommand.java | 3 +- .../asamk/signal/commands/VerifyCommand.java | 8 ++ .../org/asamk/signal/manager/Manager.java | 112 ++++++++++++++---- .../asamk/signal/manager/ServiceConfig.java | 25 ++-- .../signal/manager/helper/PinHelper.java | 90 ++++++++++++++ .../signal/manager/storage/SignalAccount.java | 19 ++- .../asamk/signal/manager/util/KeyUtils.java | 5 + .../asamk/signal/manager/util/PinHashing.java | 31 +++++ 10 files changed, 254 insertions(+), 44 deletions(-) create mode 100644 src/main/java/org/asamk/signal/manager/helper/PinHelper.java create mode 100644 src/main/java/org/asamk/signal/manager/util/PinHashing.java diff --git a/build.gradle b/build.gradle index 1fbf5948..1c42834b 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ repositories { dependencies { implementation 'com.github.turasa:signal-service-java:2.15.3_unofficial_15' - implementation 'org.bouncycastle:bcprov-jdk15on:1.67' + implementation 'org.bouncycastle:bcprov-jdk15on:1.68' implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1' implementation 'com.github.hypfvieh:dbus-java:3.2.4' implementation 'org.slf4j:slf4j-simple:1.7.30' diff --git a/src/main/java/org/asamk/signal/commands/RemovePinCommand.java b/src/main/java/org/asamk/signal/commands/RemovePinCommand.java index b7de5402..95531249 100644 --- a/src/main/java/org/asamk/signal/commands/RemovePinCommand.java +++ b/src/main/java/org/asamk/signal/commands/RemovePinCommand.java @@ -5,6 +5,7 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.manager.Manager; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; import java.io.IOException; @@ -23,7 +24,7 @@ public class RemovePinCommand implements LocalCommand { try { m.setRegistrationLockPin(Optional.absent()); return 0; - } catch (IOException e) { + } catch (IOException | UnauthenticatedResponseException e) { System.err.println("Remove pin error: " + e.getMessage()); return 3; } diff --git a/src/main/java/org/asamk/signal/commands/SetPinCommand.java b/src/main/java/org/asamk/signal/commands/SetPinCommand.java index 9351dad0..c68ea3a9 100644 --- a/src/main/java/org/asamk/signal/commands/SetPinCommand.java +++ b/src/main/java/org/asamk/signal/commands/SetPinCommand.java @@ -5,6 +5,7 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.manager.Manager; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; import java.io.IOException; @@ -26,7 +27,7 @@ public class SetPinCommand implements LocalCommand { String registrationLockPin = ns.getString("registrationLockPin"); m.setRegistrationLockPin(Optional.of(registrationLockPin)); return 0; - } catch (IOException e) { + } catch (IOException | UnauthenticatedResponseException e) { System.err.println("Set pin error: " + e.getMessage()); return 3; } diff --git a/src/main/java/org/asamk/signal/commands/VerifyCommand.java b/src/main/java/org/asamk/signal/commands/VerifyCommand.java index b6ad100b..d4b0a7cb 100644 --- a/src/main/java/org/asamk/signal/commands/VerifyCommand.java +++ b/src/main/java/org/asamk/signal/commands/VerifyCommand.java @@ -4,6 +4,8 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.manager.Manager; +import org.whispersystems.signalservice.api.KeyBackupServicePinException; +import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException; import org.whispersystems.signalservice.internal.push.LockedException; import java.io.IOException; @@ -31,6 +33,12 @@ public class VerifyCommand implements LocalCommand { System.err.println("Verification failed! This number is locked with a pin. Hours remaining until reset: " + (e.getTimeRemaining() / 1000 / 60 / 60)); System.err.println("Use '--pin PIN_CODE' to specify the registration lock PIN"); + return 1; + } catch (KeyBackupServicePinException e) { + System.err.println("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining()); + return 1; + } catch (KeyBackupSystemNoDataException e) { + System.err.println("Verification failed! No KBS data."); return 3; } catch (IOException e) { System.err.println("Verify error: " + e.getMessage()); diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 25b2ec54..7ad7b888 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -26,6 +26,7 @@ import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.helper.GroupHelper; +import org.asamk.signal.manager.helper.PinHelper; import org.asamk.signal.manager.helper.ProfileHelper; import org.asamk.signal.manager.helper.UnidentifiedAccessHelper; import org.asamk.signal.manager.storage.SignalAccount; @@ -82,6 +83,10 @@ import org.whispersystems.libsignal.util.KeyHelper; import org.whispersystems.libsignal.util.Medium; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.KbsPinData; +import org.whispersystems.signalservice.api.KeyBackupService; +import org.whispersystems.signalservice.api.KeyBackupServicePinException; +import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceMessagePipe; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; @@ -96,6 +101,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; +import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; @@ -138,6 +144,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceConf import org.whispersystems.signalservice.internal.contacts.crypto.Quote; import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException; import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; +import org.whispersystems.signalservice.internal.push.LockedException; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; @@ -160,6 +167,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.security.KeyStore; import java.security.SignatureException; import java.util.ArrayList; import java.util.Arrays; @@ -193,6 +201,8 @@ public class Manager implements Closeable { private final SignalServiceConfiguration serviceConfiguration; private final String userAgent; + + // TODO make configurable private final boolean discoverableByPhoneNumber = true; private final boolean unrestrictedUnidentifiedAccess = false; @@ -209,6 +219,7 @@ public class Manager implements Closeable { private final UnidentifiedAccessHelper unidentifiedAccessHelper; private final ProfileHelper profileHelper; private final GroupHelper groupHelper; + private PinHelper pinHelper; Manager( SignalAccount account, @@ -222,8 +233,7 @@ public class Manager implements Closeable { this.userAgent = userAgent; this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create( serviceConfiguration)) : null; - this.accountManager = createSignalServiceAccountManager(); - this.groupsV2Api = accountManager.getGroupsV2Api(); + createSignalServiceAccountManager(); this.account.setResolver(this::resolveSignalServiceAddress); @@ -251,8 +261,8 @@ public class Manager implements Closeable { return account.getSelfAddress(); } - private SignalServiceAccountManager createSignalServiceAccountManager() { - return new SignalServiceAccountManager(serviceConfiguration, + private void createSignalServiceAccountManager() { + this.accountManager = new SignalServiceAccountManager(serviceConfiguration, new DynamicCredentialsProvider(account.getUuid(), account.getUsername(), account.getPassword(), @@ -261,6 +271,18 @@ public class Manager implements Closeable { userAgent, groupsV2Operations, timer); + this.groupsV2Api = accountManager.getGroupsV2Api(); + this.pinHelper = new PinHelper(createKeyBackupService()); + } + + private KeyBackupService createKeyBackupService() { + KeyStore keyStore = ServiceConfig.getIasKeyStore(); + + return accountManager.getKeyBackupService(keyStore, + ServiceConfig.KEY_BACKUP_ENCLAVE_NAME, + ServiceConfig.KEY_BACKUP_SERVICE_ID, + ServiceConfig.KEY_BACKUP_MRENCLAVE, + 10); } private IdentityKeyPair getIdentityKeyPair() { @@ -366,8 +388,7 @@ public class Manager implements Closeable { // Resetting UUID, because registering doesn't work otherwise account.setUuid(null); - accountManager = createSignalServiceAccountManager(); - this.groupsV2Api = accountManager.getGroupsV2Api(); + createSignalServiceAccountManager(); if (voiceVerification) { accountManager.requestVoiceVerificationCode(Locale.getDefault(), @@ -385,8 +406,9 @@ public class Manager implements Closeable { accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, - account.getRegistrationLockPin(), - account.getRegistrationLock(), + // set legacy pin only if no KBS master key is set + account.getPinMasterKey() == null ? account.getRegistrationLockPin() : null, + account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(), unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(), unrestrictedUnidentifiedAccess, capabilities, @@ -479,26 +501,39 @@ public class Manager implements Closeable { } } - public void verifyAccount(String verificationCode, String pin) throws IOException { + public void verifyAccount( + String verificationCode, + String pin + ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException { verificationCode = verificationCode.replace("-", ""); account.setSignalingKey(KeyUtils.createSignalingKey()); - // TODO make unrestricted unidentified access configurable - VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, - account.getSignalingKey(), - account.getSignalProtocolStore().getLocalRegistrationId(), - true, - pin, - null, - unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(), - unrestrictedUnidentifiedAccess, - capabilities, - discoverableByPhoneNumber); + VerifyAccountResponse response; + try { + response = verifyAccountWithCode(verificationCode, pin, null); + } catch (LockedException e) { + if (pin == null) { + throw e; + } + + KbsPinData registrationLockData = pinHelper.getRegistrationLockData(pin, e); + if (registrationLockData == null) { + throw e; + } + + String registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock(); + try { + response = verifyAccountWithCode(verificationCode, null, registrationLock); + } catch (LockedException _e) { + throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!"); + } + account.setPinMasterKey(registrationLockData.getMasterKey()); + } - UUID uuid = UuidUtil.parseOrNull(response.getUuid()); // TODO response.isStorageCapable() //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); + account.setRegistered(true); - account.setUuid(uuid); + account.setUuid(UuidUtil.parseOrNull(response.getUuid())); account.setRegistrationLockPin(pin); account.getSignalProtocolStore() .saveIdentity(account.getSelfAddress(), @@ -509,13 +544,40 @@ public class Manager implements Closeable { account.save(); } - public void setRegistrationLockPin(Optional pin) throws IOException { + private VerifyAccountResponse verifyAccountWithCode( + final String verificationCode, final String legacyPin, final String registrationLock + ) throws IOException { + return accountManager.verifyAccountWithCode(verificationCode, + account.getSignalingKey(), + account.getSignalProtocolStore().getLocalRegistrationId(), + true, + legacyPin, + registrationLock, + unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(), + unrestrictedUnidentifiedAccess, + capabilities, + discoverableByPhoneNumber); + } + + public void setRegistrationLockPin(Optional pin) throws IOException, UnauthenticatedResponseException { if (pin.isPresent()) { + final MasterKey masterKey = account.getPinMasterKey() != null + ? account.getPinMasterKey() + : KeyUtils.createMasterKey(); + + pinHelper.setRegistrationLockPin(pin.get(), masterKey); + account.setRegistrationLockPin(pin.get()); - throw new RuntimeException("Not implemented anymore, will be replaced with KBS"); + account.setPinMasterKey(masterKey); } else { - account.setRegistrationLockPin(null); + // Remove legacy registration lock accountManager.removeRegistrationLockV1(); + + // Remove KBS Pin + pinHelper.removeRegistrationLockPin(); + + account.setRegistrationLockPin(null); + account.setPinMasterKey(null); } account.save(); } diff --git a/src/main/java/org/asamk/signal/manager/ServiceConfig.java b/src/main/java/org/asamk/signal/manager/ServiceConfig.java index 0ccd826a..55935ced 100644 --- a/src/main/java/org/asamk/signal/manager/ServiceConfig.java +++ b/src/main/java/org/asamk/signal/manager/ServiceConfig.java @@ -1,5 +1,6 @@ package org.asamk.signal.manager; +import org.bouncycastle.util.encoders.Hex; import org.signal.zkgroup.ServerPublicParams; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.ecc.Curve; @@ -13,13 +14,13 @@ import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupSe import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl; import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl; -import org.whispersystems.util.Base64; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.Base64; import java.util.List; import java.util.Map; @@ -28,7 +29,8 @@ import okhttp3.Interceptor; public class ServiceConfig { - final static String UNIDENTIFIED_SENDER_TRUST_ROOT = "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"; + final static byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder() + .decode("BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"); final static int PREKEY_MINIMUM_COUNT = 20; final static int PREKEY_BATCH_SIZE = 100; final static int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024; @@ -37,6 +39,11 @@ public class ServiceConfig { final static String CDS_MRENCLAVE = "c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15"; + final static String KEY_BACKUP_ENCLAVE_NAME = "fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe"; + final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode( + "fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe"); + final static String KEY_BACKUP_MRENCLAVE = "a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87"; + private final static String URL = "https://textsecure-service.whispersystems.org"; private final static String CDN_URL = "https://cdn.signal.org"; private final static String CDN2_URL = "https://cdn2.signal.org"; @@ -48,18 +55,12 @@ public class ServiceConfig { private final static Optional dns = Optional.absent(); - private final static String zkGroupServerPublicParamsHex = "AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0="; - private final static byte[] zkGroupServerPublicParams; + private final static byte[] zkGroupServerPublicParams = Base64.getDecoder() + .decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0="); static final AccountAttributes.Capabilities capabilities; static { - try { - zkGroupServerPublicParams = Base64.decode(zkGroupServerPublicParamsHex); - } catch (IOException e) { - throw new AssertionError(e); - } - boolean zkGroupAvailable; try { new ServerPublicParams(zkGroupServerPublicParams); @@ -110,8 +111,8 @@ public class ServiceConfig { static ECPublicKey getUnidentifiedSenderTrustRoot() { try { - return Curve.decodePoint(Base64.decode(UNIDENTIFIED_SENDER_TRUST_ROOT), 0); - } catch (InvalidKeyException | IOException e) { + return Curve.decodePoint(UNIDENTIFIED_SENDER_TRUST_ROOT, 0); + } catch (InvalidKeyException e) { throw new AssertionError(e); } } diff --git a/src/main/java/org/asamk/signal/manager/helper/PinHelper.java b/src/main/java/org/asamk/signal/manager/helper/PinHelper.java new file mode 100644 index 00000000..47ee6b40 --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/helper/PinHelper.java @@ -0,0 +1,90 @@ +package org.asamk.signal.manager.helper; + +import org.asamk.signal.manager.util.PinHashing; +import org.whispersystems.signalservice.api.KbsPinData; +import org.whispersystems.signalservice.api.KeyBackupService; +import org.whispersystems.signalservice.api.KeyBackupServicePinException; +import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException; +import org.whispersystems.signalservice.api.kbs.HashedPin; +import org.whispersystems.signalservice.api.kbs.MasterKey; +import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; +import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; +import org.whispersystems.signalservice.internal.push.LockedException; + +import java.io.IOException; + +public class PinHelper { + + private final KeyBackupService keyBackupService; + + public PinHelper(final KeyBackupService keyBackupService) { + this.keyBackupService = keyBackupService; + } + + public void setRegistrationLockPin( + String pin, MasterKey masterKey + ) throws IOException, UnauthenticatedResponseException { + final KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(); + final HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession); + + pinChangeSession.setPin(hashedPin, masterKey); + pinChangeSession.enableRegistrationLock(masterKey); + } + + public void removeRegistrationLockPin() throws IOException, UnauthenticatedResponseException { + final KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(); + pinChangeSession.removePin(); + } + + public KbsPinData getRegistrationLockData( + String pin, + LockedException e + ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException { + String basicStorageCredentials = e.getBasicStorageCredentials(); + if (basicStorageCredentials == null) { + return null; + } + + return getRegistrationLockData(pin, basicStorageCredentials); + } + + private KbsPinData getRegistrationLockData( + String pin, + String basicStorageCredentials + ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException { + TokenResponse tokenResponse = keyBackupService.getToken(basicStorageCredentials); + if (tokenResponse == null || tokenResponse.getTries() == 0) { + throw new IOException("KBS Account locked"); + } + + KbsPinData registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse); + if (registrationLockData == null) { + throw new AssertionError("Failed to restore master key"); + } + return registrationLockData; + } + + private KbsPinData restoreMasterKey( + String pin, String basicStorageCredentials, TokenResponse tokenResponse + ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException { + if (pin == null) return null; + + if (basicStorageCredentials == null) { + throw new AssertionError("Cannot restore KBS key, no storage credentials supplied"); + } + + KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, + tokenResponse); + + try { + HashedPin hashedPin = PinHashing.hashPin(pin, session); + KbsPinData kbsData = session.restorePin(hashedPin); + if (kbsData == null) { + throw new AssertionError("Null not expected"); + } + return kbsData; + } catch (UnauthenticatedResponseException e) { + throw new IOException(e); + } + } +} diff --git a/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index c787471f..1c35b1fb 100644 --- a/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -36,6 +36,7 @@ import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.util.Medium; import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.util.Base64; @@ -66,6 +67,7 @@ public class SignalAccount implements Closeable { private boolean isMultiDevice = false; private String password; private String registrationLockPin; + private MasterKey pinMasterKey; private String signalingKey; private ProfileKey profileKey; private int preKeyIdOffset; @@ -217,6 +219,10 @@ public class SignalAccount implements Closeable { password = Utils.getNotNullNode(rootNode, "password").asText(); JsonNode pinNode = rootNode.get("registrationLockPin"); registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText(); + JsonNode pinMasterKeyNode = rootNode.get("pinMasterKey"); + pinMasterKey = pinMasterKeyNode == null || pinMasterKeyNode.isNull() + ? null + : new MasterKey(Base64.decode(pinMasterKeyNode.asText())); if (rootNode.has("signalingKey")) { signalingKey = Utils.getNotNullNode(rootNode, "signalingKey").asText(); } @@ -345,6 +351,7 @@ public class SignalAccount implements Closeable { .put("isMultiDevice", isMultiDevice) .put("password", password) .put("registrationLockPin", registrationLockPin) + .put("pinMasterKey", pinMasterKey == null ? null : Base64.encodeBytes(pinMasterKey.serialize())) .put("signalingKey", signalingKey) .put("preKeyIdOffset", preKeyIdOffset) .put("nextSignedPreKeyId", nextSignedPreKeyId) @@ -456,14 +463,18 @@ public class SignalAccount implements Closeable { return registrationLockPin; } - public String getRegistrationLock() { - return null; // TODO implement KBS - } - public void setRegistrationLockPin(final String registrationLockPin) { this.registrationLockPin = registrationLockPin; } + public MasterKey getPinMasterKey() { + return pinMasterKey; + } + + public void setPinMasterKey(final MasterKey pinMasterKey) { + this.pinMasterKey = pinMasterKey; + } + public String getSignalingKey() { return signalingKey; } diff --git a/src/main/java/org/asamk/signal/manager/util/KeyUtils.java b/src/main/java/org/asamk/signal/manager/util/KeyUtils.java index 2b4bc371..3f9ec08f 100644 --- a/src/main/java/org/asamk/signal/manager/util/KeyUtils.java +++ b/src/main/java/org/asamk/signal/manager/util/KeyUtils.java @@ -3,6 +3,7 @@ package org.asamk.signal.manager.util; import org.asamk.signal.util.RandomUtils; import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.profiles.ProfileKey; +import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.util.Base64; public class KeyUtils { @@ -30,6 +31,10 @@ public class KeyUtils { return getSecretBytes(32); } + public static MasterKey createMasterKey() { + return MasterKey.createNew(RandomUtils.getSecureRandom()); + } + private static String getSecret(int size) { byte[] secret = getSecretBytes(size); return Base64.encodeBytes(secret); diff --git a/src/main/java/org/asamk/signal/manager/util/PinHashing.java b/src/main/java/org/asamk/signal/manager/util/PinHashing.java new file mode 100644 index 00000000..2adf8148 --- /dev/null +++ b/src/main/java/org/asamk/signal/manager/util/PinHashing.java @@ -0,0 +1,31 @@ +package org.asamk.signal.manager.util; + +import org.bouncycastle.crypto.generators.Argon2BytesGenerator; +import org.bouncycastle.crypto.params.Argon2Parameters; +import org.whispersystems.signalservice.api.KeyBackupService; +import org.whispersystems.signalservice.api.kbs.HashedPin; +import org.whispersystems.signalservice.internal.registrationpin.PinHasher; + +public final class PinHashing { + + private PinHashing() { + } + + public static HashedPin hashPin(String pin, KeyBackupService.HashSession hashSession) { + final Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id).withParallelism(1) + .withIterations(32) + .withVersion(13) + .withMemoryAsKB(16 * 1024) + .withSalt(hashSession.hashSalt()) + .build(); + + final Argon2BytesGenerator generator = new Argon2BytesGenerator(); + generator.init(params); + + return PinHasher.hashPin(PinHasher.normalize(pin), password -> { + byte[] output = new byte[64]; + generator.generateBytes(password, output); + return output; + }); + } +} -- 2.51.0