mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-24 07:12:05 +01:00
Merge branch '4.4'
This commit is contained in:
commit
4fefa92e40
22 changed files with 288 additions and 132 deletions
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
return contains;
|
||||
DiscoverInfo getMucServiceDiscoInfo(DomainBareJid mucServiceAddress)
|
||||
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
DiscoverInfo discoInfo = KNOWN_MUC_SERVICES.get(mucServiceAddress);
|
||||
if (discoInfo != null) {
|
||||
return discoInfo;
|
||||
}
|
||||
|
||||
discoInfo = serviceDiscoveryManager.discoverInfo(mucServiceAddress);
|
||||
if (!discoInfo.containsFeature(MUCInitialPresence.NAMESPACE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
KNOWN_MUC_SERVICES.put(mucServiceAddress, discoInfo);
|
||||
return discoInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
// 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;
|
||||
// Close the connection and notify connection listeners of the
|
||||
// error.
|
||||
notifyConnectionError(e);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue