mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 20:42:06 +01:00
Work on Jingle Encrypted Transfers
This commit is contained in:
parent
1dbdafe28c
commit
c82b25ea09
11 changed files with 210 additions and 96 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -55,7 +55,7 @@ public class JingleIncomingFileRequest extends AbstractJingleFileRequest<RemoteF
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTransportReady(BytestreamSession bytestreamSession) {
|
||||
public void onBytestreamReady(BytestreamSession bytestreamSession) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -47,7 +47,7 @@ public class JingleOutgoingFileRequest extends AbstractJingleFileRequest<RemoteF
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTransportReady(BytestreamSession bytestreamSession) {
|
||||
public void onBytestreamReady(BytestreamSession bytestreamSession) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue