Merge branch '4.4'

This commit is contained in:
Florian Schmaus 2021-03-19 09:47:07 +01:00
commit 4fefa92e40
22 changed files with 288 additions and 132 deletions

View File

@ -148,7 +148,7 @@ allprojects {
jxmppVersion = '[1.0.0, 1.0.999]'
miniDnsVersion = '[1.0.0, 1.0.999]'
smackMinAndroidSdk = 19
junitVersion = '5.6.2'
junitVersion = '5.7.1'
commonsIoVersion = '2.6'
bouncyCastleVersion = '1.68'
guavaVersion = '30.1-jre'

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2015 Florian Schmaus
* Copyright 2015-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -44,11 +44,20 @@ public abstract class AbstractListFilter implements StanzaFilter {
* @param filters the filters to add.
*/
protected AbstractListFilter(StanzaFilter... filters) {
this(new ArrayList<StanzaFilter>(Arrays.asList(filters)));
}
/**
* Creates an filter using the specified filters.
*
* @param filters the filters to add.
*/
protected AbstractListFilter(List<StanzaFilter> filters) {
Objects.requireNonNull(filters, "Parameter must not be null.");
for (StanzaFilter filter : filters) {
Objects.requireNonNull(filter, "Parameter must not be null.");
}
this.filters = new ArrayList<StanzaFilter>(Arrays.asList(filters));
this.filters = filters;
}
/**

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software.
* Copyright 2003-2007 Jive Software, 2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +17,8 @@
package org.jivesoftware.smack.filter;
import java.util.List;
import org.jivesoftware.smack.packet.Stanza;
/**
@ -44,6 +46,15 @@ public class AndFilter extends AbstractListFilter implements StanzaFilter {
super(filters);
}
/**
* Creates an AND filter using the specified filters.
*
* @param filters the filters to add.
*/
public AndFilter(List<StanzaFilter> filters) {
super(filters);
}
@Override
public boolean accept(Stanza packet) {
for (StanzaFilter filter : filters) {

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2016-2019 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2016-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -47,4 +47,8 @@ public class RandomUtil {
public static int nextSecureRandomInt() {
return SECURE_RANDOM.get().nextInt();
}
public static void fillWithSecureRandom(byte[] bytes) {
SECURE_RANDOM.get().nextBytes(bytes);
}
}

View File

@ -183,6 +183,10 @@ public abstract class FileTransfer {
protected void setException(Exception exception) {
this.exception = exception;
Status currentStatus = getStatus();
if (currentStatus != Status.error) {
updateStatus(currentStatus, Status.error);
}
}
protected void setStatus(Status status) {

View File

@ -40,6 +40,7 @@ import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.filetransfer.FileTransferException.NoAcceptableTransferMechanisms;
import org.jivesoftware.smackx.filetransfer.FileTransferException.NoStreamMethodsOfferedException;
import org.jivesoftware.smackx.formtypes.FormFieldRegistry;
import org.jivesoftware.smackx.si.packet.StreamInitiation;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.ListSingleFormField;
@ -66,6 +67,9 @@ public final class FileTransferNegotiator extends Manager {
private static final String STREAM_INIT_PREFIX = "jsi_";
static final String STREAM_DATA_FIELD_NAME = "stream-method";
static {
FormFieldRegistry.addLookasideFieldRegistryEntry(STREAM_DATA_FIELD_NAME, FormField.Type.list_single);
}
private static final Random randomGenerator = new Random();

View File

@ -324,6 +324,10 @@ public class OutgoingFileTransfer extends FileTransfer {
transferThread.start();
}
public void setCallback(NegotiationProgress negotiationProcess) {
this.callback = negotiationProcess;
}
private void handleXMPPException(XMPPErrorException e) {
StanzaError error = e.getStanzaError();
if (error != null) {

View File

@ -36,6 +36,8 @@ public class FormFieldRegistry {
private static final Map<String, FormField.Type> CLARK_NOTATION_FIELD_REGISTRY = new ConcurrentHashMap<>();
private static final Map<String, FormField.Type> LOOKASIDE_FIELD_REGISTRY = new ConcurrentHashMap<>();
@SuppressWarnings("ReferenceEquality")
public static void register(DataForm dataForm) {
// TODO: Also allow forms of type 'result'?
@ -98,11 +100,11 @@ public class FormFieldRegistry {
public static FormField.Type lookup(String formType, String fieldName) {
if (formType == null) {
if (!XmlUtil.isClarkNotation(fieldName)) {
return null;
if (XmlUtil.isClarkNotation(fieldName)) {
return CLARK_NOTATION_FIELD_REGISTRY.get(fieldName);
}
return CLARK_NOTATION_FIELD_REGISTRY.get(fieldName);
return LOOKASIDE_FIELD_REGISTRY.get(fieldName);
}
synchronized (REGISTRY) {
@ -122,4 +124,7 @@ public class FormFieldRegistry {
return lookup(null, fieldName);
}
public static void addLookasideFieldRegistryEntry(String fieldName, FormField.Type formFieldType) {
LOOKASIDE_FIELD_REGISTRY.put(fieldName, formFieldType);
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software. 2020 Florian Schmaus
* Copyright 2003-2007 Jive Software. 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -149,6 +149,8 @@ public class MultiUserChat {
private EntityFullJid myRoomJid;
private StanzaCollector messageCollector;
private DiscoverInfo mucServiceDiscoInfo;
/**
* Used to signal that the reflected self-presence was received <b>and</b> processed by us.
*/
@ -342,7 +344,8 @@ public class MultiUserChat {
private Presence enter(MucEnterConfiguration conf) throws NotConnectedException, NoResponseException,
XMPPErrorException, InterruptedException, NotAMucServiceException {
final DomainBareJid mucService = room.asDomainBareJid();
if (!multiUserChatManager.providesMucService(mucService)) {
mucServiceDiscoInfo = multiUserChatManager.getMucServiceDiscoInfo(mucService);
if (mucServiceDiscoInfo == null) {
throw new NotAMucServiceException(this);
}
// We enter a room by sending a presence packet where the "to"
@ -757,6 +760,10 @@ public class MultiUserChat {
throw new MucNotJoinedException(this);
}
// TODO: Consider adding a origin-id to the presence, once it is moved form smack-experimental into
// smack-extensions, in case the MUC service does not support stable IDs, and modify
// reflectedLeavePresenceFilters accordingly.
// We leave a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence leavePresence = connection.getStanzaFactory().buildPresenceStanza()
@ -764,14 +771,19 @@ public class MultiUserChat {
.to(myRoomJid)
.build();
StanzaFilter reflectedLeavePresenceFilter = new AndFilter(
StanzaTypeFilter.PRESENCE,
new StanzaIdFilter(leavePresence),
new OrFilter(
new AndFilter(FromMatchesFilter.createFull(myRoomJid), PresenceTypeFilter.UNAVAILABLE, MUCUserStatusCodeFilter.STATUS_110_PRESENCE_TO_SELF),
new AndFilter(fromRoomFilter, PresenceTypeFilter.ERROR)
)
);
List<StanzaFilter> reflectedLeavePresenceFilters = new ArrayList<>(3);
reflectedLeavePresenceFilters.add(StanzaTypeFilter.PRESENCE);
reflectedLeavePresenceFilters.add(new OrFilter(
new AndFilter(FromMatchesFilter.createFull(myRoomJid), PresenceTypeFilter.UNAVAILABLE,
MUCUserStatusCodeFilter.STATUS_110_PRESENCE_TO_SELF),
new AndFilter(fromRoomFilter, PresenceTypeFilter.ERROR)));
boolean supportsStableId = mucServiceDiscoInfo.containsFeature(MultiUserChatConstants.STABLE_ID_FEATURE);
if (supportsStableId) {
reflectedLeavePresenceFilters.add(new StanzaIdFilter(leavePresence));
}
StanzaFilter reflectedLeavePresenceFilter = new AndFilter(reflectedLeavePresenceFilters);
// Reset occupant information first so that we are assume that we left the room even if sendStanza() would
// throw.

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2020 Florian Schmaus
* Copyright 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,4 +20,6 @@ public class MultiUserChatConstants {
public static final String NAMESPACE = "http://jabber.org/protocol/muc";
public static final String STABLE_ID_FEATURE = NAMESPACE + "#stable_id";
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014-2020 Florian Schmaus
* Copyright © 2014-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -137,7 +137,7 @@ public final class MultiUserChatManager extends Manager {
private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
new NotFilter(MessageTypeFilter.ERROR));
private static final ExpirationCache<DomainBareJid, Void> KNOWN_MUC_SERVICES = new ExpirationCache<>(
private static final ExpirationCache<DomainBareJid, DiscoverInfo> KNOWN_MUC_SERVICES = new ExpirationCache<>(
100, 1000 * 60 * 60 * 24);
private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
@ -396,16 +396,23 @@ public final class MultiUserChatManager extends Manager {
*/
public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
boolean contains = KNOWN_MUC_SERVICES.containsKey(domainBareJid);
if (!contains) {
if (serviceDiscoveryManager.supportsFeature(domainBareJid,
MUCInitialPresence.NAMESPACE)) {
KNOWN_MUC_SERVICES.put(domainBareJid, null);
return true;
}
return getMucServiceDiscoInfo(domainBareJid) != null;
}
DiscoverInfo getMucServiceDiscoInfo(DomainBareJid mucServiceAddress)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
DiscoverInfo discoInfo = KNOWN_MUC_SERVICES.get(mucServiceAddress);
if (discoInfo != null) {
return discoInfo;
}
return contains;
discoInfo = serviceDiscoveryManager.discoverInfo(mucServiceAddress);
if (!discoInfo.containsFeature(MUCInitialPresence.NAMESPACE)) {
return null;
}
KNOWN_MUC_SERVICES.put(mucServiceAddress, discoInfo);
return discoInfo;
}
/**

View File

@ -16,6 +16,7 @@
*/
package org.igniterealtime.smack;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -90,7 +91,13 @@ public class XmppConnectionStressTest {
MessageBuilder messageBuilder = fromConnection.getStanzaFactory().buildMessageStanza();
messageBuilder.to(toConnection.getUser());
int payloadChunkCount = random.nextInt(configuration.maxPayloadChunks) + 1;
final int payloadChunkCount;
if (configuration.maxPayloadChunks == 0) {
payloadChunkCount = 0;
} else {
payloadChunkCount = random.nextInt(configuration.maxPayloadChunks) + 1;
}
for (int c = 0; c < payloadChunkCount; c++) {
int payloadChunkSize = random.nextInt(configuration.maxPayloadChunkSize) + 1;
String payloadCunk = StringUtils.randomString(payloadChunkSize, random);
@ -128,6 +135,13 @@ public class XmppConnectionStressTest {
Map<XMPPConnection, Map<EntityFullJid, boolean[]>> receiveMarkers = new ConcurrentHashMap<>(connections.size());
for (XMPPConnection connection : connections) {
final Map<EntityFullJid, boolean[]> myReceiveMarkers = new HashMap<>(connections.size());
receiveMarkers.put(connection, myReceiveMarkers);
for (XMPPConnection otherConnection : connections) {
boolean[] fromMarkers = new boolean[configuration.messagesPerConnection];
myReceiveMarkers.put(otherConnection.getUser(), fromMarkers);
}
connection.addSyncStanzaListener(new StanzaListener() {
@Override
public void processStanza(Stanza stanza) {
@ -139,17 +153,7 @@ public class XmppConnectionStressTest {
Integer messageNumber = (Integer) extension.getProperty(MESSAGE_NUMBER_PROPERTY);
Map<EntityFullJid, boolean[]> myReceiveMarkers = receiveMarkers.get(connection);
if (myReceiveMarkers == null) {
myReceiveMarkers = new HashMap<>(connections.size());
receiveMarkers.put(connection, myReceiveMarkers);
}
boolean[] fromMarkers = myReceiveMarkers.get(from);
if (fromMarkers == null) {
fromMarkers = new boolean[configuration.messagesPerConnection];
myReceiveMarkers.put(from, fromMarkers);
}
// Sanity check: All markers before must be true, all markers including the messageNumber marker must be false.
for (int i = 0; i < fromMarkers.length; i++) {
@ -173,7 +177,9 @@ public class XmppConnectionStressTest {
exceptionMessage.append(i);
exceptionMessage.append("\nMessage with id ").append(stanza.getStanzaId())
.append(" from ").append(from)
.append(" to ").append(stanza.getTo());
.append(" to ").append(stanza.getTo())
.append('\n');
exceptionMessage.append("From Markers: ").append(Arrays.toString(fromMarkers)).append('\n');
Exception exception = new Exception(exceptionMessage.toString());
receiveExceptions.put(connection, exception);
@ -188,15 +194,14 @@ public class XmppConnectionStressTest {
fromMarkers[messageNumber] = true;
if (myReceiveMarkers.size() != connections.size()) {
return;
}
for (boolean[] markers : myReceiveMarkers.values()) {
if (BooleansUtils.contains(markers, false)) {
// There is at least one message we did not receive yet, therefore do not signal the
// receivedSemaphore.
return;
}
}
// All markers set to true, this means we received all messages.
receivedSemaphore.release();
}
@ -276,6 +281,7 @@ public class XmppConnectionStressTest {
XMPPConnection connection = connections.get(i);
EntityFullJid connectionAddress = connection.getUser();
connectionIds.put(connectionAddress, i);
sb.append(i).append(": ").append(connection).append('\n');
}
for (Map.Entry<XMPPConnection, Map<EntityFullJid, boolean[]>> entry : receiveMarkers.entrySet()) {
@ -295,7 +301,7 @@ public class XmppConnectionStressTest {
sb.append(markerToConnectionId)
.append(" is missing ").append(numberOfFalseMarkers)
.append(" messages from ").append(markerFromConnectionId)
.append(" :");
.append(": ");
for (int i = 0; i < marker.length; i++) {
if (marker[i]) {
continue;

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2020 Florian Schmaus
* Copyright 2015-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import java.util.Arrays;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.filetransfer.FileTransfer.Status;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
@ -98,9 +99,12 @@ public class FileTransferIntegrationTest extends AbstractSmackIntegrationTest {
oft.sendStream(new ByteArrayInputStream(dataToSend), "hello.txt", dataToSend.length, "A greeting");
int duration = 0;
while (!oft.isDone()) {
switch (oft.getStatus()) {
Status status = oft.getStatus();
switch (status) {
case error:
throw new Exception("FileTransfer error: " + oft.getError());
FileTransfer.Error error = oft.getError();
Exception exception = oft.getException();
throw new Exception("FileTransfer error: " + error, exception);
default:
LOGGER.info("FileTransfer status: " + oft.getStatus() + ". Progress: " + oft.getProgress());
break;

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Paul Schaub
* Copyright 2017 Paul Schaub, 2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,7 +17,9 @@
package org.jivesoftware.smackx.omemo;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
@ -25,6 +27,7 @@ import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
@ -172,11 +175,10 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
byte[] encryptedBody = payloadAndAuthTag(element, cipherAndAuthTag.getAuthTag());
try {
String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StandardCharsets.UTF_8);
return plaintext;
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new CryptoFailedException("decryptMessageElement could not decipher message body: "
+ e.getMessage());
return cipherAndAuthTag.decrypt(encryptedBody);
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException
| NoSuchPaddingException | InvalidAlgorithmParameterException e) {
throw new CryptoFailedException("decryptMessageElement could not decipher message body", e);
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Paul Schaub
* Copyright 2017 Paul Schaub, 2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,6 @@
*/
package org.jivesoftware.smackx.omemo.exceptions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Exception gets thrown when some cryptographic function failed.
*
@ -27,20 +23,18 @@ import java.util.List;
*/
public class CryptoFailedException extends Exception {
private static final long serialVersionUID = 3466888654338119924L;
private static final long serialVersionUID = 1;
private final ArrayList<Exception> exceptions = new ArrayList<>();
public CryptoFailedException(String message, Exception wrappedException) {
super(message, wrappedException);
}
public CryptoFailedException(String message) {
super(message);
this(message, null);
}
public CryptoFailedException(Exception e) {
super(e);
exceptions.add(e);
this("Crypto failed " + e.getMessage(), e);
}
public List<Exception> getExceptions() {
return Collections.unmodifiableList(exceptions);
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,18 +16,14 @@
*/
package org.jivesoftware.smackx.omemo.internal;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
/**
* Encapsulate Cipher and AuthTag.
@ -45,21 +41,10 @@ public class CipherAndAuthTag {
this.wasPreKey = wasPreKey;
}
public Cipher getCipher() throws CryptoFailedException {
Cipher cipher;
try {
cipher = Cipher.getInstance(CIPHERMODE);
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
} catch (NoSuchAlgorithmException | java.security.InvalidKeyException |
InvalidAlgorithmParameterException |
NoSuchPaddingException e) {
throw new CryptoFailedException(e);
}
return cipher;
public String decrypt(byte[] ciphertext) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
byte[] plaintext = OmemoAesCipher.decryptAesGcmNoPadding(ciphertext, key, iv);
return new String(plaintext, StandardCharsets.UTF_8);
}
public byte[] getAuthTag() {

View File

@ -0,0 +1,99 @@
/**
*
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
*
* 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.omemo.internal;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.jivesoftware.smack.util.RandomUtil;
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
public class OmemoAesCipher {
static {
byte[] iv = OmemoMessageBuilder.generateIv();
byte[] key = new byte[16];
RandomUtil.fillWithSecureRandom(key);
try {
encryptAesGcmNoPadding("This is just a test", key, iv);
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
String message = "Unable to perform " + OmemoConstants.Crypto.CIPHERMODE
+ " operation requires by OMEMO. Ensure that a suitable crypto provider for is available."
+ " For example Bouncycastle on Android (BouncyCastleProvider)";
throw new AssertionError(message);
}
}
private enum CipherOpmode {
encrypt(Cipher.ENCRYPT_MODE),
decrypt(Cipher.DECRYPT_MODE),
;
public final int opmodeInt;
CipherOpmode(int opmodeInt) {
this.opmodeInt = opmodeInt;
}
}
private static byte[] performCipherOperation(CipherOpmode opmode, byte[] input, byte[] key,
byte[] initializationVector)
throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
SecretKey secretKey = new SecretKeySpec(key, OmemoConstants.Crypto.KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
Cipher cipher = Cipher.getInstance(OmemoConstants.Crypto.CIPHERMODE);
cipher.init(opmode.opmodeInt, secretKey, ivSpec);
byte[] ciphertext = cipher.doFinal(input);
return ciphertext;
}
public static byte[] decryptAesGcmNoPadding(byte[] ciphertext, byte[] key, byte[] initializationVector)
throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
return performCipherOperation(CipherOpmode.decrypt, ciphertext, key, initializationVector);
}
public static byte[] encryptAesGcmNoPadding(byte[] plaintext, byte[] key, byte[] initializationVector)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return performCipherOperation(CipherOpmode.encrypt, plaintext, key, initializationVector);
}
public static byte[] encryptAesGcmNoPadding(String plaintext, byte[] key, byte[] initializationVector)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
return encryptAesGcmNoPadding(plaintextBytes, key, initializationVector);
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,26 +16,20 @@
*/
package org.jivesoftware.smackx.omemo.util;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.jivesoftware.smack.util.RandomUtil;
import org.jivesoftware.smackx.omemo.OmemoRatchet;
import org.jivesoftware.smackx.omemo.OmemoService;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
@ -47,6 +41,7 @@ import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
import org.jivesoftware.smackx.omemo.internal.OmemoAesCipher;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
@ -159,16 +154,7 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
}
// Encrypt message body
SecretKey secretKey = new SecretKeySpec(messageKey, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
Cipher cipher = Cipher.getInstance(CIPHERMODE);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] body;
byte[] ciphertext;
body = message.getBytes(StandardCharsets.UTF_8);
ciphertext = cipher.doFinal(body);
byte[] ciphertext = OmemoAesCipher.encryptAesGcmNoPadding(message, messageKey, initializationVector);
byte[] clearKeyWithAuthTag = new byte[messageKey.length + 16];
byte[] cipherTextWithoutAuthTag = new byte[ciphertext.length - 16];
@ -278,9 +264,8 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @return initialization vector
*/
public static byte[] generateIv() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[12];
random.nextBytes(iv);
RandomUtil.fillWithSecureRandom(iv);
return iv;
}
}

View File

@ -22,7 +22,6 @@ import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import java.security.NoSuchAlgorithmException;
@ -61,7 +60,6 @@ public class WrapperObjectsTest extends SmackTestSuite {
CipherAndAuthTag cat = new CipherAndAuthTag(key, iv, authTag, true);
assertNotNull(cat.getCipher());
assertArrayEquals(key, cat.getKey());
assertArrayEquals(iv, cat.getIv());
assertArrayEquals(authTag, cat.getAuthTag());

View File

@ -1143,16 +1143,27 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
}
catch (Exception e) {
// The exception can be ignored if the the connection is 'done'
// or if the it was caused because the socket got closed.
if (!done) {
// Set running to false since this thread will exit here and notifyConnectionError() will wait until
// the reader and writer thread's 'running' value is false.
running = false;
// Close the connection and notify connection listeners of the
// error.
notifyConnectionError(e);
// Set running to false since this thread will exit here and notifyConnectionError() will wait until
// the reader and writer thread's 'running' value is false. Hence we need to set it to false before calling
// notifyConnetctionError() below, even though run() also sets it to false. Therefore, do not remove this.
running = false;
String ignoreReasonThread = null;
boolean writerThreadWasShutDown = packetWriter.queue.isShutdown();
if (writerThreadWasShutDown) {
ignoreReasonThread = "writer";
} else if (done) {
ignoreReasonThread = "reader";
}
if (ignoreReasonThread != null) {
LOGGER.log(Level.FINER, "Ignoring " + e + " as " + ignoreReasonThread + " was already shut down");
return;
}
// Close the connection and notify connection listeners of the error.
notifyConnectionError(e);
}
}
}

View File

@ -73,6 +73,12 @@ public final class StaxXmlPullParser implements XmlPullParser {
return namespaceContext.getNamespaceURI(prefix);
}
@Override
public String getNamespace() {
String prefix = getPrefix();
return getNamespace(prefix);
}
@Override
public int getDepth() {
return depth;
@ -106,13 +112,6 @@ public final class StaxXmlPullParser implements XmlPullParser {
return xmlStreamReader.getText();
}
@Override
public String getNamespace() {
NamespaceContext namespaceContext = xmlStreamReader.getNamespaceContext();
String prefix = getPrefix();
return namespaceContext.getNamespaceURI(prefix);
}
@Override
public String getName() {
QName qname = getQName();

View File

@ -1,6 +1,6 @@
/**
*
* Copyright 2019 Florian Schmaus.
* Copyright 2019-2021 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,7 +28,14 @@ public class SmackXmlParser {
xmlPullParserFactoryServiceLoader = ServiceLoader.load(XmlPullParserFactory.class);
}
private static XmlPullParserFactory xmlPullParserFactory;
public static XmlPullParserFactory getXmlPullParserFactory() {
final XmlPullParserFactory xmlPullParserFactory = SmackXmlParser.xmlPullParserFactory;
if (xmlPullParserFactory != null) {
return xmlPullParserFactory;
}
Iterator<XmlPullParserFactory> iterator = xmlPullParserFactoryServiceLoader.iterator();
if (!iterator.hasNext()) {
throw new IllegalStateException(
@ -37,6 +44,10 @@ public class SmackXmlParser {
return iterator.next();
}
public static void setXmlPullParserFactory(XmlPullParserFactory xmlPullParserFactory) {
SmackXmlParser.xmlPullParserFactory = xmlPullParserFactory;
}
/**
* Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
* FEATURE_PROCESS_NAMESPACES is enabled.