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 69a76b67f..47c0a5264 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 @@ -17,31 +17,22 @@ 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.ExtensionElement; -import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smackx.jet.element.JetSecurityElement; +import org.jivesoftware.smackx.jet.internal.JetSecurity; import org.jivesoftware.smackx.jft.controller.OutgoingFileOfferController; -import org.jivesoftware.smackx.jft.element.JingleFileTransferChildElement; -import org.jivesoftware.smackx.jft.element.JingleFileTransferElement; import org.jivesoftware.smackx.jft.internal.JingleOutgoingFileOffer; -import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; +import org.jivesoftware.smackx.jingle.JingleManager; +import org.jivesoftware.smackx.jingle.JingleTransportManager; +import org.jivesoftware.smackx.jingle.components.JingleContent; +import org.jivesoftware.smackx.jingle.components.JingleSession; import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle.util.Role; import org.jxmpp.jid.FullJid; @@ -56,8 +47,11 @@ public final class JetManager extends Manager { private static final Map encryptionMethods = new HashMap<>(); + private final JingleManager jingleManager; + private JetManager(XMPPConnection connection) { super(connection); + this.jingleManager = JingleManager.getInstanceFor(connection); } public static JetManager getInstanceFor(XMPPConnection connection) { @@ -72,85 +66,24 @@ public final class JetManager extends Manager { } public OutgoingFileOfferController sendEncryptedFile(FullJid recipient, File file, String encryptionMethodNamespace) throws Exception { - - JingleEncryptionMethod encryptionMethod = getEncryptionMethod(encryptionMethodNamespace); - if (encryptionMethod == null) { - throw new IllegalStateException("No encryption method with namespace " + encryptionMethodNamespace + " registered."); + if (file == null || !file.exists()) { + throw new IllegalArgumentException("File MUST NOT be null and MUST exist."); } - int keyLength = 256; - String keyType = "AES"; - String cipherMode = "AES/GCM/NoPadding"; + JingleSession session = jingleManager.createSession(Role.initiator, recipient); - 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; - } - - String contentName = StringUtils.randomString(24); - - ExtensionElement encryptionExtension = encryptionMethod.encryptJingleTransfer(recipient, keyAndIv); - JetSecurityElement securityElement = new JetSecurityElement(contentName, encryptionExtension); + JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator); + session.addContent(content); JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(file); + content.setDescription(offer); - JingleFileTransferChildElement fileTransferChild = JingleFileTransferChildElement.getBuilder().setFile(file).build(); - JingleFileTransferElement fileTransfer = new JingleFileTransferElement(Collections.singletonList(fileTransferChild)); + JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(); + content.setTransport(transportManager.createTransport(content)); - JingleContentElement content = JingleContentElement.getBuilder() - .setCreator(JingleContentElement.Creator.initiator) - .setName(contentName) - //.setTransport(offer.getTransportSession().createTransport()) - .setSecurity(securityElement) - .setDescription(fileTransfer) - .build(); + JetSecurity security = new JetSecurity(encryptionMethodNamespace, connection()); + content.setSecurity(security); + session.initiate(connection()); return offer; } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/internal/JetSecurity.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/internal/JetSecurity.java index fbd860124..1aa0c2025 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/internal/JetSecurity.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/internal/JetSecurity.java @@ -16,8 +16,25 @@ */ package org.jivesoftware.smackx.jet.internal; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.jet.JetManager; +import org.jivesoftware.smackx.jet.JingleEncryptionMethod; import org.jivesoftware.smackx.jet.element.JetSecurityElement; +import org.jivesoftware.smackx.jingle.callbacks.JingleSecurityCallback; import org.jivesoftware.smackx.jingle.components.JingleSecurity; import org.jivesoftware.smackx.jingle.element.JingleContentSecurityInfoElement; import org.jivesoftware.smackx.jingle.element.JingleElement; @@ -30,8 +47,53 @@ public class JetSecurity extends JingleSecurity { public static final String NAMESPACE_V0 = "urn:xmpp:jingle:jet:0"; public static final String NAMESPACE = NAMESPACE_V0; + private final String methodNamespace; + + private final Cipher cipher; + private ExtensionElement child; + public JetSecurity(Cipher cipher, ExtensionElement child) { + super(); + this.cipher = cipher; + this.child = child; + this.methodNamespace = child.getNamespace(); + } + + public JetSecurity(String methodNamespace, XMPPConnection connection) + throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, + InvalidAlgorithmParameterException, InvalidKeyException { + this.methodNamespace = methodNamespace; + + JetManager jetManager = JetManager.getInstanceFor(connection); + JingleEncryptionMethod encryptionMethod = jetManager.getEncryptionMethod(methodNamespace); + if (encryptionMethod == null) { + throw new IllegalStateException("No encryption method with namespace " + methodNamespace + " registered."); + } + + //Create key and cipher + 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.getInstance(cipherMode, "BC"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + } + @Override public JetSecurityElement getElement() { return new JetSecurityElement(getParent().getName(), child); @@ -41,4 +103,16 @@ public class JetSecurity extends JingleSecurity { public JingleElement handleSecurityInfo(JingleContentSecurityInfoElement element, JingleElement wrapping) { return null; } + + @Override + public void decryptIncomingBytestream(BytestreamSession bytestreamSession, JingleSecurityCallback callback) { + JetSecurityBytestreamSession securityBytestreamSession = new JetSecurityBytestreamSession(bytestreamSession, cipher); + callback.onSecurityReady(securityBytestreamSession); + } + + @Override + public void encryptIncomingBytestream(BytestreamSession bytestreamSession, JingleSecurityCallback callback) { + JetSecurityBytestreamSession securityBytestreamSession = new JetSecurityBytestreamSession(bytestreamSession, cipher); + callback.onSecurityReady(securityBytestreamSession); + } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/internal/JetSecurityBytestreamSession.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/internal/JetSecurityBytestreamSession.java new file mode 100644 index 000000000..9e7b543e5 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jet/internal/JetSecurityBytestreamSession.java @@ -0,0 +1,51 @@ +/** + * + * Copyright 2017 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.jet.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; + +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.jingle.components.JingleSecurityBytestreamSession; + +public class JetSecurityBytestreamSession extends JingleSecurityBytestreamSession { + private final Cipher cipher; + + public JetSecurityBytestreamSession(BytestreamSession session, Cipher cipher) { + super(session); + this.cipher = cipher; + } + + @Override + public InputStream getInputStream() throws IOException { + return new CipherInputStream(wrapped.getInputStream(), cipher); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return new CipherOutputStream(wrapped.getOutputStream(), cipher); + } + + @Override + public void close() throws IOException { + wrapped.close(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleIncomingFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleIncomingFileOffer.java index d3ed7e210..7cc129818 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleIncomingFileOffer.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleIncomingFileOffer.java @@ -55,7 +55,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer } @Override - public void onTransportReady(BytestreamSession bytestreamSession) { + public void onBytestreamReady(BytestreamSession bytestreamSession) { LOGGER.log(Level.INFO, "Receive file to " + target.getAbsolutePath()); File mFile = target; if (!mFile.exists()) { @@ -89,7 +89,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer outputStream.write(filebuf); outputStream.flush(); - + } catch (IOException e) { LOGGER.log(Level.SEVERE, "Cannot get InputStream from BytestreamSession: " + e, e); } finally { diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleIncomingFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleIncomingFileRequest.java index 2fbf3b077..7de7348a5 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleIncomingFileRequest.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleIncomingFileRequest.java @@ -55,7 +55,7 @@ public class JingleIncomingFileRequest extends AbstractJingleFileRequest } @Override - public void onTransportReady(BytestreamSession bytestreamSession) { + public void onBytestreamReady(BytestreamSession bytestreamSession) { File mFile = ((LocalFile) file).getFile(); OutputStream outputStream = null; InputStream inputStream = null; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleOutgoingFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleOutgoingFileRequest.java index b71697164..d33d39968 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleOutgoingFileRequest.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleOutgoingFileRequest.java @@ -47,7 +47,7 @@ public class JingleOutgoingFileRequest extends AbstractJingleFileRequest blacklist, XMPPConnection connection) throws SmackException.NotConnectedException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NoResponseException { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleDescription.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleDescription.java index 07bbc20e3..b5491b8f6 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleDescription.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleDescription.java @@ -42,7 +42,7 @@ public abstract class JingleDescription { public JingleContent getParent() { return parent; } + + public abstract void decryptIncomingBytestream(BytestreamSession bytestreamSession, JingleSecurityCallback callback); + + public abstract void encryptIncomingBytestream(BytestreamSession bytestreamSession, JingleSecurityCallback callbacks); }