diff options
Diffstat (limited to '')
10 files changed, 306 insertions, 327 deletions
diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java index de423634a..daf25028a 100644 --- a/src/main/java/de/pixart/messenger/Config.java +++ b/src/main/java/de/pixart/messenger/Config.java @@ -110,6 +110,7 @@ public final class Config { public static final boolean REMOVE_BROKEN_DEVICES = false; public static final boolean OMEMO_PADDING = false; public static final boolean PUT_AUTH_TAG_INTO_KEY = true; + public static final boolean TWELVE_BYTE_IV = true; public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096; public static final int MAX_STORAGE_MESSAGE_CHARS = 2 * 1024 * 1024; //2MB @@ -140,7 +141,7 @@ public final class Config { public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 30; public static final int MAM_MAX_MESSAGES = 750; - public static final ChatState DEFAULT_CHATSTATE = ChatState.ACTIVE; + public static final ChatState DEFAULT_CHAT_STATE = ChatState.ACTIVE; public static final int TYPING_TIMEOUT = 5; public static final int EXPIRY_INTERVAL = 30 * 60 * 1000; // 30 minutes diff --git a/src/main/java/de/pixart/messenger/crypto/OtrService.java b/src/main/java/de/pixart/messenger/crypto/OtrService.java index cf41ec26f..b6e6507f5 100644 --- a/src/main/java/de/pixart/messenger/crypto/OtrService.java +++ b/src/main/java/de/pixart/messenger/crypto/OtrService.java @@ -188,7 +188,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { try { Jid jid = OtrJidHelper.fromSessionID(session); Conversation conversation = mXmppConnectionService.find(account, jid); - if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) { if (mXmppConnectionService.sendChatStates()) { packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); } diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java index 186629853..3bc0c2901 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java @@ -80,16 +80,37 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { private final Map<Jid, Boolean> fetchDeviceListStatus = new HashMap<>(); private final HashMap<Jid, List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>(); private final SerialSingleThreadExecutor executor; + private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>(); + private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>(); + private final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>(); private int numPublishTriesOnEmptyPep = 0; private boolean pepBroken = false; - private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>(); private int lastDeviceListNotificationHash = 0; - private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>(); private Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment private Set<SignalProtocolAddress> postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup - private AtomicBoolean changeAccessMode = new AtomicBoolean(false); + public AxolotlService(Account account, XmppConnectionService connectionService) { + if (account == null || connectionService == null) { + throw new IllegalArgumentException("account and service cannot be null"); + } + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + this.mXmppConnectionService = connectionService; + this.account = account; + this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); + this.deviceIds = new HashMap<>(); + this.messageCache = new HashMap<>(); + this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account); + this.fetchStatusMap = new FetchStatusMap(); + this.executor = new SerialSingleThreadExecutor("Axolotl"); + } + + public static String getLogprefix(Account account) { + return LOGPREFIX + " (" + account.getJid().asBareJid().toString() + "): "; + } + @Override public void onAdvancedStreamFeaturesAvailable(Account account) { if (Config.supportOmemo() @@ -146,172 +167,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return false; } - private static class AxolotlAddressMap<T> { - protected Map<String, Map<Integer, T>> map; - protected final Object MAP_LOCK = new Object(); - - public AxolotlAddressMap() { - this.map = new HashMap<>(); - } - - public void put(SignalProtocolAddress address, T value) { - synchronized (MAP_LOCK) { - Map<Integer, T> devices = map.get(address.getName()); - if (devices == null) { - devices = new HashMap<>(); - map.put(address.getName(), devices); - } - devices.put(address.getDeviceId(), value); - } - } - - public T get(SignalProtocolAddress address) { - synchronized (MAP_LOCK) { - Map<Integer, T> devices = map.get(address.getName()); - if (devices == null) { - return null; - } - return devices.get(address.getDeviceId()); - } - } - - public Map<Integer, T> getAll(String name) { - synchronized (MAP_LOCK) { - Map<Integer, T> devices = map.get(name); - if (devices == null) { - return new HashMap<>(); - } - return devices; - } - } - - public boolean hasAny(SignalProtocolAddress address) { - synchronized (MAP_LOCK) { - Map<Integer, T> devices = map.get(address.getName()); - return devices != null && !devices.isEmpty(); - } - } - - public void clear() { - map.clear(); - } - - } - - private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> { - private final XmppConnectionService xmppConnectionService; - private final Account account; - - public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) { - super(); - this.xmppConnectionService = service; - this.account = account; - this.fillMap(store); - } - - public Set<Jid> findCounterpartsForSourceId(Integer sid) { - Set<Jid> candidates = new HashSet<>(); - synchronized (MAP_LOCK) { - for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) { - String key = entry.getKey(); - if (entry.getValue().containsKey(sid)) { - candidates.add(Jid.of(key)); - } - } - } - return candidates; - } - - private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) { - for (Integer deviceId : deviceIds) { - SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId); - IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey(); - if (Config.X509_VERIFICATION) { - X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize())); - if (certificate != null) { - Bundle information = CryptoHelper.extractCertificateInformation(certificate); - try { - final String cn = information.getString("subject_cn"); - final Jid jid = Jid.of(bareJid); - Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn); - account.getRoster().getContact(jid).setCommonName(cn); - } catch (final IllegalArgumentException ignored) { - //ignored - } - } - } - this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey)); - } - } - - private void fillMap(SQLiteAxolotlStore store) { - List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString()); - putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store); - for (String address : store.getKnownAddresses()) { - deviceIds = store.getSubDeviceSessions(address); - putDevicesForJid(address, deviceIds, store); - } - } - - @Override - public void put(SignalProtocolAddress address, XmppAxolotlSession value) { - super.put(address, value); - value.setNotFresh(); - } - - public void put(XmppAxolotlSession session) { - this.put(session.getRemoteAddress(), session); - } - } - - public enum FetchStatus { - PENDING, - SUCCESS, - SUCCESS_VERIFIED, - TIMEOUT, - SUCCESS_TRUSTED, - ERROR - } - - private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> { - - public void clearErrorFor(Jid jid) { - synchronized (MAP_LOCK) { - Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString()); - if (devices == null) { - return; - } - for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) { - if (entry.getValue() == FetchStatus.ERROR) { - Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")"); - entry.setValue(FetchStatus.TIMEOUT); - } - } - } - } - } - - public static String getLogprefix(Account account) { - return LOGPREFIX + " (" + account.getJid().asBareJid().toString() + "): "; - } - - public AxolotlService(Account account, XmppConnectionService connectionService) { - if (account == null || connectionService == null) { - throw new IllegalArgumentException("account and service cannot be null"); - } - if (Security.getProvider("BC") == null) { - Security.addProvider(new BouncyCastleProvider()); - } - this.mXmppConnectionService = connectionService; - this.account = account; - this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); - this.deviceIds = new HashMap<>(); - this.messageCache = new HashMap<>(); - this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account); - this.fetchStatusMap = new FetchStatusMap(); - this.executor = new SerialSingleThreadExecutor("Axolotl"); - } - public String getOwnFingerprint() { return CryptoHelper.bytesToHex(axolotlStore.getIdentityKeyPair().getPublicKey().serialize()); } @@ -360,7 +215,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return s; } - public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) { SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid()); ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values()); @@ -925,8 +779,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } - private final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>(); - private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) { SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0); Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName()); @@ -964,14 +816,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty()); } - public interface OnDeviceIdsFetched { - void fetched(Jid jid, Set<Integer> deviceIds); - } - - public interface OnMultipleDeviceIdFetched { - void fetched(); - } - public void fetchDeviceIds(final Jid jid) { fetchDeviceIds(jid, null); } @@ -1048,12 +892,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } - interface OnSessionBuildFromPep { - void onSessionBuildSuccessful(); - - void onSessionBuildFailed(); - } - private void buildSessionFromPEP(final SignalProtocolAddress address) { buildSessionFromPEP(address, null); } @@ -1124,18 +962,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { callback.onSessionBuildSuccessful(); } } - } catch (UntrustedIdentityException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " - + e.getClass().getName() + ", " + e.getMessage()); - fetchStatusMap.put(address, FetchStatus.ERROR); - finishBuildingSessionsFromPEP(address); - if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) { - removeFromDeviceAnnouncement(address.getDeviceId()); - } - if (callback != null) { - callback.onSessionBuildFailed(); - } - } catch (InvalidKeyException e) { + } catch (UntrustedIdentityException | InvalidKeyException e) { Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " + e.getClass().getName() + ", " + e.getMessage()); fetchStatusMap.put(address, FetchStatus.ERROR); @@ -1412,7 +1239,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) { XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { - //Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); session = recreateUncachedSession(senderAddress); if (session == null) { session = new XmppAxolotlSession(account, axolotlStore, senderAddress); @@ -1421,7 +1247,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return session; } - public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException { + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException, OutdatedSenderException { XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; XmppAxolotlSession session = getReceivingSession(message); @@ -1440,6 +1266,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } catch (final BrokenSessionException e) { throw e; + } catch (final OutdatedSenderException e) { + Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage()); + throw e; } catch (CryptoFailedException e) { Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e); } @@ -1545,7 +1374,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } - public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) { final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; final XmppAxolotlSession session = getReceivingSession(message); @@ -1577,4 +1405,164 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } } + + public enum FetchStatus { + PENDING, + SUCCESS, + SUCCESS_VERIFIED, + TIMEOUT, + SUCCESS_TRUSTED, + ERROR + } + + public interface OnDeviceIdsFetched { + void fetched(Jid jid, Set<Integer> deviceIds); + } + + + public interface OnMultipleDeviceIdFetched { + void fetched(); + } + + interface OnSessionBuildFromPep { + void onSessionBuildSuccessful(); + + void onSessionBuildFailed(); + } + + private static class AxolotlAddressMap<T> { + protected final Object MAP_LOCK = new Object(); + protected Map<String, Map<Integer, T>> map; + + public AxolotlAddressMap() { + this.map = new HashMap<>(); + } + + public void put(SignalProtocolAddress address, T value) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + if (devices == null) { + devices = new HashMap<>(); + map.put(address.getName(), devices); + } + devices.put(address.getDeviceId(), value); + } + } + + public T get(SignalProtocolAddress address) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + if (devices == null) { + return null; + } + return devices.get(address.getDeviceId()); + } + } + + public Map<Integer, T> getAll(String name) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(name); + if (devices == null) { + return new HashMap<>(); + } + return devices; + } + } + + public boolean hasAny(SignalProtocolAddress address) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + return devices != null && !devices.isEmpty(); + } + } + + public void clear() { + map.clear(); + } + + } + + private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> { + private final XmppConnectionService xmppConnectionService; + private final Account account; + + public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) { + super(); + this.xmppConnectionService = service; + this.account = account; + this.fillMap(store); + } + + public Set<Jid> findCounterpartsForSourceId(Integer sid) { + Set<Jid> candidates = new HashSet<>(); + synchronized (MAP_LOCK) { + for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) { + String key = entry.getKey(); + if (entry.getValue().containsKey(sid)) { + candidates.add(Jid.of(key)); + } + } + } + return candidates; + } + + private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) { + for (Integer deviceId : deviceIds) { + SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId); + IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey(); + if (Config.X509_VERIFICATION) { + X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize())); + if (certificate != null) { + Bundle information = CryptoHelper.extractCertificateInformation(certificate); + try { + final String cn = information.getString("subject_cn"); + final Jid jid = Jid.of(bareJid); + Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn); + account.getRoster().getContact(jid).setCommonName(cn); + } catch (final IllegalArgumentException ignored) { + //ignored + } + } + } + this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey)); + } + } + + private void fillMap(SQLiteAxolotlStore store) { + List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString()); + putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store); + for (String address : store.getKnownAddresses()) { + deviceIds = store.getSubDeviceSessions(address); + putDevicesForJid(address, deviceIds, store); + } + } + + @Override + public void put(SignalProtocolAddress address, XmppAxolotlSession value) { + super.put(address, value); + value.setNotFresh(); + } + + public void put(XmppAxolotlSession session) { + this.put(session.getRemoteAddress(), session); + } + } + + private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> { + + public void clearErrorFor(Jid jid) { + synchronized (MAP_LOCK) { + Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString()); + if (devices == null) { + return; + } + for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) { + if (entry.getValue() == FetchStatus.ERROR) { + Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")"); + entry.setValue(FetchStatus.TIMEOUT); + } + } + } + } + } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/OutdatedSenderException.java b/src/main/java/de/pixart/messenger/crypto/axolotl/OutdatedSenderException.java new file mode 100644 index 000000000..905a11b84 --- /dev/null +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/OutdatedSenderException.java @@ -0,0 +1,8 @@ +package de.pixart.messenger.crypto.axolotl; + +public class OutdatedSenderException extends CryptoFailedException { + + public OutdatedSenderException(final String msg) { + super(msg); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java index cfa6a275e..6fbf8c3dc 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java @@ -37,69 +37,13 @@ public class XmppAxolotlMessage { private static final String KEYTYPE = "AES"; private static final String CIPHERMODE = "AES/GCM/NoPadding"; private static final String PROVIDER = "BC"; - + private final List<XmppAxolotlSession.AxolotlKey> keys; + private final Jid from; + private final int sourceDeviceId; private byte[] innerKey; private byte[] ciphertext = null; private byte[] authtagPlusInnerKey = null; private byte[] iv = null; - private final List<XmppAxolotlSession.AxolotlKey> keys; - private final Jid from; - private final int sourceDeviceId; - - public static class XmppAxolotlPlaintextMessage { - private final String plaintext; - private final String fingerprint; - - XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { - this.plaintext = plaintext; - this.fingerprint = fingerprint; - } - - public String getPlaintext() { - return plaintext; - } - - - public String getFingerprint() { - return fingerprint; - } - } - - public static class XmppAxolotlKeyTransportMessage { - private final String fingerprint; - private final byte[] key; - private final byte[] iv; - - XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { - this.fingerprint = fingerprint; - this.key = key; - this.iv = iv; - } - - public String getFingerprint() { - return fingerprint; - } - - public byte[] getKey() { - return key; - } - - public byte[] getIv() { - return iv; - } - } - - public static int parseSourceId(final Element axolotlMessage) throws IllegalArgumentException { - final Element header = axolotlMessage.findChild(HEADER); - if (header == null) { - throw new IllegalArgumentException("No header found"); - } - try { - return Integer.parseInt(header.getAttribute(SOURCEID)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("invalid source id"); - } - } private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { this.from = from; @@ -148,6 +92,18 @@ public class XmppAxolotlMessage { this.innerKey = generateKey(); } + public static int parseSourceId(final Element axolotlMessage) throws IllegalArgumentException { + final Element header = axolotlMessage.findChild(HEADER); + if (header == null) { + throw new IllegalArgumentException("No header found"); + } + try { + return Integer.parseInt(header.getAttribute(SOURCEID)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("invalid source id"); + } + } + public static XmppAxolotlMessage fromElement(Element element, Jid from) { return new XmppAxolotlMessage(element, from); } @@ -164,12 +120,28 @@ public class XmppAxolotlMessage { } private static byte[] generateIv() { - SecureRandom random = new SecureRandom(); - byte[] iv = new byte[16]; + final SecureRandom random = new SecureRandom(); + byte[] iv = new byte[Config.TWELVE_BYTE_IV ? 12 : 16]; random.nextBytes(iv); return iv; } + private static byte[] getPaddedBytes(String plaintext) { + int plainLength = plaintext.getBytes().length; + int pad = Math.max(64, (plainLength / 32 + 1) * 32) - plainLength; + SecureRandom random = new SecureRandom(); + int left = random.nextInt(pad); + int right = pad - left; + StringBuilder builder = new StringBuilder(plaintext); + for (int i = 0; i < left; ++i) { + builder.insert(0, random.nextBoolean() ? "\t" : " "); + } + for (int i = 0; i < right; ++i) { + builder.append(random.nextBoolean() ? "\t" : " "); + } + return builder.toString().getBytes(); + } + public boolean hasPayload() { return ciphertext != null; } @@ -189,39 +161,13 @@ public class XmppAxolotlMessage { System.arraycopy(this.innerKey, 0, authtagPlusInnerKey, 0, this.innerKey.length); this.ciphertext = ciphertext; } - } catch (NoSuchAlgorithmException e) { - throw new CryptoFailedException(e); - } catch (NoSuchPaddingException e) { - throw new CryptoFailedException(e); - } catch (InvalidKeyException e) { - throw new CryptoFailedException(e); - } catch (IllegalBlockSizeException e) { - throw new CryptoFailedException(e); - } catch (BadPaddingException e) { - throw new CryptoFailedException(e); - } catch (NoSuchProviderException e) { - throw new CryptoFailedException(e); - } catch (InvalidAlgorithmParameterException e) { + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException + | InvalidAlgorithmParameterException e) { throw new CryptoFailedException(e); } } - private static byte[] getPaddedBytes(String plaintext) { - int plainLength = plaintext.getBytes().length; - int pad = Math.max(64, (plainLength / 32 + 1) * 32) - plainLength; - SecureRandom random = new SecureRandom(); - int left = random.nextInt(pad); - int right = pad - left; - StringBuilder builder = new StringBuilder(plaintext); - for (int i = 0; i < left; ++i) { - builder.insert(0, random.nextBoolean() ? "\t" : " "); - } - for (int i = 0; i < right; ++i) { - builder.append(random.nextBoolean() ? "\t" : " "); - } - return builder.toString().getBytes(); - } - public Jid getFrom() { return this.from; } @@ -297,19 +243,19 @@ public class XmppAxolotlMessage { byte[] key = unpackKey(session, sourceDeviceId); if (key != null) { try { - if (key.length >= 32) { - int authtaglength = key.length - 16; - Log.d(Config.LOGTAG, "found auth tag as part of omemo key"); - byte[] newCipherText = new byte[key.length - 16 + ciphertext.length]; - byte[] newKey = new byte[16]; - System.arraycopy(ciphertext, 0, newCipherText, 0, ciphertext.length); - System.arraycopy(key, 16, newCipherText, ciphertext.length, authtaglength); - System.arraycopy(key, 0, newKey, 0, newKey.length); - ciphertext = newCipherText; - key = newKey; + if (key.length < 32) { + throw new OutdatedSenderException("Key did not contain auth tag. Sender needs to update their OMEMO client"); } - - Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + final int authTagLength = key.length - 16; + byte[] newCipherText = new byte[key.length - 16 + ciphertext.length]; + byte[] newKey = new byte[16]; + System.arraycopy(ciphertext, 0, newCipherText, 0, ciphertext.length); + System.arraycopy(key, 16, newCipherText, ciphertext.length, authTagLength); + System.arraycopy(key, 0, newKey, 0, newKey.length); + ciphertext = newCipherText; + key = newKey; + + final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); @@ -318,22 +264,55 @@ public class XmppAxolotlMessage { String plaintext = new String(cipher.doFinal(ciphertext)); plaintextMessage = new XmppAxolotlPlaintextMessage(Config.OMEMO_PADDING ? plaintext.trim() : plaintext, session.getFingerprint()); - } catch (NoSuchAlgorithmException e) { - throw new CryptoFailedException(e); - } catch (NoSuchPaddingException e) { - throw new CryptoFailedException(e); - } catch (InvalidKeyException e) { - throw new CryptoFailedException(e); - } catch (InvalidAlgorithmParameterException e) { - throw new CryptoFailedException(e); - } catch (IllegalBlockSizeException e) { - throw new CryptoFailedException(e); - } catch (BadPaddingException e) { - throw new CryptoFailedException(e); - } catch (NoSuchProviderException e) { + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException | NoSuchProviderException e) { throw new CryptoFailedException(e); } } return plaintextMessage; } + + public static class XmppAxolotlPlaintextMessage { + private final String plaintext; + private final String fingerprint; + + XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { + this.plaintext = plaintext; + this.fingerprint = fingerprint; + } + + public String getPlaintext() { + return plaintext; + } + + + public String getFingerprint() { + return fingerprint; + } + } + + public static class XmppAxolotlKeyTransportMessage { + private final String fingerprint; + private final byte[] key; + private final byte[] iv; + + XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { + this.fingerprint = fingerprint; + this.key = key; + this.iv = iv; + } + + public String getFingerprint() { + return fingerprint; + } + + public byte[] getKey() { + return key; + } + + public byte[] getIv() { + return iv; + } + } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/entities/Conversation.java b/src/main/java/de/pixart/messenger/entities/Conversation.java index 13bfecfca..0635a4553 100644 --- a/src/main/java/de/pixart/messenger/entities/Conversation.java +++ b/src/main/java/de/pixart/messenger/entities/Conversation.java @@ -89,8 +89,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl private transient MucOptions mucOptions = null; private byte[] symmetricKey; private boolean messagesLeftOnServer = true; - private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE; - private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE; + private ChatState mOutgoingChatState = Config.DEFAULT_CHAT_STATE; + private ChatState mIncomingChatState = Config.DEFAULT_CHAT_STATE; private String mLastReceivedOtrMessageId = null; private String mFirstMamReference = null; diff --git a/src/main/java/de/pixart/messenger/entities/MucOptions.java b/src/main/java/de/pixart/messenger/entities/MucOptions.java index 40a0be58d..c4e711400 100644 --- a/src/main/java/de/pixart/messenger/entities/MucOptions.java +++ b/src/main/java/de/pixart/messenger/entities/MucOptions.java @@ -96,7 +96,7 @@ public class MucOptions { public void resetChatState() { synchronized (users) { for (User user : users) { - user.chatState = Config.DEFAULT_CHATSTATE; + user.chatState = Config.DEFAULT_CHAT_STATE; } } } @@ -747,7 +747,7 @@ public class MucOptions { private long pgpKeyId = 0; private Avatar avatar; private MucOptions options; - private ChatState chatState = Config.DEFAULT_CHATSTATE; + private ChatState chatState = Config.DEFAULT_CHAT_STATE; public User(MucOptions options, Jid fullJid) { this.options = options; diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java index 8dadbd364..5fd701f89 100644 --- a/src/main/java/de/pixart/messenger/parser/MessageParser.java +++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java @@ -26,6 +26,7 @@ import de.pixart.messenger.crypto.OtrService; import de.pixart.messenger.crypto.axolotl.AxolotlService; import de.pixart.messenger.crypto.axolotl.BrokenSessionException; import de.pixart.messenger.crypto.axolotl.NotEncryptedForThisDeviceException; +import de.pixart.messenger.crypto.axolotl.OutdatedSenderException; import de.pixart.messenger.crypto.axolotl.XmppAxolotlMessage; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Bookmark; @@ -235,6 +236,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } catch (NotEncryptedForThisDeviceException e) { return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status); + } catch (OutdatedSenderException e) { + return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status); } if (plaintextMessage != null) { Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index 5f4abb833..bd981774c 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -1753,7 +1753,7 @@ public class XmppConnectionService extends Service { if (delay) { mMessageGenerator.addDelay(packet, message.getTimeSent()); } - if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + if (conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) { if (this.sendChatStates()) { packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); } @@ -2809,7 +2809,7 @@ public class XmppConnectionService extends Service { if (conversation.getMode() == Conversation.MODE_MULTI) { conversation.getMucOptions().resetChatState(); } else { - conversation.setIncomingChatState(Config.DEFAULT_CHATSTATE); + conversation.setIncomingChatState(Config.DEFAULT_CHAT_STATE); } } for (Account account : getAccounts()) { diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index b2a1c8969..2e8e50ecb 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -2020,7 +2020,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke Toast.makeText(getActivity(), activity.getString(R.string.user_has_left_conference, counterpart.getResource()), Toast.LENGTH_SHORT).show(); return; } - if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + if (conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) { activity.xmppConnectionService.sendChatState(conversation); } this.binding.textinput.setText(""); @@ -2158,7 +2158,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } private void updateChatState(final Conversation conversation, final String msg) { - ChatState state = msg.length() == 0 ? Config.DEFAULT_CHATSTATE : ChatState.PAUSED; + ChatState state = msg.length() == 0 ? Config.DEFAULT_CHAT_STATE : ChatState.PAUSED; Account.State status = conversation.getAccount().getStatus(); if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) { activity.xmppConnectionService.sendChatState(conversation); @@ -2967,7 +2967,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { + if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) { service.sendChatState(conversation); } runOnUiThread(() -> { |