diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/JetManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/JetManager.java index 78e711064..5521a3aa1 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/JetManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/JetManager.java @@ -16,26 +16,54 @@ */ package org.jivesoftware.smackx.jet; +import java.io.File; +import java.io.FileInputStream; +import java.security.SecureRandom; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; +import java.util.logging.Logger; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.Element; +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smackx.jingle.JingleManager; +import org.jivesoftware.smackx.jingle.JingleUtil; +import org.jivesoftware.smackx.jingle.element.Jingle; +import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.OutgoingJingleFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransfer; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChild; +import org.jivesoftware.smackx.jingle_filetransfer.handler.FileTransferHandler; + +import org.jxmpp.jid.FullJid; /** * Manager for Jingle Encrypted Transfers (XEP-XXXX). */ public final class JetManager extends Manager { + private static final Logger LOGGER = Logger.getLogger(JetManager.class.getName()); + public static final String NAMESPACE = "urn:xmpp:jingle:jet:0"; private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + private final JingleUtil jutil; - private static final Map encryptionProviders = new HashMap<>(); + private static final Map encryptionMethods = new HashMap<>(); private JetManager(XMPPConnection connection) { super(connection); + jutil = new JingleUtil(connection); } public static JetManager getInstanceFor(XMPPConnection connection) { @@ -49,16 +77,91 @@ public final class JetManager extends Manager { return manager; } + public FileTransferHandler sendEncryptedFile(FullJid recipient, File file, String encryptionMethodNamespace) throws Exception { - public void registerEncryptionProvider(String namespace, JingleEncryptionMethod provider) { - encryptionProviders.put(namespace, provider); + JingleEncryptionMethod encryptionMethod = getEncryptionMethod(encryptionMethodNamespace); + if (encryptionMethod == null) { + throw new IllegalStateException("No encryption method with namespace " + encryptionMethodNamespace + " registered."); + } + + int keyLength = 256; + String keyType = "AES"; + String cipherMode = "AES/GCM/NoPadding"; + + KeyGenerator keyGenerator = KeyGenerator.getInstance(keyType); + keyGenerator.init(keyLength); + byte[] key = keyGenerator.generateKey().getEncoded(); + + SecureRandom secureRandom = new SecureRandom(); + byte[] iv = new byte[keyLength]; + secureRandom.nextBytes(iv); + + byte[] keyAndIv = new byte[2 * keyLength]; + System.arraycopy(key, 0, keyAndIv, 0, keyLength); + System.arraycopy(iv, 0, keyAndIv, keyLength, keyLength); + + SecretKey secretKey = new SecretKeySpec(key, keyType); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance(cipherMode, "BC"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + + Exception ioe = null; + byte[] fileBuf = null; + + FileInputStream fi = null; + CipherInputStream ci = null; + + try { + fi = new FileInputStream(file); + ci = new CipherInputStream(fi, cipher); + + fileBuf = new byte[(int) file.length()]; + ci.read(fileBuf); + + } catch (Exception e) { + ioe = e; + + } finally { + + if (ci != null) { + ci.close(); + } + + if (fi != null) { + fi.close(); + } + } + + if (ioe != null) { + throw ioe; + } + + if (fileBuf == null) { + return null; + } + + ExtensionElement encryptionExtension = encryptionMethod.encryptJingleTransfer(recipient, keyAndIv); + + OutgoingJingleFileOffer offer = new OutgoingJingleFileOffer(connection(), recipient); + + JingleFileTransferChild fileTransferChild = JingleFileTransferChild.getBuilder().setFile(file).build(); + JingleFileTransfer fileTransfer = new JingleFileTransfer(Collections.singletonList(fileTransferChild)); + Jingle initiate = jutil.createSessionInitiateFileOffer(recipient, JingleManager.randomId(), JingleContent.Creator.initiator, + JingleManager.randomId(), fileTransfer, offer.getTransportSession().createTransport(), Collections.singletonList(encryptionExtension)); + return offer; } - public void unregisterEncryptionProvider(String namespace) { - encryptionProviders.remove(namespace); + + public void registerEncryptionMethod(String namespace, JingleEncryptionMethod method) { + encryptionMethods.put(namespace, method); } - public JingleEncryptionMethod getEncryptionProvider(String namespace) { - return encryptionProviders.get(namespace); + public void unregisterEncryptionMethod(String namespace) { + encryptionMethods.remove(namespace); } + + public JingleEncryptionMethod getEncryptionMethod(String namespace) { + return encryptionMethods.get(namespace); + } + } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/OutgoingJetOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/OutgoingJetOffer.java new file mode 100644 index 000000000..a0f17a3bb --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/OutgoingJetOffer.java @@ -0,0 +1,27 @@ +package org.jivesoftware.smackx.jet; + +import java.io.File; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle_filetransfer.OutgoingJingleFileOffer; + +import org.jxmpp.jid.FullJid; + +/** + * Created by vanitas on 14.07.17. + */ +public class OutgoingJetOffer extends OutgoingJingleFileOffer { + + public OutgoingJetOffer(XMPPConnection connection, FullJid responder, String sid) { + super(connection, responder, sid); + } + + public OutgoingJetOffer(XMPPConnection connection, FullJid recipient) { + super(connection, recipient); + } + + @Override + public void send(File file) { + + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/OutgoingJingleFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/OutgoingJingleFileOffer.java index c0cec237b..ccc9856e8 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/OutgoingJingleFileOffer.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/OutgoingJingleFileOffer.java @@ -122,7 +122,7 @@ public class OutgoingJingleFileOffer extends JingleFileTransferSession { state = State.pending; - Jingle initiate = jutil.createSessionInitiateFileOffer(getResponder(), getSessionId(), creator, name, file, transportSession.createTransport()); + Jingle initiate = jutil.createSessionInitiateFileOffer(getResponder(), getSessionId(), creator, name, file, transportSession.createTransport(), null); this.contents.addAll(initiate.getContents()); connection.sendStanza(initiate); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java index 8bfa8d467..5f4a0e947 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java @@ -16,9 +16,12 @@ */ package org.jivesoftware.smackx.jingle; +import java.util.List; + import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Element; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smackx.jingle.element.Jingle; @@ -60,7 +63,8 @@ public class JingleUtil { String contentName, JingleContent.Senders contentSenders, JingleContentDescription description, - JingleContentTransport transport) { + JingleContentTransport transport, + List additionalElements) { Jingle.Builder jb = Jingle.getBuilder(); jb.setAction(JingleAction.session_initiate) @@ -72,7 +76,8 @@ public class JingleUtil { .setName(contentName) .setSenders(contentSenders) .setDescription(description) - .setTransport(transport); + .setTransport(transport) + .addAdditionalElements(additionalElements); Jingle jingle = jb.addJingleContent(cb.build()).build(); jingle.setFrom(connection.getUser()); @@ -97,9 +102,10 @@ public class JingleUtil { JingleContent.Creator contentCreator, String contentName, JingleContentDescription description, - JingleContentTransport transport) { + JingleContentTransport transport, + List additionalElements) { return createSessionInitiate(recipient, sessionId, contentCreator, contentName, - JingleContent.Senders.initiator, description, transport); + JingleContent.Senders.initiator, description, transport, additionalElements); } public IQ sendSessionInitiateFileOffer(FullJid recipient, @@ -107,11 +113,12 @@ public class JingleUtil { JingleContent.Creator contentCreator, String contentName, JingleContentDescription description, - JingleContentTransport transport) + JingleContentTransport transport, + List additionalElements) throws SmackException.NotConnectedException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NoResponseException { - Jingle jingle = createSessionInitiateFileOffer(recipient, sessionId, contentCreator, contentName, description, transport); + Jingle jingle = createSessionInitiateFileOffer(recipient, sessionId, contentCreator, contentName, description, transport, additionalElements); return connection.createStanzaCollectorAndSend(jingle).nextResultOrThrow(); } @@ -121,12 +128,13 @@ public class JingleUtil { String contentName, JingleContent.Senders contentSenders, JingleContentDescription description, - JingleContentTransport transport) + JingleContentTransport transport, + List additionalElements) throws SmackException.NotConnectedException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NoResponseException { Jingle jingle = createSessionInitiate(recipient, sessionId, contentCreator, contentName, contentSenders, - description, transport); + description, transport, additionalElements); return connection.createStanzaCollectorAndSend(jingle).nextResult(); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java index d5a3741bc..34352415b 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java @@ -16,6 +16,10 @@ */ package org.jivesoftware.smackx.jingle.element; +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.packet.Element; import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; @@ -68,17 +72,22 @@ public final class JingleContent implements NamedElement { private final JingleContentTransport transport; + private final List additionalElements = new ArrayList<>(); + /** * Creates a content description.. */ private JingleContent(Creator creator, String disposition, String name, Senders senders, - JingleContentDescription description, JingleContentTransport transport) { + JingleContentDescription description, JingleContentTransport transport, List additionalElements) { this.creator = Objects.requireNonNull(creator, "Jingle content creator must not be null"); this.disposition = disposition; this.name = StringUtils.requireNotNullOrEmpty(name, "Jingle content name must not be null or empty"); this.senders = senders; this.description = description; this.transport = transport; + if (additionalElements != null) { + this.additionalElements.addAll(additionalElements); + } } public Creator getCreator() { @@ -115,6 +124,10 @@ public final class JingleContent implements NamedElement { return transport; } + public List getAdditionalElements() { + return additionalElements; + } + @Override public String getElementName() { return ELEMENT; @@ -132,6 +145,10 @@ public final class JingleContent implements NamedElement { xml.optAppend(description); xml.optElement(transport); + for (Element element : additionalElements) { + xml.element(element); + } + xml.closeElement(this); return xml; } @@ -153,6 +170,8 @@ public final class JingleContent implements NamedElement { private JingleContentTransport transport; + private List additionalElements = new ArrayList<>(); + private Builder() { } @@ -189,8 +208,15 @@ public final class JingleContent implements NamedElement { return this; } + public Builder addAdditionalElements(List elements) { + if (elements != null) { + additionalElements.addAll(elements); + } + return this; + } + public JingleContent build() { - return new JingleContent(creator, disposition, name, senders, description, transport); + return new JingleContent(creator, disposition, name, senders, description, transport, additionalElements); } } }