Add support for XEP-xxxx Jingle Encrypted Transports

This commit is contained in:
vanitasvitae 2017-08-20 17:42:10 +02:00
parent a53a9f4f76
commit c77aee7010
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
24 changed files with 1382 additions and 11 deletions

View File

@ -0,0 +1,43 @@
/**
*
* 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.ciphers;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.NoSuchPaddingException;
public class Aes128GcmNoPadding extends AesGcmNoPadding {
public static final String NAMESPACE = "urn:xmpp:ciphers:aes-128-gcm-nopadding:0";
public Aes128GcmNoPadding(int MODE) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, InvalidAlgorithmParameterException {
super(128, MODE);
}
public Aes128GcmNoPadding(byte[] keyAndIv, int MODE) throws NoSuchProviderException, InvalidAlgorithmParameterException,
NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException {
super(AesGcmNoPadding.copyOfRange(keyAndIv, 0, 16), // 16 byte key
AesGcmNoPadding.copyOfRange(keyAndIv, 16, keyAndIv.length), MODE); // rest (12 byte) IV
}
@Override
public String getNamespace() {
return NAMESPACE;
}
}

View File

@ -0,0 +1,43 @@
/**
*
* 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.ciphers;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.NoSuchPaddingException;
public class Aes256GcmNoPadding extends AesGcmNoPadding {
public static final String NAMESPACE = "urn:xmpp:ciphers:aes-256-gcm-nopadding:0";
public Aes256GcmNoPadding(int MODE) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, InvalidAlgorithmParameterException {
super(256, MODE);
}
public Aes256GcmNoPadding(byte[] keyAndIv, int MODE) throws NoSuchProviderException, InvalidAlgorithmParameterException,
NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException {
super(AesGcmNoPadding.copyOfRange(keyAndIv, 0, 32), // 32 byte key
AesGcmNoPadding.copyOfRange(keyAndIv, 32, keyAndIv.length), MODE); // rest (12 byte) IV
}
@Override
public String getNamespace() {
return NAMESPACE;
}
}

View File

@ -0,0 +1,144 @@
/**
*
* 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.ciphers;
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;
public abstract class AesGcmNoPadding {
public static final String keyType = "AES";
public static final String cipherMode = "AES/GCM/NoPadding";
private final int length;
protected final Cipher cipher;
private final byte[] key, iv, keyAndIv;
AesGcmNoPadding(int bits, int MODE) throws NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {
this.length = bits;
int bytes = bits / 8;
KeyGenerator keyGenerator = KeyGenerator.getInstance(keyType);
keyGenerator.init(bits);
key = keyGenerator.generateKey().getEncoded();
SecureRandom secureRandom = new SecureRandom();
iv = new byte[12];
secureRandom.nextBytes(iv);
keyAndIv = new byte[bytes + 12];
System.arraycopy(key, 0, keyAndIv, 0, bytes);
System.arraycopy(iv, 0, keyAndIv, bytes, 12);
cipher = Cipher.getInstance(cipherMode, "BC");
SecretKey keySpec = new SecretKeySpec(key, keyType);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(MODE, keySpec, ivSpec);
}
public static AesGcmNoPadding createEncryptionKey(String cipherName)
throws NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidAlgorithmParameterException {
switch (cipherName) {
case Aes128GcmNoPadding.NAMESPACE:
return new Aes128GcmNoPadding(Cipher.ENCRYPT_MODE);
case Aes256GcmNoPadding.NAMESPACE:
return new Aes256GcmNoPadding(Cipher.ENCRYPT_MODE);
default: throw new NoSuchAlgorithmException("Invalid cipher.");
}
}
/**
* Create a new AES key.
* @param key key
* @param iv iv
* @param MODE cipher mode (Cipher.ENCRYPT_MODE / Cipher.DECRYPT_MODE)
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
*/
public AesGcmNoPadding(byte[] key, byte[] iv, int MODE) throws NoSuchPaddingException, NoSuchAlgorithmException,
NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException {
assert iv.length == 12;
this.length = key.length * 8;
this.key = key;
this.iv = iv;
keyAndIv = new byte[key.length + iv.length];
System.arraycopy(key, 0, keyAndIv, 0, key.length);
System.arraycopy(iv, 0, keyAndIv, key.length, iv.length);
cipher = Cipher.getInstance(cipherMode, "BC");
SecretKeySpec keySpec = new SecretKeySpec(key, keyType);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(MODE, keySpec, ivSpec);
}
public static AesGcmNoPadding createDecryptionKey(String namespace, byte[] serialized)
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException,
InvalidKeyException, NoSuchPaddingException {
switch (namespace) {
case Aes128GcmNoPadding.NAMESPACE:
return new Aes128GcmNoPadding(serialized, Cipher.DECRYPT_MODE);
case Aes256GcmNoPadding.NAMESPACE:
return new Aes256GcmNoPadding(serialized, Cipher.DECRYPT_MODE);
default: throw new NoSuchAlgorithmException("Invalid cipher.");
}
}
public byte[] getKeyAndIv() {
return keyAndIv.clone();
}
public byte[] getKey() {
return key.clone();
}
public byte[] getIv() {
return iv.clone();
}
public int getLength() {
return length;
}
public Cipher getCipher() {
return cipher;
}
public abstract String getNamespace();
static byte[] copyOfRange(byte[] source, int start, int end) {
byte[] copy = new byte[end - start];
System.arraycopy(source, start, copy, 0, end - start);
return copy;
}
}

View File

@ -0,0 +1,22 @@
/**
*
* 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.
*/
/**
* Smack's API for XEP-XXXX: Ciphers</a>.
* This contains some AES cipher utility functions.
*/
package org.jivesoftware.smackx.ciphers;

View File

@ -0,0 +1,187 @@
/**
*
* 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;
import java.io.File;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.HashMap;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.ciphers.Aes256GcmNoPadding;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.jet.component.JetSecurity;
import org.jivesoftware.smackx.jet.provider.JetSecurityProvider;
import org.jivesoftware.smackx.jingle.JingleDescriptionManager;
import org.jivesoftware.smackx.jingle.JingleManager;
import org.jivesoftware.smackx.jingle.JingleTransportManager;
import org.jivesoftware.smackx.jingle.component.JingleContent;
import org.jivesoftware.smackx.jingle.component.JingleSession;
import org.jivesoftware.smackx.jingle.element.JingleContentElement;
import org.jivesoftware.smackx.jingle.util.Role;
import org.jivesoftware.smackx.jingle_filetransfer.JingleFileTransferManager;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleOutgoingFileOffer;
import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController;
import org.jxmpp.jid.FullJid;
/**
* Manager for Jingle Encrypted Transfers (XEP-XXXX).
*/
public final class JetManager extends Manager implements JingleDescriptionManager {
private static final Logger LOGGER = Logger.getLogger(JetManager.class.getName());
private static final WeakHashMap<XMPPConnection, JetManager> INSTANCES = new WeakHashMap<>();
private static final HashMap<String, JingleEnvelopeManager> envelopeManagers = new HashMap<>();
private static final HashMap<String, ExtensionElementProvider<?>> envelopeProviders = new HashMap<>();
private final JingleManager jingleManager;
static {
JingleManager.addJingleSecurityAdapter(new JetSecurityAdapter());
JingleManager.addJingleSecurityProvider(new JetSecurityProvider());
}
private JetManager(XMPPConnection connection) {
super(connection);
this.jingleManager = JingleManager.getInstanceFor(connection);
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(getNamespace());
jingleManager.addJingleDescriptionManager(this);
}
public static JetManager getInstanceFor(XMPPConnection connection) {
JetManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new JetManager(connection);
INSTANCES.put(connection, manager);
}
return manager;
}
public OutgoingFileOfferController sendEncryptedFile(File file, FullJid recipient, JingleEnvelopeManager envelopeManager) throws Exception {
return sendEncryptedFile(file, JingleFile.fromFile(file, null, null, null), recipient, envelopeManager);
}
public OutgoingFileOfferController sendEncryptedFile(File file, JingleFile metadata, FullJid recipient, JingleEnvelopeManager envelopeManager) throws Exception {
if (file == null || !file.exists()) {
throw new IllegalArgumentException("File MUST NOT be null and MUST exist.");
}
throwIfRecipientLacksSupport(recipient);
JingleSession session = jingleManager.createSession(Role.initiator, recipient);
JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator);
session.addContent(content);
JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(file, metadata);
content.setDescription(offer);
JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(recipient);
content.setTransport(transportManager.createTransportForInitiator(content));
JetSecurity security = new JetSecurity(envelopeManager, recipient, content.getName(), Aes256GcmNoPadding.NAMESPACE);
content.setSecurity(security);
session.sendInitiate(connection());
return offer;
}
public OutgoingFileOfferController sendEncryptedStream(InputStream inputStream, JingleFile metadata, FullJid recipient, JingleEnvelopeManager envelopeManager)
throws XMPPException.XMPPErrorException, SmackException.FeatureNotSupportedException, SmackException.NotConnectedException,
InterruptedException, SmackException.NoResponseException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException,
JingleEnvelopeManager.JingleEncryptionException, NoSuchProviderException, InvalidAlgorithmParameterException {
throwIfRecipientLacksSupport(recipient);
JingleSession session = jingleManager.createSession(Role.initiator, recipient);
JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator);
session.addContent(content);
JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(inputStream, metadata);
content.setDescription(offer);
JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(recipient);
content.setTransport(transportManager.createTransportForInitiator(content));
JetSecurity security = new JetSecurity(envelopeManager, recipient, content.getName(), Aes256GcmNoPadding.NAMESPACE);
content.setSecurity(security);
session.sendInitiate(connection());
return offer;
}
public void registerEnvelopeManager(JingleEnvelopeManager method) {
envelopeManagers.put(method.getJingleEnvelopeNamespace(), method);
}
public void unregisterEnvelopeManager(String namespace) {
envelopeManagers.remove(namespace);
}
public JingleEnvelopeManager getEnvelopeManager(String namespace) {
return envelopeManagers.get(namespace);
}
public static void registerEnvelopeProvider(String namespace, ExtensionElementProvider<?> provider) {
envelopeProviders.put(namespace, provider);
}
public static void unregisterEnvelopeProvider(String namespace) {
envelopeProviders.remove(namespace);
}
public static ExtensionElementProvider<?> getEnvelopeProvider(String namespace) {
return envelopeProviders.get(namespace);
}
@Override
public String getNamespace() {
return JetSecurity.NAMESPACE;
}
@Override
public void notifySessionInitiate(JingleSession session) {
JingleFileTransferManager.getInstanceFor(connection()).notifySessionInitiate(session);
}
@Override
public void notifyContentAdd(JingleSession session, JingleContent content) {
JingleFileTransferManager.getInstanceFor(connection()).notifyContentAdd(session, content);
}
private void throwIfRecipientLacksSupport(FullJid recipient) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, SmackException.FeatureNotSupportedException {
if (!ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(recipient, getNamespace())) {
throw new SmackException.FeatureNotSupportedException(getNamespace(), recipient);
}
}
}

View File

@ -0,0 +1,35 @@
/**
*
* 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;
import org.jivesoftware.smackx.jet.component.JetSecurity;
import org.jivesoftware.smackx.jet.element.JetSecurityElement;
import org.jivesoftware.smackx.jingle.adapter.JingleSecurityAdapter;
import org.jivesoftware.smackx.jingle.element.JingleContentSecurityElement;
public class JetSecurityAdapter implements JingleSecurityAdapter<JetSecurity> {
@Override
public JetSecurity securityFromElement(JingleContentSecurityElement element) {
return new JetSecurity((JetSecurityElement) element);
}
@Override
public String getNamespace() {
return JetSecurity.NAMESPACE;
}
}

View File

@ -0,0 +1,52 @@
/**
*
* 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;
import java.security.NoSuchAlgorithmException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jxmpp.jid.FullJid;
/**
* Classes that implement this interface can be used to encrypt Jingle File Transfers.
*/
public interface JingleEnvelopeManager {
ExtensionElement encryptJingleTransfer(FullJid recipient, byte[] keyData)
throws JingleEncryptionException, InterruptedException, NoSuchAlgorithmException,
SmackException.NotConnectedException, SmackException.NoResponseException;
byte[] decryptJingleTransfer(FullJid sender, ExtensionElement envelope)
throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException;
class JingleEncryptionException extends Exception {
private static final long serialVersionUID = 1L;
public JingleEncryptionException(Throwable throwable) {
super(throwable);
}
}
XMPPConnection getConnection();
String getJingleEnvelopeNamespace();
}

View File

@ -0,0 +1,142 @@
/**
*
* 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.component;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smackx.bytestreams.BytestreamSession;
import org.jivesoftware.smackx.ciphers.AesGcmNoPadding;
import org.jivesoftware.smackx.jet.JetManager;
import org.jivesoftware.smackx.jet.JingleEnvelopeManager;
import org.jivesoftware.smackx.jet.element.JetSecurityElement;
import org.jivesoftware.smackx.jingle.callbacks.JingleSecurityCallback;
import org.jivesoftware.smackx.jingle.component.JingleSecurity;
import org.jivesoftware.smackx.jingle.element.JingleContentSecurityInfoElement;
import org.jivesoftware.smackx.jingle.element.JingleElement;
import org.jxmpp.jid.FullJid;
/**
* Created by vanitas on 22.07.17.
*/
public class JetSecurity extends JingleSecurity<JetSecurityElement> {
private static final Logger LOGGER = Logger.getLogger(JetSecurity.class.getName());
public static final String NAMESPACE_V0 = "urn:xmpp:jingle:jet:0";
public static final String NAMESPACE = NAMESPACE_V0;
private final String envelopeNamespace;
private AesGcmNoPadding aesKey;
private final ExtensionElement child;
private final String cipherName;
private final String contentName;
public JetSecurity(JetSecurityElement element) {
super();
this.child = element.getChild();
this.envelopeNamespace = element.getEnvelopeNamespace();
this.contentName = element.getContentName();
this.cipherName = element.getCipherName();
}
public JetSecurity(JingleEnvelopeManager envelopeManager, FullJid recipient, String contentName, String cipherName)
throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidAlgorithmParameterException, InvalidKeyException, InterruptedException,
JingleEnvelopeManager.JingleEncryptionException, SmackException.NotConnectedException,
SmackException.NoResponseException {
this.envelopeNamespace = envelopeManager.getJingleEnvelopeNamespace();
this.aesKey = AesGcmNoPadding.createEncryptionKey(cipherName);
this.child = envelopeManager.encryptJingleTransfer(recipient, aesKey.getKeyAndIv());
this.contentName = contentName;
this.cipherName = cipherName;
}
private void decryptEncryptionKey(JingleEnvelopeManager method, FullJid sender)
throws InterruptedException, JingleEnvelopeManager.JingleEncryptionException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, NoSuchProviderException, InvalidKeyException, NoSuchPaddingException {
byte[] keyAndIv = method.decryptJingleTransfer(sender, child);
aesKey = AesGcmNoPadding.createDecryptionKey(cipherName, keyAndIv);
}
@Override
public JetSecurityElement getElement() {
return new JetSecurityElement(contentName, cipherName, child);
}
@Override
public JingleElement handleSecurityInfo(JingleContentSecurityInfoElement element, JingleElement wrapping) {
return null;
}
@Override
public void decryptIncomingBytestream(BytestreamSession bytestreamSession, JingleSecurityCallback callback) {
if (aesKey == null) {
throw new IllegalStateException("Encryption key has not yet been decrypted.");
}
JetSecurityBytestreamSession securityBytestreamSession = new JetSecurityBytestreamSession(bytestreamSession, aesKey.getCipher());
callback.onSecurityReady(securityBytestreamSession);
}
@Override
public void encryptOutgoingBytestream(BytestreamSession bytestreamSession, JingleSecurityCallback callback) {
JetSecurityBytestreamSession securityBytestreamSession = new JetSecurityBytestreamSession(bytestreamSession, aesKey.getCipher());
callback.onSecurityReady(securityBytestreamSession);
}
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public void prepare(XMPPConnection connection, FullJid sender) {
if (getParent().getParent().isInitiator()) {
return;
}
if (aesKey != null) {
return;
}
JingleEnvelopeManager method = JetManager.getInstanceFor(connection).getEnvelopeManager(getEnvelopeNamespace());
if (method == null) {
throw new AssertionError("No JingleEncryptionMethodManager found for " + getEnvelopeNamespace());
}
try {
decryptEncryptionKey(method, sender);
} catch (InterruptedException | NoSuchPaddingException | InvalidKeyException | NoSuchProviderException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | JingleEnvelopeManager.JingleEncryptionException e) {
LOGGER.log(Level.SEVERE, "Could not decrypt security key: " + e, e);
}
}
public String getEnvelopeNamespace() {
return envelopeNamespace;
}
}

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.component;
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.component.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

@ -0,0 +1,22 @@
/**
*
* 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.
*/
/**
* Smack's API for XEP-XXXX: Jingle Encrypted Transfers</a>.
* Internal classes.
*/
package org.jivesoftware.smackx.jet.component;

View File

@ -0,0 +1,81 @@
/**
*
* 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.element;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.jet.component.JetSecurity;
import org.jivesoftware.smackx.jingle.element.JingleContentSecurityElement;
/**
* Implementation of the Jingle security element as specified in XEP-XXXX (Jingle Encrypted Transfers).
* <jingle>
* <content>
* <description/>
* <transport/>
* <security/> <- You are here.
* </content>
* </jingle>
*/
public class JetSecurityElement extends JingleContentSecurityElement {
public static final String ATTR_CONTENT_NAME = "name";
public static final String ATTR_ENVELOPE_TYPE = "type";
public static final String ATTR_CIPHER_TYPE = "cipher";
private final ExtensionElement child;
private final String contentName;
private final String cipherName;
public JetSecurityElement(String contentName, String cipherName, ExtensionElement child) {
this.contentName = contentName;
this.child = child;
this.cipherName = cipherName;
}
@Override
public CharSequence toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.attribute(ATTR_CONTENT_NAME, contentName)
.attribute(ATTR_CIPHER_TYPE, cipherName)
.attribute(ATTR_ENVELOPE_TYPE, child.getNamespace());
xml.rightAngleBracket();
xml.element(child);
xml.closeElement(this);
return xml;
}
@Override
public String getNamespace() {
return JetSecurity.NAMESPACE;
}
public String getEnvelopeNamespace() {
return child.getNamespace();
}
public ExtensionElement getChild() {
return child;
}
public String getContentName() {
return contentName;
}
public String getCipherName() {
return cipherName;
}
}

View File

@ -0,0 +1,22 @@
/**
*
* 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.
*/
/**
* Smack's API for XEP-XXXX: Jingle Encrypted Transfers</a>.
* Elements.
*/
package org.jivesoftware.smackx.jet.element;

View File

@ -0,0 +1,21 @@
/**
*
* 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.
*/
/**
* Smack's API for XEP-XXXX: Jingle Encrypted Transfers</a>.
*/
package org.jivesoftware.smackx.jet;

View File

@ -0,0 +1,65 @@
/**
*
* 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.provider;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.jet.JetManager;
import org.jivesoftware.smackx.jet.component.JetSecurity;
import org.jivesoftware.smackx.jet.element.JetSecurityElement;
import org.jivesoftware.smackx.jingle.provider.JingleContentSecurityProvider;
import org.xmlpull.v1.XmlPullParser;
/**
* Provider for the Jingle security element for XEP-XXXX (Jingle Encrypted Transfers).
*/
public class JetSecurityProvider extends JingleContentSecurityProvider<JetSecurityElement> {
private static final Logger LOGGER = Logger.getLogger(JetSecurityProvider.class.getName());
@Override
public JetSecurityElement parse(XmlPullParser parser, int initialDepth) throws Exception {
String name = parser.getAttributeValue("", JetSecurityElement.ATTR_CONTENT_NAME);
String cipher = parser.getAttributeValue("", JetSecurityElement.ATTR_CIPHER_TYPE);
String type = parser.getAttributeValue("", JetSecurityElement.ATTR_ENVELOPE_TYPE);
ExtensionElement child;
Objects.requireNonNull(type);
Objects.requireNonNull(cipher);
ExtensionElementProvider<?> encryptionElementProvider =
JetManager.getEnvelopeProvider(type);
if (encryptionElementProvider != null) {
child = encryptionElementProvider.parse(parser);
} else {
LOGGER.log(Level.WARNING, "Unknown child element in JetSecurityElement: " + type);
return null;
}
return new JetSecurityElement(name, cipher, child);
}
@Override
public String getNamespace() {
return JetSecurity.NAMESPACE;
}
}

View File

@ -0,0 +1,22 @@
/**
*
* 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.
*/
/**
* Smack's API for XEP-XXXX: Jingle Encrypted Transfers</a>.
* Providers.
*/
package org.jivesoftware.smackx.jet.provider;

View File

@ -0,0 +1,101 @@
/**
*
* 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.ciphers;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
import java.util.Random;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.junit.Test;
public class AesGcmNoPaddingTest extends SmackTestSuite {
@Test
public void Aes128Test() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, InvalidAlgorithmParameterException {
AesGcmNoPadding aes128 = AesGcmNoPadding.createEncryptionKey(Aes128GcmNoPadding.NAMESPACE);
assertNotNull(aes128);
assertEquals(16, aes128.getKey().length);
assertEquals(12, aes128.getIv().length);
assertEquals(28, aes128.getKeyAndIv().length);
assertNotNull(aes128.getCipher());
assertEquals(128, aes128.getLength());
}
@Test
public void Aes256Test() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, InvalidAlgorithmParameterException {
AesGcmNoPadding aes256 = AesGcmNoPadding.createEncryptionKey(Aes256GcmNoPadding.NAMESPACE);
assertNotNull(aes256);
assertEquals(32, aes256.getKey().length);
assertEquals(12, aes256.getIv().length);
assertEquals(44, aes256.getKeyAndIv().length);
assertNotNull(aes256.getCipher());
assertEquals(256, aes256.getLength());
}
@Test(expected = NoSuchAlgorithmException.class)
public void invalidEncryptionCipher() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, InvalidAlgorithmParameterException {
AesGcmNoPadding.createEncryptionKey("invalid");
}
@Test(expected = NoSuchAlgorithmException.class)
public void invalidDecryptionCipher() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, InvalidAlgorithmParameterException {
AesGcmNoPadding.createDecryptionKey("invalid", null);
}
@Test
public void encryption128Test() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
AesGcmNoPadding aes1 = AesGcmNoPadding.createEncryptionKey(Aes128GcmNoPadding.NAMESPACE);
AesGcmNoPadding aes2 = AesGcmNoPadding.createDecryptionKey(Aes128GcmNoPadding.NAMESPACE, aes1.getKeyAndIv());
byte[] data = new byte[4096];
new Random().nextBytes(data);
byte[] enc = aes1.getCipher().doFinal(data);
assertFalse(Arrays.equals(data, enc));
byte[] dec = aes2.getCipher().doFinal(enc);
assertTrue(Arrays.equals(dec, data));
}
@Test
public void encryption256Test() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
AesGcmNoPadding aes1 = AesGcmNoPadding.createEncryptionKey(Aes256GcmNoPadding.NAMESPACE);
AesGcmNoPadding aes2 = AesGcmNoPadding.createDecryptionKey(Aes256GcmNoPadding.NAMESPACE, aes1.getKeyAndIv());
byte[] data = new byte[4096];
new Random().nextBytes(data);
byte[] enc = aes1.getCipher().doFinal(data);
assertFalse(Arrays.equals(data, enc));
byte[] dec = aes2.getCipher().doFinal(enc);
assertTrue(Arrays.equals(dec, data));
}
}

View File

@ -0,0 +1,97 @@
/**
*
* 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;
import static junit.framework.TestCase.assertEquals;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smackx.ciphers.Aes128GcmNoPadding;
import org.jivesoftware.smackx.jet.component.JetSecurity;
import org.jivesoftware.smackx.jet.element.JetSecurityElement;
import org.junit.Test;
import org.jxmpp.jid.FullJid;
import org.xml.sax.SAXException;
public class JetElementTest extends SmackTestSuite {
@Test
public void jetTest() throws InterruptedException, JingleEnvelopeManager.JingleEncryptionException, NoSuchAlgorithmException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException, SAXException {
ExtensionElement child = new SecurityStub().encryptJingleTransfer(null, null);
JetSecurityElement element = new JetSecurityElement("content1", Aes128GcmNoPadding.NAMESPACE, child);
JetSecurity security = new JetSecurity(element);
assertEquals(SecurityStub.NAMESPACE, security.getEnvelopeNamespace());
assertEquals(Aes128GcmNoPadding.NAMESPACE, element.getCipherName());
assertEquals(SecurityStub.NAMESPACE, element.getEnvelopeNamespace());
assertEquals("content1", element.getContentName());
String xml = "<security xmlns='" + JetSecurity.NAMESPACE + "' " +
"name='content1' " +
"cipher='" + Aes128GcmNoPadding.NAMESPACE + "' " +
"type='" + SecurityStub.NAMESPACE + "'>" +
"<security-stub/>" +
"</security>";
assertXMLEqual(xml, security.getElement().toXML().toString());
}
private static class SecurityStub implements JingleEnvelopeManager {
public static final String NAMESPACE = "urn:xmpp:security-stub";
@Override
public ExtensionElement encryptJingleTransfer(FullJid recipient, byte[] keyData) throws JingleEncryptionException, InterruptedException, NoSuchAlgorithmException, SmackException.NotConnectedException, SmackException.NoResponseException {
return new ExtensionElement() {
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
public String getElementName() {
return "security-stub";
}
@Override
public CharSequence toXML() {
return "<security-stub/>";
}
};
}
@Override
public byte[] decryptJingleTransfer(FullJid sender, ExtensionElement envelope) throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
return new byte[0];
}
@Override
public XMPPConnection getConnection() {
return null;
}
@Override
public String getJingleEnvelopeNamespace() {
return NAMESPACE;
}
}
}

View File

@ -0,0 +1,145 @@
/**
*
* 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;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.subscribe;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.unidirectionalTrust;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager;
import org.jivesoftware.smackx.jingle.transport.jingle_s5b.JingleS5BTransportManager;
import org.jivesoftware.smackx.jingle_filetransfer.JingleFileTransferManager;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile;
import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController;
import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener;
import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener;
import org.jivesoftware.smackx.omemo.AbstractOmemoIntegrationTest;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoService;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.provider.OmemoVAxolotlProvider;
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
public class JetIntegrationTest extends AbstractOmemoIntegrationTest {
private OmemoManager oa, ob;
private JetManager ja, jb;
private JingleIBBTransportManager ia, ib;
private JingleS5BTransportManager sa, sb;
private OmemoStore<?,?,?,?,?,?,?,?,?> store;
public JetIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);
}
@Override
public void before() {
store = OmemoService.getInstance().getOmemoStoreBackend();
oa = OmemoManager.getInstanceFor(conOne, 666);
ob = OmemoManager.getInstanceFor(conTwo, 777);
ja = JetManager.getInstanceFor(conOne);
jb = JetManager.getInstanceFor(conTwo);
ia = JingleIBBTransportManager.getInstanceFor(conOne);
ib = JingleIBBTransportManager.getInstanceFor(conTwo);
sa = JingleS5BTransportManager.getInstanceFor(conOne);
sb = JingleS5BTransportManager.getInstanceFor(conTwo);
JetManager.registerEnvelopeProvider(OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL, new OmemoVAxolotlProvider());
}
@SmackIntegrationTest
public void JingleEncryptedFileTransferTest()
throws Exception {
final SimpleResultSyncPoint received = new SimpleResultSyncPoint();
Random weakRandom = new Random();
//Setup OMEMO
subscribe(oa, ob, "Bob");
subscribe(ob, oa, "Alice");
setUpOmemoManager(oa);
setUpOmemoManager(ob);
unidirectionalTrust(oa, ob);
unidirectionalTrust(ob, oa);
ja.registerEnvelopeManager(oa);
jb.registerEnvelopeManager(ob);
byte[] sourceBytes = new byte[16000];
weakRandom.nextBytes(sourceBytes);
InputStream sourceStream = new ByteArrayInputStream(sourceBytes);
final ByteArrayOutputStream targetStream = new ByteArrayOutputStream(16000);
JingleFileTransferManager.getInstanceFor(conTwo).addIncomingFileOfferListener(new IncomingFileOfferListener() {
@Override
public void onIncomingFileOffer(IncomingFileOfferController offer) {
try {
offer.addProgressListener(new ProgressListener() {
@Override
public void started() {
}
@Override
public void progress(float percent) {
}
@Override
public void finished() {
received.signal();
}
});
offer.accept(conTwo, targetStream);
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException | IOException e) {
received.signal(e);
}
}
});
ja.sendEncryptedStream(sourceStream, new JingleFile("test", "desc", (long) sourceBytes.length, null, null, null), conTwo.getUser().asFullJidOrThrow(), oa);
received.waitForResult(60 * 1000);
assertArrayEquals(sourceBytes, targetStream.toByteArray());
}
@Override
public void after() {
oa.shutdown();
ob.shutdown();
cleanServerSideTraces(oa);
cleanServerSideTraces(ob);
}
}

View File

@ -0,0 +1,21 @@
/**
*
* 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.
*/
/**
* Tests for XEP-XXXX - Jingle Encrypted Transfers.
*/
package org.jivesoftware.smackx.jet;

View File

@ -28,7 +28,6 @@ import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smack.roster.RosterEntry;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
@ -41,11 +40,11 @@ import org.jivesoftware.smackx.pubsub.PubSubManager;
/**
* Class containing some helper methods for OmemoIntegrationTests.
*/
final class OmemoIntegrationTestHelper {
public final class OmemoIntegrationTestHelper {
private static final Logger LOGGER = Logger.getLogger(OmemoIntegrationTestHelper.class.getSimpleName());
static void cleanServerSideTraces(OmemoManager omemoManager) {
public static void cleanServerSideTraces(OmemoManager omemoManager) {
cleanUpPubSub(omemoManager);
cleanUpRoster(omemoManager);
}
@ -115,7 +114,7 @@ final class OmemoIntegrationTestHelper {
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
static void subscribe(OmemoManager alice, OmemoManager bob, String nick)
public static void subscribe(OmemoManager alice, OmemoManager bob, String nick)
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
@ -127,7 +126,7 @@ final class OmemoIntegrationTestHelper {
}
static void unidirectionalTrust(OmemoManager alice, OmemoManager bob) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException {
public static void unidirectionalTrust(OmemoManager alice, OmemoManager bob) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException {
//Fetch deviceList
alice.requestDeviceListUpdateFor(bob.getOwnJid());
LOGGER.log(Level.INFO, "Current deviceList state: " + alice.getOwnDevice() + " knows " + bob.getOwnDevice() + ": "
@ -147,7 +146,7 @@ final class OmemoIntegrationTestHelper {
}
static void setUpOmemoManager(OmemoManager omemoManager) throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, NotAPubSubNodeException {
public static void setUpOmemoManager(OmemoManager omemoManager) throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, NotAPubSubNodeException {
omemoManager.initialize();
OmemoBundleElement bundle = OmemoService.fetchBundle(omemoManager, omemoManager.getOwnDevice());
assertNotNull("Bundle must not be null.", bundle);

View File

@ -22,6 +22,7 @@ import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNotSame;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.deletePath;
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
@ -154,6 +155,8 @@ public class OmemoStoreTest extends AbstractOmemoIntegrationTest {
@Override
public void after() {
cleanServerSideTraces(alice);
cleanServerSideTraces(bob);
alice.shutdown();
bob.shutdown();
}

View File

@ -48,7 +48,7 @@ public class SmackOmemoSignalIntegrationTestFramework {
SignalOmemoService.acknowledgeLicense();
SignalOmemoService.setup();
final String[] smackOmemoPackages = new String[] { "org.jivesoftware.smackx.omemo" };
final String[] smackOmemoPackages = new String[] { "org.jivesoftware.smackx.omemo", "org.jivesoftware.smackx.jet" };
SmackIntegrationTestFramework.main(smackOmemoPackages);
}

View File

@ -41,11 +41,12 @@ import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
import org.jivesoftware.smackx.hints.element.StoreHint;
import org.jivesoftware.smackx.jet.JingleEnvelopeManager;
import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.MultiUserChatManager;
@ -66,6 +67,7 @@ import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener;
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.pep.PEPListener;
import org.jivesoftware.smackx.pep.PEPManager;
import org.jivesoftware.smackx.pubsub.EventElement;
@ -78,6 +80,7 @@ import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.FullJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
@ -88,7 +91,7 @@ import org.jxmpp.stringprep.XmppStringprepException;
* @author Paul Schaub
*/
public final class OmemoManager extends Manager {
public final class OmemoManager extends Manager implements JingleEnvelopeManager {
private static final Logger LOGGER = Logger.getLogger(OmemoManager.class.getName());
private static final WeakHashMap<XMPPConnection, WeakHashMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>();
@ -133,6 +136,7 @@ public final class OmemoManager extends Manager {
});
service = OmemoService.getInstance();
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL);
}
/**
@ -751,10 +755,20 @@ public final class OmemoManager extends Manager {
*
* @return the connection of this manager
*/
XMPPConnection getConnection() {
@Override
public XMPPConnection getConnection() {
return connection();
}
public static String getNamespace() {
return OMEMO_NAMESPACE_V_AXOLOTL;
}
@Override
public String getJingleEnvelopeNamespace() {
return getNamespace();
}
/**
* Return the OMEMO service object.
*
@ -840,4 +854,44 @@ public final class OmemoManager extends Manager {
}
return omemoCarbonCopyListener;
}
@Override
public ExtensionElement encryptJingleTransfer(FullJid recipient, byte[] keyData) throws JingleEncryptionException, InterruptedException, NoSuchAlgorithmException, SmackException.NotConnectedException, SmackException.NoResponseException {
BareJid bareJid = recipient.asBareJid();
Message EncryptedMessage;
try {
EncryptedMessage = encrypt(bareJid, Base64.encodeToString(keyData));
} catch (CryptoFailedException | UndecidedOmemoIdentityException | CannotEstablishOmemoSessionException e) {
throw new JingleEncryptionException(e);
}
ExtensionElement encryptionElement = EncryptedMessage.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
if (encryptionElement == null) {
throw new AssertionError("OmemoElement MUST NOT be null.");
}
return encryptionElement;
}
@Override
public byte[] decryptJingleTransfer(FullJid sender, ExtensionElement envelope) throws JingleEncryptionException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
if (!envelope.getNamespace().equals(OMEMO_NAMESPACE_V_AXOLOTL)
|| !envelope.getElementName().equals(OmemoElement.ENCRYPTED)) {
throw new IllegalArgumentException("Passed ExtensionElement MUST be an OmemoElement!");
}
OmemoElement omemoElement = (OmemoElement) envelope;
Message pseudoMessage = new Message();
pseudoMessage.setFrom(sender.asBareJid());
pseudoMessage.addExtension(omemoElement);
ClearTextMessage decryptedPseudoMessage;
try {
decryptedPseudoMessage = decrypt(sender.asBareJid(), pseudoMessage);
} catch (CryptoFailedException | CorruptedOmemoKeyException | NoRawSessionException e) {
throw new JingleEncryptionException(e);
}
return Base64.decode(decryptedPseudoMessage.getBody());
}
}

View File

@ -51,7 +51,6 @@ import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;