mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 20:42:06 +01:00
Add support for XEP-xxxx Jingle Encrypted Transports
This commit is contained in:
parent
a53a9f4f76
commit
c77aee7010
24 changed files with 1382 additions and 11 deletions
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -28,7 +28,6 @@ import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.roster.Roster;
|
import org.jivesoftware.smack.roster.Roster;
|
||||||
import org.jivesoftware.smack.roster.RosterEntry;
|
import org.jivesoftware.smack.roster.RosterEntry;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
|
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
|
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||||
|
@ -41,11 +40,11 @@ import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||||
/**
|
/**
|
||||||
* Class containing some helper methods for OmemoIntegrationTests.
|
* Class containing some helper methods for OmemoIntegrationTests.
|
||||||
*/
|
*/
|
||||||
final class OmemoIntegrationTestHelper {
|
public final class OmemoIntegrationTestHelper {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(OmemoIntegrationTestHelper.class.getSimpleName());
|
private static final Logger LOGGER = Logger.getLogger(OmemoIntegrationTestHelper.class.getSimpleName());
|
||||||
|
|
||||||
static void cleanServerSideTraces(OmemoManager omemoManager) {
|
public static void cleanServerSideTraces(OmemoManager omemoManager) {
|
||||||
cleanUpPubSub(omemoManager);
|
cleanUpPubSub(omemoManager);
|
||||||
cleanUpRoster(omemoManager);
|
cleanUpRoster(omemoManager);
|
||||||
}
|
}
|
||||||
|
@ -115,7 +114,7 @@ final class OmemoIntegrationTestHelper {
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
* @throws SmackException.NoResponseException
|
* @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,
|
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
|
||||||
SmackException.NotConnectedException, InterruptedException,
|
SmackException.NotConnectedException, InterruptedException,
|
||||||
SmackException.NoResponseException {
|
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
|
//Fetch deviceList
|
||||||
alice.requestDeviceListUpdateFor(bob.getOwnJid());
|
alice.requestDeviceListUpdateFor(bob.getOwnJid());
|
||||||
LOGGER.log(Level.INFO, "Current deviceList state: " + alice.getOwnDevice() + " knows " + bob.getOwnDevice() + ": "
|
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();
|
omemoManager.initialize();
|
||||||
OmemoBundleElement bundle = OmemoService.fetchBundle(omemoManager, omemoManager.getOwnDevice());
|
OmemoBundleElement bundle = OmemoService.fetchBundle(omemoManager, omemoManager.getOwnDevice());
|
||||||
assertNotNull("Bundle must not be null.", bundle);
|
assertNotNull("Bundle must not be null.", bundle);
|
||||||
|
|
|
@ -22,6 +22,7 @@ import static junit.framework.TestCase.assertNotNull;
|
||||||
import static junit.framework.TestCase.assertNotSame;
|
import static junit.framework.TestCase.assertNotSame;
|
||||||
import static junit.framework.TestCase.assertNull;
|
import static junit.framework.TestCase.assertNull;
|
||||||
import static junit.framework.TestCase.assertTrue;
|
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.deletePath;
|
||||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
|
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
|
||||||
|
|
||||||
|
@ -154,6 +155,8 @@ public class OmemoStoreTest extends AbstractOmemoIntegrationTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void after() {
|
public void after() {
|
||||||
|
cleanServerSideTraces(alice);
|
||||||
|
cleanServerSideTraces(bob);
|
||||||
alice.shutdown();
|
alice.shutdown();
|
||||||
bob.shutdown();
|
bob.shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class SmackOmemoSignalIntegrationTestFramework {
|
||||||
SignalOmemoService.acknowledgeLicense();
|
SignalOmemoService.acknowledgeLicense();
|
||||||
SignalOmemoService.setup();
|
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);
|
SmackIntegrationTestFramework.main(smackOmemoPackages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,11 +41,12 @@ import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.util.Async;
|
import org.jivesoftware.smack.util.Async;
|
||||||
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
import org.jivesoftware.smackx.carbons.CarbonManager;
|
import org.jivesoftware.smackx.carbons.CarbonManager;
|
||||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
|
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
|
||||||
import org.jivesoftware.smackx.hints.element.StoreHint;
|
import org.jivesoftware.smackx.hints.element.StoreHint;
|
||||||
|
import org.jivesoftware.smackx.jet.JingleEnvelopeManager;
|
||||||
import org.jivesoftware.smackx.mam.MamManager;
|
import org.jivesoftware.smackx.mam.MamManager;
|
||||||
import org.jivesoftware.smackx.muc.MultiUserChat;
|
import org.jivesoftware.smackx.muc.MultiUserChat;
|
||||||
import org.jivesoftware.smackx.muc.MultiUserChatManager;
|
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.internal.OmemoMessageInformation;
|
||||||
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
|
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
|
||||||
import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener;
|
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.PEPListener;
|
||||||
import org.jivesoftware.smackx.pep.PEPManager;
|
import org.jivesoftware.smackx.pep.PEPManager;
|
||||||
import org.jivesoftware.smackx.pubsub.EventElement;
|
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||||
|
@ -78,6 +80,7 @@ import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
import org.jxmpp.jid.EntityFullJid;
|
import org.jxmpp.jid.EntityFullJid;
|
||||||
|
import org.jxmpp.jid.FullJid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.jxmpp.stringprep.XmppStringprepException;
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
|
@ -88,7 +91,7 @@ import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
* @author Paul Schaub
|
* @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 Logger LOGGER = Logger.getLogger(OmemoManager.class.getName());
|
||||||
|
|
||||||
private static final WeakHashMap<XMPPConnection, WeakHashMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>();
|
private static final WeakHashMap<XMPPConnection, WeakHashMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>();
|
||||||
|
@ -133,6 +136,7 @@ public final class OmemoManager extends Manager {
|
||||||
});
|
});
|
||||||
|
|
||||||
service = OmemoService.getInstance();
|
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
|
* @return the connection of this manager
|
||||||
*/
|
*/
|
||||||
XMPPConnection getConnection() {
|
@Override
|
||||||
|
public XMPPConnection getConnection() {
|
||||||
return connection();
|
return connection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getNamespace() {
|
||||||
|
return OMEMO_NAMESPACE_V_AXOLOTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getJingleEnvelopeNamespace() {
|
||||||
|
return getNamespace();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the OMEMO service object.
|
* Return the OMEMO service object.
|
||||||
*
|
*
|
||||||
|
@ -840,4 +854,44 @@ public final class OmemoManager extends Manager {
|
||||||
}
|
}
|
||||||
return omemoCarbonCopyListener;
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,6 @@ import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.XMPPError;
|
import org.jivesoftware.smack.packet.XMPPError;
|
||||||
import org.jivesoftware.smack.util.Async;
|
import org.jivesoftware.smack.util.Async;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener;
|
import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener;
|
||||||
import org.jivesoftware.smackx.carbons.CarbonManager;
|
import org.jivesoftware.smackx.carbons.CarbonManager;
|
||||||
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
|
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
|
||||||
|
|
Loading…
Reference in a new issue