OMEMO encrypted File transfers
This commit is contained in:
parent
9f53052954
commit
fcd0a7ce8c
4 changed files with 152 additions and 21 deletions
|
@ -2,20 +2,36 @@ package de.vanitasvitae.sync_client;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackConfiguration;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.roster.Roster;
|
import org.jivesoftware.smack.roster.Roster;
|
||||||
import org.jivesoftware.smack.roster.SubscribeListener;
|
import org.jivesoftware.smack.roster.SubscribeListener;
|
||||||
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
||||||
import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransport;
|
import org.jivesoftware.smackx.jet.JetManager;
|
||||||
import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager;
|
import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager;
|
||||||
import org.jivesoftware.smackx.jingle.transport.jingle_s5b.JingleS5BTransportManager;
|
import org.jivesoftware.smackx.jingle.transport.jingle_s5b.JingleS5BTransportManager;
|
||||||
|
import org.jivesoftware.smackx.omemo.OmemoConfiguration;
|
||||||
|
import org.jivesoftware.smackx.omemo.OmemoManager;
|
||||||
|
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||||
|
import org.jivesoftware.smackx.omemo.provider.OmemoVAxolotlProvider;
|
||||||
|
import org.jivesoftware.smackx.omemo.signal.SignalOmemoService;
|
||||||
|
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
|
||||||
|
|
||||||
import org.jxmpp.jid.FullJid;
|
import org.jxmpp.jid.FullJid;
|
||||||
import org.jxmpp.stringprep.XmppStringprepException;
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
@ -26,7 +42,10 @@ public class Client {
|
||||||
protected final Path root;
|
protected final Path root;
|
||||||
protected final Set<FullJid> remotes = new HashSet<>();
|
protected final Set<FullJid> remotes = new HashSet<>();
|
||||||
|
|
||||||
public Client(String username, String password, String directory) throws XmppStringprepException {
|
public Client(String username, String password, String directory)
|
||||||
|
throws XmppStringprepException, CorruptedOmemoKeyException, NoSuchAlgorithmException, UnsupportedEncodingException,
|
||||||
|
InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException, BadPaddingException,
|
||||||
|
InvalidAlgorithmParameterException, NoSuchProviderException, IllegalBlockSizeException, SmackException {
|
||||||
File dir = new File(directory);
|
File dir = new File(directory);
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
throw new IllegalArgumentException("Directory " + directory + " does not exist!");
|
throw new IllegalArgumentException("Directory " + directory + " does not exist!");
|
||||||
|
@ -37,10 +56,29 @@ public class Client {
|
||||||
Roster.getInstanceFor(connection).addSubscribeListener((from, subscribeRequest) -> SubscribeListener.SubscribeAnswer.Approve);
|
Roster.getInstanceFor(connection).addSubscribeListener((from, subscribeRequest) -> SubscribeListener.SubscribeAnswer.Approve);
|
||||||
JingleS5BTransportManager.getInstanceFor(connection);
|
JingleS5BTransportManager.getInstanceFor(connection);
|
||||||
JingleIBBTransportManager.getInstanceFor(connection);
|
JingleIBBTransportManager.getInstanceFor(connection);
|
||||||
|
|
||||||
|
JetManager jetm = JetManager.getInstanceFor(connection);
|
||||||
|
|
||||||
|
SignalOmemoService.acknowledgeLicense();
|
||||||
|
SignalOmemoService.setup();
|
||||||
|
String userHome = System.getProperty("user.home");
|
||||||
|
File storePath;
|
||||||
|
if (userHome != null) {
|
||||||
|
File f = new File(userHome);
|
||||||
|
storePath = new File(f, ".config/omemo_store");
|
||||||
|
} else {
|
||||||
|
storePath = new File("omemo_store");
|
||||||
|
}
|
||||||
|
OmemoConfiguration.setFileBasedOmemoStoreDefaultPath(storePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void login() throws InterruptedException, IOException, SmackException, XMPPException {
|
public void login() throws InterruptedException, IOException, SmackException, XMPPException, CorruptedOmemoKeyException {
|
||||||
((XMPPTCPConnection) connection).connect().login();
|
((XMPPTCPConnection) connection).connect().login();
|
||||||
|
OmemoManager omemoManager = OmemoManager.getInstanceFor(connection);
|
||||||
|
omemoManager.initialize();
|
||||||
|
connection.setReplyTimeout(10000);
|
||||||
|
JetManager.getInstanceFor(connection).registerEncryptionMethod(OmemoManager.getInstanceFor(connection));
|
||||||
|
JetManager.registerEncryptionMethodProvider(OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL, new OmemoVAxolotlProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addRemote(FullJid remote) {
|
public void addRemote(FullJid remote) {
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
package de.vanitasvitae.sync_client;
|
package de.vanitasvitae.sync_client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.AbstractXMPPConnection;
|
import org.jivesoftware.smack.AbstractXMPPConnection;
|
||||||
import org.jivesoftware.smack.SmackConfiguration;
|
import org.jivesoftware.smack.SmackConfiguration;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
@ -11,6 +20,10 @@ import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.roster.Roster;
|
import org.jivesoftware.smack.roster.Roster;
|
||||||
import org.jivesoftware.smack.roster.RosterEntry;
|
import org.jivesoftware.smack.roster.RosterEntry;
|
||||||
|
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
|
||||||
|
import org.jivesoftware.smackx.omemo.OmemoManager;
|
||||||
|
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||||
|
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
||||||
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
import org.apache.commons.cli.CommandLine;
|
||||||
import org.apache.commons.cli.CommandLineParser;
|
import org.apache.commons.cli.CommandLineParser;
|
||||||
|
@ -41,11 +54,17 @@ public class Main {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Slave slave(String username, String password, String dir) throws IOException, InterruptedException, XMPPException, SmackException {
|
public static Slave slave(String username, String password, String dir) throws IOException, InterruptedException,
|
||||||
|
XMPPException, SmackException, NoSuchPaddingException, CorruptedOmemoKeyException, InvalidKeyException,
|
||||||
|
NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException,
|
||||||
|
InvalidAlgorithmParameterException {
|
||||||
return new Slave(username, password, dir);
|
return new Slave(username, password, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Master master(String username, String password, String dir) throws IOException, InterruptedException, XMPPException, SmackException {
|
public static Master master(String username, String password, String dir) throws IOException, InterruptedException,
|
||||||
|
XMPPException, SmackException, CorruptedOmemoKeyException, InvalidKeyException, NoSuchAlgorithmException,
|
||||||
|
NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException,
|
||||||
|
InvalidAlgorithmParameterException {
|
||||||
return new Master(username, password, dir);
|
return new Master(username, password, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +114,7 @@ public class Main {
|
||||||
}
|
}
|
||||||
|
|
||||||
Roster roster = Roster.getInstanceFor(client.connection);
|
Roster roster = Roster.getInstanceFor(client.connection);
|
||||||
|
OmemoManager omemoManager = OmemoManager.getInstanceFor(client.connection);
|
||||||
|
|
||||||
switch (split[0]) {
|
switch (split[0]) {
|
||||||
case "/add":
|
case "/add":
|
||||||
|
@ -146,6 +166,52 @@ public class Main {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "/trust":
|
||||||
|
if (split.length != 2) {
|
||||||
|
System.out.println("Usage: /trust user@server.tld");
|
||||||
|
continue outerloop;
|
||||||
|
}
|
||||||
|
|
||||||
|
BareJid jid = getJid(split[1]);
|
||||||
|
omemoManager.requestDeviceListUpdateFor(jid);
|
||||||
|
omemoManager.requestDeviceListUpdateFor(client.connection.getUser().asBareJid());
|
||||||
|
HashMap<OmemoDevice, OmemoFingerprint> theirFPs = omemoManager.getActiveFingerprints(jid);
|
||||||
|
HashMap<OmemoDevice, OmemoFingerprint> ourFPs = omemoManager.getActiveFingerprints(client.connection.getUser().asBareJid());
|
||||||
|
System.out.println("Their fingerprints: <deviceId>: <fingerprint>: <trusted>");
|
||||||
|
for (OmemoDevice d : theirFPs.keySet()) {
|
||||||
|
OmemoFingerprint f = theirFPs.get(d);
|
||||||
|
System.out.println(d.getDeviceId() + ": " + f + ": " + omemoManager.isTrustedOmemoIdentity(d, f));
|
||||||
|
System.out.println("Trust? (y/n)");
|
||||||
|
String a = scanner.nextLine();
|
||||||
|
if (a.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
} else if (a.toLowerCase().equals("y")) {
|
||||||
|
omemoManager.trustOmemoIdentity(d, f);
|
||||||
|
System.out.println("Trusted.");
|
||||||
|
} else {
|
||||||
|
omemoManager.distrustOmemoIdentity(d, f);
|
||||||
|
System.out.println("Distrusted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Our fingerprints: <deviceId>: <fingerprint>: <trusted>");
|
||||||
|
for (OmemoDevice d : ourFPs.keySet()) {
|
||||||
|
OmemoFingerprint f = ourFPs.get(d);
|
||||||
|
System.out.println(d.getDeviceId() + ": " + f + ": " + omemoManager.isTrustedOmemoIdentity(d, f));
|
||||||
|
System.out.println("Trust? (y/n)");
|
||||||
|
String a = scanner.nextLine();
|
||||||
|
if (a.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
} else if (a.toLowerCase().equals("y")) {
|
||||||
|
omemoManager.trustOmemoIdentity(d, f);
|
||||||
|
System.out.println("Trusted.");
|
||||||
|
} else {
|
||||||
|
omemoManager.distrustOmemoIdentity(d, f);
|
||||||
|
System.out.println("Distrusted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case "/quit":
|
case "/quit":
|
||||||
((AbstractXMPPConnection) client.connection).disconnect(new Presence(Presence.Type.unavailable, "Shutdown", 100, Presence.Mode.away));
|
((AbstractXMPPConnection) client.connection).disconnect(new Presence(Presence.Type.unavailable, "Shutdown", 100, Presence.Mode.away));
|
||||||
break outerloop;
|
break outerloop;
|
||||||
|
|
|
@ -17,12 +17,23 @@ import java.nio.file.WatchEvent;
|
||||||
import java.nio.file.WatchKey;
|
import java.nio.file.WatchKey;
|
||||||
import java.nio.file.WatchService;
|
import java.nio.file.WatchService;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.util.Async;
|
import org.jivesoftware.smack.util.Async;
|
||||||
import org.jivesoftware.smackx.jft.JingleFileTransferManager;
|
import org.jivesoftware.smackx.jet.JetManager;
|
||||||
|
import org.jivesoftware.smackx.jingle_filetransfer.JingleFileTransferManager;
|
||||||
|
import org.jivesoftware.smackx.omemo.OmemoManager;
|
||||||
|
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||||
|
|
||||||
import org.jxmpp.jid.FullJid;
|
import org.jxmpp.jid.FullJid;
|
||||||
|
|
||||||
|
@ -31,18 +42,23 @@ public class Master extends Client {
|
||||||
private final WatchService fileWatcher;
|
private final WatchService fileWatcher;
|
||||||
private final HashMap<WatchKey, Path> fileKeys;
|
private final HashMap<WatchKey, Path> fileKeys;
|
||||||
private boolean trace;
|
private boolean trace;
|
||||||
private JingleFileTransferManager jftm;
|
private final JingleFileTransferManager jftm;
|
||||||
|
private final JetManager jetm;
|
||||||
|
|
||||||
public Master(String username, String password, String directory) throws IOException, InterruptedException, SmackException, XMPPException {
|
public Master(String username, String password, String directory) throws IOException, InterruptedException,
|
||||||
|
SmackException, XMPPException, CorruptedOmemoKeyException, InvalidAlgorithmParameterException,
|
||||||
|
NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException,
|
||||||
|
NoSuchProviderException, InvalidKeyException {
|
||||||
super(username, password, directory);
|
super(username, password, directory);
|
||||||
fileWatcher = FileSystems.getDefault().newWatchService();
|
fileWatcher = FileSystems.getDefault().newWatchService();
|
||||||
fileKeys = new HashMap<>();
|
fileKeys = new HashMap<>();
|
||||||
registerFileWatcher();
|
registerFileWatcher();
|
||||||
jftm = JingleFileTransferManager.getInstanceFor(connection);
|
jftm = JingleFileTransferManager.getInstanceFor(connection);
|
||||||
|
jetm = JetManager.getInstanceFor(connection);
|
||||||
jftm.addIncomingFileOfferListener(offer -> {
|
jftm.addIncomingFileOfferListener(offer -> {
|
||||||
try {
|
try {
|
||||||
offer.accept(connection, new File(root.toFile(), offer.getFile().getName()));
|
offer.accept(connection, new File(root.toFile(), offer.getFile().getName()));
|
||||||
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NoResponseException | SmackException.NotConnectedException e) {
|
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NoResponseException | SmackException.NotConnectedException | IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -126,14 +142,6 @@ public class Master extends Client {
|
||||||
{
|
{
|
||||||
WatchKey key = dir.register(fileWatcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
|
WatchKey key = dir.register(fileWatcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
|
||||||
if (trace) {
|
if (trace) {
|
||||||
Path prev = fileKeys.get(key);
|
|
||||||
if (prev == null) {
|
|
||||||
System.out.format("register: %s\n", dir);
|
|
||||||
} else {
|
|
||||||
if (!dir.equals(prev)) {
|
|
||||||
System.out.format("update: %s -> %s\n", prev, dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File[] files = dir.toFile().listFiles();
|
File[] files = dir.toFile().listFiles();
|
||||||
for (File f : files) {
|
for (File f : files) {
|
||||||
if (f.isFile()) {
|
if (f.isFile()) {
|
||||||
|
@ -181,7 +189,12 @@ public class Master extends Client {
|
||||||
throw new AssertionError("Illegal path! " + fileName);
|
throw new AssertionError("Illegal path! " + fileName);
|
||||||
}
|
}
|
||||||
for (FullJid recipient : remotes) {
|
for (FullJid recipient : remotes) {
|
||||||
jftm.sendFile(file, fileName, recipient);
|
try {
|
||||||
|
jetm.sendEncryptedFile(file, fileName, recipient, OmemoManager.getInstanceFor(connection));
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("Could not send file: " + e);
|
||||||
|
}
|
||||||
|
//jftm.sendFile(file, fileName, recipient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
package de.vanitasvitae.sync_client;
|
package de.vanitasvitae.sync_client;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smackx.jft.JingleFileTransferManager;
|
import org.jivesoftware.smackx.jingle_filetransfer.JingleFileTransferManager;
|
||||||
|
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||||
|
|
||||||
import org.jxmpp.stringprep.XmppStringprepException;
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
public class Slave extends Client {
|
public class Slave extends Client {
|
||||||
|
|
||||||
public Slave(String username, String password, String dir) throws XmppStringprepException {
|
public Slave(String username, String password, String dir) throws XmppStringprepException,
|
||||||
|
InvalidAlgorithmParameterException, NoSuchAlgorithmException, UnsupportedEncodingException,
|
||||||
|
InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException,
|
||||||
|
BadPaddingException, CorruptedOmemoKeyException, NoSuchProviderException, IllegalBlockSizeException,
|
||||||
|
SmackException {
|
||||||
super(username, password, dir);
|
super(username, password, dir);
|
||||||
JingleFileTransferManager jingleFileTransferManager = JingleFileTransferManager.getInstanceFor(connection);
|
JingleFileTransferManager jingleFileTransferManager = JingleFileTransferManager.getInstanceFor(connection);
|
||||||
jingleFileTransferManager.addIncomingFileOfferListener(offer -> {
|
jingleFileTransferManager.addIncomingFileOfferListener(offer -> {
|
||||||
|
@ -18,7 +32,7 @@ public class Slave extends Client {
|
||||||
File target = new File(root.toFile(), offer.getFile().getName());
|
File target = new File(root.toFile(), offer.getFile().getName());
|
||||||
target.getParentFile().mkdirs();
|
target.getParentFile().mkdirs();
|
||||||
offer.accept(connection, target);
|
offer.accept(connection, target);
|
||||||
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NoResponseException | SmackException.NotConnectedException e) {
|
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NoResponseException | SmackException.NotConnectedException | IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue