diff --git a/build.gradle b/build.gradle index de0d5323f..2d4f95f23 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/AbstractListFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/AbstractListFilter.java index d06ddad47..0d55777ce 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/filter/AbstractListFilter.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/AbstractListFilter.java @@ -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(Arrays.asList(filters))); + } + + /** + * Creates an filter using the specified filters. + * + * @param filters the filters to add. + */ + protected AbstractListFilter(List 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(Arrays.asList(filters)); + this.filters = filters; } /** diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/AndFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/AndFilter.java index cbdccd6e6..686ce2491 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/filter/AndFilter.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/AndFilter.java @@ -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 filters) { + super(filters); + } + @Override public boolean accept(Stanza packet) { for (StanzaFilter filter : filters) { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/RandomUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/RandomUtil.java index 857557869..88c71e7aa 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/RandomUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/RandomUtil.java @@ -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); + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransfer.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransfer.java index cac3147cf..8c5dc63d3 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransfer.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransfer.java @@ -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) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java index 885448fda..ea2747b1d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java @@ -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(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java index 3e33208e6..6553f1cdf 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java @@ -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) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java index 97156974f..9eab79b73 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java @@ -36,6 +36,8 @@ public class FormFieldRegistry { private static final Map CLARK_NOTATION_FIELD_REGISTRY = new ConcurrentHashMap<>(); + private static final Map 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); + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index 36aac4bd9..a4d347fb8 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -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 and 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 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. diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java index ffc00a5a9..35167c081 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java @@ -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"; + } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java index 7d92e9817..2cf5778ff 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java @@ -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 KNOWN_MUC_SERVICES = new ExpirationCache<>( + private static final ExpirationCache KNOWN_MUC_SERVICES = new ExpirationCache<>( 100, 1000 * 60 * 60 * 24); private final Set invitationsListeners = new CopyOnWriteArraySet(); @@ -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; } /** diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/XmppConnectionStressTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/XmppConnectionStressTest.java index 712d75783..f9d988308 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/XmppConnectionStressTest.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/XmppConnectionStressTest.java @@ -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> receiveMarkers = new ConcurrentHashMap<>(connections.size()); for (XMPPConnection connection : connections) { + final Map 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 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> 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; diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java index 5d536aedb..ac79e46ec 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java @@ -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; diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoRatchet.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoRatchet.java index 838784806..9c0ff2d30 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoRatchet.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoRatchet.java @@ -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 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 getExceptions() { - return Collections.unmodifiableList(exceptions); - } } diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/CipherAndAuthTag.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/CipherAndAuthTag.java index 15f4fa27f..3953e6642 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/CipherAndAuthTag.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/CipherAndAuthTag.java @@ -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() { diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/OmemoAesCipher.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/OmemoAesCipher.java new file mode 100644 index 000000000..aee330764 --- /dev/null +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/internal/OmemoAesCipher.java @@ -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); + } +} diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/util/OmemoMessageBuilder.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/util/OmemoMessageBuilder.java index 6e49db945..0810fe1d1 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/util/OmemoMessageBuilder.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/util/OmemoMessageBuilder.java @@ -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 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.