From 3a0356488da91c3e6f42ecae90e16dbe42ef9596 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 4 Aug 2019 15:28:46 +0200 Subject: [PATCH] Add OmemoClient repl class --- smack-repl/build.gradle | 1 + .../smack/smackrepl/OmemoClient.java | 216 ++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/OmemoClient.java diff --git a/smack-repl/build.gradle b/smack-repl/build.gradle index 2b36f85ef..53579773d 100644 --- a/smack-repl/build.gradle +++ b/smack-repl/build.gradle @@ -19,6 +19,7 @@ dependencies { compile project(':smack-experimental') compile project(':smack-legacy') compile project(':smack-integration-test') + compile project(':smack-omemo-signal') compile "org.scala-lang:scala-library:$scalaVersion" compile "com.lihaoyi:ammonite_$scalaVersion:1.3.2" testCompile project(path: ":smack-core", configuration: "testRuntime") diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/OmemoClient.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/OmemoClient.java new file mode 100644 index 000000000..2bb415468 --- /dev/null +++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/OmemoClient.java @@ -0,0 +1,216 @@ +/** + * + * Copyright 2019 Paul Schaub + * + * This file is part of smack-repl. + * + * smack-repl is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.igniterealtime.smack.smackrepl; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.SmackException.NotConnectedException; +import org.jivesoftware.smack.SmackException.NotLoggedInException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.tcp.XMPPTCPConnection; +import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; +import org.jivesoftware.smackx.carbons.packet.CarbonExtension; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.omemo.OmemoManager; +import org.jivesoftware.smackx.omemo.OmemoMessage; +import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException; +import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; +import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; +import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; +import org.jivesoftware.smackx.omemo.internal.OmemoDevice; +import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; +import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener; +import org.jivesoftware.smackx.omemo.signal.SignalCachingOmemoStore; +import org.jivesoftware.smackx.omemo.signal.SignalFileBasedOmemoStore; +import org.jivesoftware.smackx.omemo.signal.SignalOmemoService; +import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint; +import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback; +import org.jivesoftware.smackx.omemo.trust.TrustState; + +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.impl.JidCreate; + +public class OmemoClient { + + public static final Logger LOGGER = Logger.getLogger(OmemoClient.class.getName()); + + private static final Scanner scanner = new Scanner(System.in, "UTF-8"); + private final XMPPTCPConnection connection; + private final OmemoManager omemoManager; + + public static void main(String[] args) + throws XMPPException, SmackException, IOException, InterruptedException, CorruptedOmemoKeyException { + SmackConfiguration.DEBUG = true; + if (args.length != 2) { + print("Missing arguments: "); + return; + } + SignalOmemoService.acknowledgeLicense(); + SignalOmemoService.setup(); + SignalOmemoService omemoService = (SignalOmemoService) SignalOmemoService.getInstance(); + Path omemoStoreDirectory = Files.createTempDirectory("omemo-store"); + omemoService.setOmemoStoreBackend(new SignalCachingOmemoStore(new SignalFileBasedOmemoStore(omemoStoreDirectory.toFile()))); + + EntityBareJid jid = JidCreate.entityBareFromOrThrowUnchecked(args[0]); + String password = args[1]; + OmemoClient client = new OmemoClient(jid, password); + try { + client.start(); + + while (true) { + String input = scanner.nextLine(); + if (input.startsWith("/quit")) { + break; + } + if (input.isEmpty()) { + continue; + } + client.handleInput(input); + } + } finally { + client.stop(); + } + } + + public OmemoClient(EntityBareJid jid, String password) { + connection = new XMPPTCPConnection(XMPPTCPConnectionConfiguration.builder() + .setXmppAddressAndPassword(jid, password).build()); + connection.setReplyTimeout(10 * 1000); + omemoManager = OmemoManager.getInstanceFor(connection); + omemoManager.setTrustCallback(new OmemoTrustCallback() { + // In a real app you'd want to persist these decisions + private final Map trustStateMap = new HashMap<>(); + @Override + public TrustState getTrust(OmemoDevice device, OmemoFingerprint fingerprint) { + return trustStateMap.get(fingerprint) != null ? trustStateMap.get(fingerprint) : TrustState.undecided; + } + + @Override + public void setTrust(OmemoDevice device, OmemoFingerprint fingerprint, TrustState state) { + trustStateMap.put(fingerprint, state); + } + }); + omemoManager.addOmemoMessageListener(new OmemoMessageListener() { + @Override + public void onOmemoMessageReceived(Stanza s, OmemoMessage.Received m) { + print(m.getSenderDevice() + ": " + (m.getBody() != null ? m.getBody() : "")); + } + + @Override + public void onOmemoCarbonCopyReceived(CarbonExtension.Direction d, Message cc, Message wm, OmemoMessage.Received m) { + onOmemoMessageReceived(cc, m); + } + }); + omemoManager.addOmemoMucMessageListener(new OmemoMucMessageListener() { + @Override + public void onOmemoMucMessageReceived(MultiUserChat muc, Stanza s, OmemoMessage.Received m) { + print(s.getFrom() + ":" + m.getSenderDevice().getDeviceId() + ": " + (m.getBody() != null ? m.getBody() : "")); + } + }); + } + + public void start() + throws XMPPException, SmackException, IOException, InterruptedException, CorruptedOmemoKeyException { + connection.connect().login(); + omemoManager.initialize(); + print("Logged in!"); + } + + public void stop() { + connection.disconnect(); + } + + public void handleInput(String input) + throws NotConnectedException, NotLoggedInException, InterruptedException, IOException { + String[] com = input.split(" ", 3); + switch (com[0]) { + case "/omemo": + if (com.length < 3) { + print("Usage: /omemo "); + return; + } + + BareJid recipient = JidCreate.bareFrom(com[1]); + String body = com[2]; + + try { + Message omemoMessage = omemoManager.encrypt(recipient, body).asMessage(recipient); + connection.sendStanza(omemoMessage); + } catch (UndecidedOmemoIdentityException e) { + print("Undecided Identities!\n" + Arrays.toString(e.getUndecidedDevices().toArray())); + } catch (CryptoFailedException | SmackException.NoResponseException e) { + LOGGER.log(Level.SEVERE, "Unexpected Exception", e); + } + break; + case "/trust": + print("Trust"); + if (com.length != 2) { + print("Usage: /trust "); + } + + BareJid contact = JidCreate.bareFrom(com[1]); + + HashMap devices; + try { + devices = omemoManager.getActiveFingerprints(contact); + } catch (CorruptedOmemoKeyException | CannotEstablishOmemoSessionException | SmackException.NoResponseException e) { + LOGGER.log(Level.SEVERE, "Unexpected Exception", e); + return; + } + for (OmemoDevice d : devices.keySet()) { + print("Trust (1) or distrust (2)?\n" + devices.get(d).blocksOf8Chars()); + if (Integer.parseInt(scanner.nextLine()) == 1) { + omemoManager.trustOmemoIdentity(d, devices.get(d)); + } else { + omemoManager.distrustOmemoIdentity(d, devices.get(d)); + } + } + print("Done."); + break; + case "/purge": + try { + omemoManager.purgeDeviceList(); + print("Purged."); + } catch (XMPPException.XMPPErrorException | SmackException.NoResponseException e) { + LOGGER.log(Level.SEVERE, "Unexpected Exception", e); + } + } + } + + private static void print(String msg) { + // CHECKSTYLE:OFF + System.out.println(msg); + // CHECKSTYLE:ON + } +}