Work on Jingle Encrypted Transfers

This commit is contained in:
vanitasvitae 2017-07-30 15:40:04 +02:00
parent 1dbdafe28c
commit c82b25ea09
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
11 changed files with 210 additions and 96 deletions

View File

@ -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<String, JingleEncryptionMethod> 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.<JingleContentDescriptionChildElement>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;
}

View File

@ -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<JetSecurityElement> {
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<JetSecurityElement> {
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);
}
}

View File

@ -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();
}
}

View File

@ -55,7 +55,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer<RemoteFile>
}
@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<RemoteFile>
outputStream.write(filebuf);
outputStream.flush();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Cannot get InputStream from BytestreamSession: " + e, e);
} finally {

View File

@ -55,7 +55,7 @@ public class JingleIncomingFileRequest extends AbstractJingleFileRequest<RemoteF
}
@Override
public void onTransportReady(BytestreamSession bytestreamSession) {
public void onBytestreamReady(BytestreamSession bytestreamSession) {
}
}

View File

@ -46,7 +46,7 @@ public class JingleOutgoingFileOffer extends AbstractJingleFileOffer<LocalFile>
}
@Override
public void onTransportReady(BytestreamSession bytestreamSession) {
public void onBytestreamReady(BytestreamSession bytestreamSession) {
File mFile = ((LocalFile) file).getFile();
OutputStream outputStream = null;
InputStream inputStream = null;

View File

@ -47,7 +47,7 @@ public class JingleOutgoingFileRequest extends AbstractJingleFileRequest<RemoteF
}
@Override
public void onTransportReady(BytestreamSession bytestreamSession) {
public void onBytestreamReady(BytestreamSession bytestreamSession) {
}
}

View File

@ -0,0 +1,29 @@
/**
*
* 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.jingle.callbacks;
import org.jivesoftware.smackx.bytestreams.BytestreamSession;
/**
* Callback used to signal success/failure of Jingle Security components.
*/
public interface JingleSecurityCallback {
void onSecurityReady(BytestreamSession bytestreamSession);
void onSecurityFailed(Exception e);
}

View File

@ -37,6 +37,7 @@ import org.jivesoftware.smackx.jingle.JingleTransportManager;
import org.jivesoftware.smackx.jingle.adapter.JingleDescriptionAdapter;
import org.jivesoftware.smackx.jingle.adapter.JingleSecurityAdapter;
import org.jivesoftware.smackx.jingle.adapter.JingleTransportAdapter;
import org.jivesoftware.smackx.jingle.callbacks.JingleSecurityCallback;
import org.jivesoftware.smackx.jingle.callbacks.JingleTransportCallback;
import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionElement;
import org.jivesoftware.smackx.jingle.element.JingleContentElement;
@ -48,7 +49,7 @@ import org.jivesoftware.smackx.jingle.element.JingleReasonElement;
/**
* Internal class that holds the state of a content in a modifiable form.
*/
public class JingleContent implements JingleTransportCallback {
public class JingleContent implements JingleTransportCallback, JingleSecurityCallback {
private static final Logger LOGGER = Logger.getLogger(JingleContent.class.getName());
@ -131,7 +132,7 @@ public class JingleContent implements JingleTransportCallback {
return new JingleContent(description, transport, security, content.getName(), content.getDisposition(), content.getCreator(), content.getSenders());
}
/* HANDLEXYZ */
/* HANDLE_XYZ */
public IQ handleJingleRequest(JingleElement request, XMPPConnection connection) {
switch (request.getAction()) {
@ -334,7 +335,17 @@ public class JingleContent implements JingleTransportCallback {
throw new AssertionError("bytestreamSession MUST NOT be null at this point.");
}
description.onTransportReady(bytestreamSession);
if (security != null) {
if (isReceiving()) {
LOGGER.log(Level.INFO, "Decrypt incoming Bytestream.");
getSecurity().decryptIncomingBytestream(bytestreamSession, this);
} else if (isSending()) {
LOGGER.log(Level.INFO, "Encrypt outgoing Bytestream.");
getSecurity().encryptIncomingBytestream(bytestreamSession, this);
}
} else {
description.onBytestreamReady(bytestreamSession);
}
}
@Override
@ -352,6 +363,16 @@ public class JingleContent implements JingleTransportCallback {
}
}
@Override
public void onSecurityReady(BytestreamSession bytestreamSession) {
getDescription().onBytestreamReady(bytestreamSession);
}
@Override
public void onSecurityFailed(Exception e) {
LOGGER.log(Level.SEVERE, "Security failed: " + e, e);
}
private void replaceTransport(Set<String> blacklist, XMPPConnection connection)
throws SmackException.NotConnectedException, InterruptedException,
XMPPException.XMPPErrorException, SmackException.NoResponseException {

View File

@ -42,7 +42,7 @@ public abstract class JingleDescription<D extends JingleContentDescriptionElemen
return parent;
}
public abstract void onTransportReady(BytestreamSession bytestreamSession);
public abstract void onBytestreamReady(BytestreamSession bytestreamSession);
public abstract String getNamespace();
}

View File

@ -16,6 +16,8 @@
*/
package org.jivesoftware.smackx.jingle.components;
import org.jivesoftware.smackx.bytestreams.BytestreamSession;
import org.jivesoftware.smackx.jingle.callbacks.JingleSecurityCallback;
import org.jivesoftware.smackx.jingle.element.JingleContentSecurityElement;
import org.jivesoftware.smackx.jingle.element.JingleContentSecurityInfoElement;
import org.jivesoftware.smackx.jingle.element.JingleElement;
@ -40,4 +42,8 @@ public abstract class JingleSecurity<D extends JingleContentSecurityElement> {
public JingleContent getParent() {
return parent;
}
public abstract void decryptIncomingBytestream(BytestreamSession bytestreamSession, JingleSecurityCallback callback);
public abstract void encryptIncomingBytestream(BytestreamSession bytestreamSession, JingleSecurityCallback callbacks);
}