omemo changes: use 12 byte IV, no longer accept auth tag appended to payload

This commit is contained in:
Daniel Gultsch 2020-01-18 12:08:03 +01:00
parent b56f6fbf4c
commit e38a9cd729
10 changed files with 1807 additions and 1800 deletions

View File

@ -100,7 +100,7 @@ public final class Config {
public static final boolean REMOVE_BROKEN_DEVICES = false; public static final boolean REMOVE_BROKEN_DEVICES = false;
public static final boolean OMEMO_PADDING = false; public static final boolean OMEMO_PADDING = false;
public static final boolean PUT_AUTH_TAG_INTO_KEY = true; public static final boolean PUT_AUTH_TAG_INTO_KEY = true;
public static final boolean TWELVE_BYTE_IV = false; public static final boolean TWELVE_BYTE_IV = true;
public static final boolean USE_BOOKMARKS2 = false; public static final boolean USE_BOOKMARKS2 = false;
@ -125,7 +125,7 @@ public final class Config {
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5; public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5;
public static final int MAM_MAX_MESSAGES = 750; 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 = 8; public static final int TYPING_TIMEOUT = 8;
public static final int EXPIRY_INTERVAL = 30 * 60 * 1000; // 30 minutes public static final int EXPIRY_INTERVAL = 30 * 60 * 1000; // 30 minutes

View File

@ -79,16 +79,37 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private final Map<Jid, Boolean> fetchDeviceListStatus = new HashMap<>(); private final Map<Jid, Boolean> fetchDeviceListStatus = new HashMap<>();
private final HashMap<Jid, List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>(); private final HashMap<Jid, List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>();
private final SerialSingleThreadExecutor executor; 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 int numPublishTriesOnEmptyPep = 0;
private boolean pepBroken = false; private boolean pepBroken = false;
private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>();
private int lastDeviceListNotificationHash = 0; 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<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 Set<SignalProtocolAddress> postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup
private AtomicBoolean changeAccessMode = new AtomicBoolean(false); 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 @Override
public void onAdvancedStreamFeaturesAvailable(Account account) { public void onAdvancedStreamFeaturesAvailable(Account account) {
if (Config.supportOmemo() if (Config.supportOmemo()
@ -145,172 +166,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return false; 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() { public String getOwnFingerprint() {
return CryptoHelper.bytesToHex(axolotlStore.getIdentityKeyPair().getPublicKey().serialize()); return CryptoHelper.bytesToHex(axolotlStore.getIdentityKeyPair().getPublicKey().serialize());
} }
@ -359,7 +214,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return s; return s;
} }
public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) { public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid()); SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid());
ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values()); ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values());
@ -924,8 +778,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
private final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>();
private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) { private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0); SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName()); Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName());
@ -963,14 +815,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty()); 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) { public void fetchDeviceIds(final Jid jid) {
fetchDeviceIds(jid, null); fetchDeviceIds(jid, null);
} }
@ -1047,11 +891,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
interface OnSessionBuildFromPep {
void onSessionBuildSuccessful();
void onSessionBuildFailed();
}
private void buildSessionFromPEP(final SignalProtocolAddress address) { private void buildSessionFromPEP(final SignalProtocolAddress address) {
buildSessionFromPEP(address, null); buildSessionFromPEP(address, null);
} }
@ -1399,7 +1238,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) { private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
XmppAxolotlSession session = sessions.get(senderAddress); XmppAxolotlSession session = sessions.get(senderAddress);
if (session == null) { 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); session = recreateUncachedSession(senderAddress);
if (session == null) { if (session == null) {
session = new XmppAxolotlSession(account, axolotlStore, senderAddress); session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
@ -1408,7 +1246,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return session; 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; XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
XmppAxolotlSession session = getReceivingSession(message); XmppAxolotlSession session = getReceivingSession(message);
@ -1427,6 +1265,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} catch (final BrokenSessionException e) { } catch (final BrokenSessionException e) {
throw e; throw e;
} catch (final OutdatedSenderException e) {
Log.e(Config.LOGTAG,account.getJid().asBareJid()+": "+e.getMessage());
throw e;
} catch (CryptoFailedException e) { } catch (CryptoFailedException e) {
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e); Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e);
} }
@ -1503,7 +1344,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) { private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
try { try {
return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName())); return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
@ -1533,7 +1373,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) { public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
final XmppAxolotlSession session = getReceivingSession(message); final XmppAxolotlSession session = getReceivingSession(message);
@ -1565,4 +1404,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);
}
}
}
}
}
} }

View File

@ -0,0 +1,8 @@
package eu.siacs.conversations.crypto.axolotl;
public class OutdatedSenderException extends CryptoFailedException {
public OutdatedSenderException(final String msg) {
super(msg);
}
}

View File

@ -38,69 +38,13 @@ public class XmppAxolotlMessage {
private static final String KEYTYPE = "AES"; private static final String KEYTYPE = "AES";
private static final String CIPHERMODE = "AES/GCM/NoPadding"; private static final String CIPHERMODE = "AES/GCM/NoPadding";
private static final String PROVIDER = "BC"; 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[] innerKey;
private byte[] ciphertext = null; private byte[] ciphertext = null;
private byte[] authtagPlusInnerKey = null; private byte[] authtagPlusInnerKey = null;
private byte[] iv = 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 { private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException {
this.from = from; this.from = from;
@ -149,6 +93,18 @@ public class XmppAxolotlMessage {
this.innerKey = generateKey(); 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) { public static XmppAxolotlMessage fromElement(Element element, Jid from) {
return new XmppAxolotlMessage(element, from); return new XmppAxolotlMessage(element, from);
} }
@ -171,6 +127,22 @@ public class XmppAxolotlMessage {
return 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() { public boolean hasPayload() {
return ciphertext != null; return ciphertext != null;
} }
@ -197,22 +169,6 @@ public class XmppAxolotlMessage {
} }
} }
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() { public Jid getFrom() {
return this.from; return this.from;
} }
@ -288,20 +244,19 @@ public class XmppAxolotlMessage {
byte[] key = unpackKey(session, sourceDeviceId); byte[] key = unpackKey(session, sourceDeviceId);
if (key != null) { if (key != null) {
try { try {
//TODO remove support for *not* having auth tag in key if (key.length < 32) {
if (key.length >= 32) { throw new OutdatedSenderException("Key did not contain auth tag. Sender needs to update their OMEMO client");
int authtaglength = key.length - 16; }
Log.d(Config.LOGTAG,"found auth tag as part of omemo key"); final int authTagLength = key.length - 16;
byte[] newCipherText = new byte[key.length - 16 + ciphertext.length]; byte[] newCipherText = new byte[key.length - 16 + ciphertext.length];
byte[] newKey = new byte[16]; byte[] newKey = new byte[16];
System.arraycopy(ciphertext, 0, newCipherText, 0, ciphertext.length); System.arraycopy(ciphertext, 0, newCipherText, 0, ciphertext.length);
System.arraycopy(key, 16, newCipherText, ciphertext.length, authtaglength); System.arraycopy(key, 16, newCipherText, ciphertext.length, authTagLength);
System.arraycopy(key, 0, newKey, 0, newKey.length); System.arraycopy(key, 0, newKey, 0, newKey.length);
ciphertext = newCipherText; ciphertext = newCipherText;
key = newKey; key = newKey;
}
Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv);
@ -318,4 +273,47 @@ public class XmppAxolotlMessage {
} }
return plaintextMessage; 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;
}
}
} }

View File

@ -76,8 +76,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
private Jid nextCounterpart; private Jid nextCounterpart;
private transient MucOptions mucOptions = null; private transient MucOptions mucOptions = null;
private boolean messagesLeftOnServer = true; private boolean messagesLeftOnServer = true;
private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE; private ChatState mOutgoingChatState = Config.DEFAULT_CHAT_STATE;
private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE; private ChatState mIncomingChatState = Config.DEFAULT_CHAT_STATE;
private String mFirstMamReference = null; private String mFirstMamReference = null;
public Conversation(final String name, final Account account, final Jid contactJid, public Conversation(final String name, final Account account, final Jid contactJid,

View File

@ -94,7 +94,7 @@ public class MucOptions {
public void resetChatState() { public void resetChatState() {
synchronized (users) { synchronized (users) {
for (User user : users) { for (User user : users) {
user.chatState = Config.DEFAULT_CHATSTATE; user.chatState = Config.DEFAULT_CHAT_STATE;
} }
} }
} }
@ -746,7 +746,7 @@ public class MucOptions {
private long pgpKeyId = 0; private long pgpKeyId = 0;
private Avatar avatar; private Avatar avatar;
private MucOptions options; private MucOptions options;
private ChatState chatState = Config.DEFAULT_CHATSTATE; private ChatState chatState = Config.DEFAULT_CHAT_STATE;
public User(MucOptions options, Jid fullJid) { public User(MucOptions options, Jid fullJid) {
this.options = options; this.options = options;

View File

@ -19,6 +19,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.BrokenSessionException; import eu.siacs.conversations.crypto.axolotl.BrokenSessionException;
import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException; import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
import eu.siacs.conversations.crypto.axolotl.OutdatedSenderException;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Bookmark;
@ -140,6 +141,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} }
} catch (NotEncryptedForThisDeviceException e) { } catch (NotEncryptedForThisDeviceException e) {
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status); 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) { if (plaintextMessage != null) {
Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);

View File

@ -34,7 +34,6 @@ import android.provider.ContactsContract;
import android.security.KeyChain; import android.security.KeyChain;
import android.support.annotation.BoolRes; import android.support.annotation.BoolRes;
import android.support.annotation.IntegerRes; import android.support.annotation.IntegerRes;
import android.support.annotation.NonNull;
import android.support.v4.app.RemoteInput; import android.support.v4.app.RemoteInput;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.text.TextUtils; import android.text.TextUtils;
@ -1515,7 +1514,7 @@ public class XmppConnectionService extends Service {
if (delay) { if (delay) {
mMessageGenerator.addDelay(packet, message.getTimeSent()); mMessageGenerator.addDelay(packet, message.getTimeSent());
} }
if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { if (conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) {
if (this.sendChatStates()) { if (this.sendChatStates()) {
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
} }
@ -2514,7 +2513,7 @@ public class XmppConnectionService extends Service {
if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.getMucOptions().resetChatState(); conversation.getMucOptions().resetChatState();
} else { } else {
conversation.setIncomingChatState(Config.DEFAULT_CHATSTATE); conversation.setIncomingChatState(Config.DEFAULT_CHAT_STATE);
} }
} }
for (Account account : getAccounts()) { for (Account account : getAccounts()) {

View File

@ -1721,7 +1721,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
} }
public void privateMessageWith(final Jid counterpart) { public void privateMessageWith(final Jid counterpart) {
if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { if (conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) {
activity.xmppConnectionService.sendChatState(conversation); activity.xmppConnectionService.sendChatState(conversation);
} }
this.binding.textinput.setText(""); this.binding.textinput.setText("");
@ -1859,7 +1859,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
} }
private void updateChatState(final Conversation conversation, final String msg) { 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(); Account.State status = conversation.getAccount().getStatus();
if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) { if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) {
activity.xmppConnectionService.sendChatState(conversation); activity.xmppConnectionService.sendChatState(conversation);
@ -2619,7 +2619,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return; return;
} }
Account.State status = conversation.getAccount().getStatus(); 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); service.sendChatState(conversation);
} }
if (storeNextMessage()) { if (storeNextMessage()) {

View File

@ -31,7 +31,7 @@
<resources> <resources>
<string name="pref_about_message" translatable="false"> <string name="pref_about_message" translatable="false">
Conversations • the very last word in instant messaging. Conversations • the very last word in instant messaging.
\n\nCopyright © 2014-2019 Daniel Gultsch \n\nCopyright © 2014-2020 Daniel Gultsch
\n\nThis program is free software: you can redistribute it and/or modify \n\nThis program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or