properly handle key transport messages. use prekeyparsing only when that attribute is set

This commit is contained in:
Daniel Gultsch 2018-01-18 20:58:55 +01:00
parent 6663eb0a12
commit 036dd82698
5 changed files with 54 additions and 47 deletions

View file

@ -1313,16 +1313,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return session; return session;
} }
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) { public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) {
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
XmppAxolotlSession session = getReceivingSession(message); XmppAxolotlSession session = getReceivingSession(message);
try { try {
plaintextMessage = message.decrypt(session, getOwnDeviceId()); plaintextMessage = message.decrypt(session, getOwnDeviceId());
Integer preKeyId = session.getPreKeyId(); Integer preKeyId = session.getPreKeyIdAndReset();
if (preKeyId != null) { if (preKeyId != null) {
publishBundlesIfNeeded(false, false); postPreKeyMessageHandling(session, preKeyId, postponePreKeyMessageHandling);
session.resetPreKeyId();
} }
} catch (CryptoFailedException e) { } catch (CryptoFailedException e) {
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from "+message.getFrom()+": " + e.getMessage()); Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from "+message.getFrom()+": " + e.getMessage());
@ -1335,12 +1334,22 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return plaintextMessage; return plaintextMessage;
} }
public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) { private void postPreKeyMessageHandling(final XmppAxolotlSession session, int preKeyId, final boolean postpone) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": postPreKeyMessageHandling() preKeyId="+preKeyId+", postpone="+Boolean.toString(postpone));
//TODO: do not republish if we already removed this preKeyId
publishBundlesIfNeeded(false, false);
}
public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
XmppAxolotlSession session = getReceivingSession(message); XmppAxolotlSession session = getReceivingSession(message);
try { try {
keyTransportMessage = message.getParameters(session, getOwnDeviceId()); keyTransportMessage = message.getParameters(session, getOwnDeviceId());
Integer preKeyId = session.getPreKeyIdAndReset();
if (preKeyId != null) {
postPreKeyMessageHandling(session, preKeyId, postponePreKeyMessageHandling);
}
} catch (CryptoFailedException e) { } catch (CryptoFailedException e) {
Log.d(Config.LOGTAG,"could not decrypt keyTransport message "+e.getMessage()); Log.d(Config.LOGTAG,"could not decrypt keyTransport message "+e.getMessage());
keyTransportMessage = null; keyTransportMessage = null;

View file

@ -2,6 +2,7 @@ package eu.siacs.conversations.crypto.axolotl;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
@ -43,7 +44,7 @@ public class XmppAxolotlMessage {
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 Map<Integer, XmppAxolotlSession.AxolotlKey> keys; private final SparseArray<XmppAxolotlSession.AxolotlKey> keys;
private final Jid from; private final Jid from;
private final int sourceDeviceId; private final int sourceDeviceId;
@ -99,7 +100,7 @@ public class XmppAxolotlMessage {
throw new IllegalArgumentException("invalid source id"); throw new IllegalArgumentException("invalid source id");
} }
List<Element> keyElements = header.getChildren(); List<Element> keyElements = header.getChildren();
this.keys = new HashMap<>(keyElements.size()); this.keys = new SparseArray<>();
for (Element keyElement : keyElements) { for (Element keyElement : keyElements) {
switch (keyElement.getName()) { switch (keyElement.getName()) {
case KEYTAG: case KEYTAG:
@ -132,7 +133,7 @@ public class XmppAxolotlMessage {
public XmppAxolotlMessage(Jid from, int sourceDeviceId) { public XmppAxolotlMessage(Jid from, int sourceDeviceId) {
this.from = from; this.from = from;
this.sourceDeviceId = sourceDeviceId; this.sourceDeviceId = sourceDeviceId;
this.keys = new HashMap<>(); this.keys = new SparseArray<>();
this.iv = generateIv(); this.iv = generateIv();
this.innerKey = generateKey(); this.innerKey = generateKey();
} }
@ -159,6 +160,10 @@ public class XmppAxolotlMessage {
return iv; return iv;
} }
public boolean hasPayload() {
return ciphertext != null;
}
public void encrypt(String plaintext) throws CryptoFailedException { public void encrypt(String plaintext) throws CryptoFailedException {
try { try {
SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
@ -205,10 +210,6 @@ public class XmppAxolotlMessage {
return sourceDeviceId; return sourceDeviceId;
} }
public byte[] getCiphertext() {
return ciphertext;
}
public void addDevice(XmppAxolotlSession session) { public void addDevice(XmppAxolotlSession session) {
XmppAxolotlSession.AxolotlKey key; XmppAxolotlSession.AxolotlKey key;
if (authtagPlusInnerKey != null) { if (authtagPlusInnerKey != null) {
@ -233,13 +234,13 @@ public class XmppAxolotlMessage {
Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX); Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX);
Element headerElement = encryptionElement.addChild(HEADER); Element headerElement = encryptionElement.addChild(HEADER);
headerElement.setAttribute(SOURCEID, sourceDeviceId); headerElement.setAttribute(SOURCEID, sourceDeviceId);
for (Map.Entry<Integer, XmppAxolotlSession.AxolotlKey> keyEntry : keys.entrySet()) { for(int i = 0; i < keys.size(); ++i) {
Element keyElement = new Element(KEYTAG); Element keyElement = new Element(KEYTAG);
keyElement.setAttribute(REMOTEID, keyEntry.getKey()); keyElement.setAttribute(REMOTEID, keys.keyAt(i));
if (keyEntry.getValue().prekey) { if (keys.valueAt(i).prekey) {
keyElement.setAttribute("prekey","true"); keyElement.setAttribute("prekey","true");
} }
keyElement.setContent(Base64.encodeToString(keyEntry.getValue().key, Base64.NO_WRAP)); keyElement.setContent(Base64.encodeToString(keys.valueAt(i).key, Base64.NO_WRAP));
headerElement.addChild(keyElement); headerElement.addChild(keyElement);
} }
headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP)); headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP));
@ -267,7 +268,6 @@ public class XmppAxolotlMessage {
byte[] key = unpackKey(session, sourceDeviceId); byte[] key = unpackKey(session, sourceDeviceId);
if (key != null) { if (key != null) {
try { try {
if (key.length >= 32) { if (key.length >= 32) {
int authtaglength = key.length - 16; int authtaglength = key.length - 16;
Log.d(Config.LOGTAG,"found auth tag as part of omemo key"); Log.d(Config.LOGTAG,"found auth tag as part of omemo key");

View file

@ -43,15 +43,12 @@ public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
this.account = account; this.account = account;
} }
public Integer getPreKeyId() { public Integer getPreKeyIdAndReset() {
final Integer preKeyId = this.preKeyId;
this.preKeyId = null;
return preKeyId; return preKeyId;
} }
public void resetPreKeyId() {
preKeyId = null;
}
public String getFingerprint() { public String getFingerprint() {
return identityKey == null ? null : CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); return identityKey == null ? null : CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
} }
@ -87,11 +84,10 @@ public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
FingerprintStatus status = getTrust(); FingerprintStatus status = getTrust();
if (!status.isCompromised()) { if (!status.isCompromised()) {
try { try {
CiphertextMessage ciphertextMessage; if (encryptedKey.prekey) {
try { PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKey.key);
ciphertextMessage = new PreKeySignalMessage(encryptedKey.key); Optional<Integer> optionalPreKeyId = preKeySignalMessage.getPreKeyId();
Optional<Integer> optionalPreKeyId = ((PreKeySignalMessage) ciphertextMessage).getPreKeyId(); IdentityKey identityKey = preKeySignalMessage.getIdentityKey();
IdentityKey identityKey = ((PreKeySignalMessage) ciphertextMessage).getIdentityKey();
if (!optionalPreKeyId.isPresent()) { if (!optionalPreKeyId.isPresent()) {
throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId"); throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
} }
@ -100,15 +96,13 @@ public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed."); throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
} }
this.identityKey = identityKey; this.identityKey = identityKey;
} catch (InvalidVersionException | InvalidMessageException e) { plaintext = cipher.decrypt(preKeySignalMessage);
ciphertextMessage = new SignalMessage(encryptedKey.key);
}
if (ciphertextMessage instanceof PreKeySignalMessage) {
plaintext = cipher.decrypt((PreKeySignalMessage) ciphertextMessage);
} else { } else {
plaintext = cipher.decrypt((SignalMessage) ciphertextMessage); SignalMessage signalMessage = new SignalMessage(encryptedKey.key);
plaintext = cipher.decrypt(signalMessage);
preKeyId = null; //better safe than sorry because we use that to do special after prekey handling
} }
} catch (InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException | InvalidKeyIdException | UntrustedIdentityException e) { } catch (InvalidVersionException | InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException | InvalidKeyIdException | UntrustedIdentityException e) {
if (!(e instanceof DuplicateMessageException)) { if (!(e instanceof DuplicateMessageException)) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -163,24 +163,28 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return false; return false;
} }
private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status) { private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean postpone) {
AxolotlService service = conversation.getAccount().getAxolotlService(); final AxolotlService service = conversation.getAccount().getAxolotlService();
XmppAxolotlMessage xmppAxolotlMessage; final XmppAxolotlMessage xmppAxolotlMessage;
try { try {
xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid());
} catch (Exception e) { } catch (Exception e) {
Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": invalid omemo message received " + e.getMessage()); Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": invalid omemo message received " + e.getMessage());
return null; return null;
} }
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage); if (xmppAxolotlMessage.hasPayload()) {
if (plaintextMessage != null) { final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); if (plaintextMessage != null) {
finishedMessage.setFingerprint(plaintextMessage.getFingerprint()); Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint()); finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
return finishedMessage; Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint());
return finishedMessage;
}
} else { } else {
return null; Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+": received OMEMO key transport message");
service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
} }
return null;
} }
private class Invite { private class Invite {
@ -468,7 +472,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} else { } else {
origin = from; origin = from;
} }
message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status); message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, query != null);
if (message == null) { if (message == null) {
if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.toBareJid()), isTypeGroupChat, packet)) { if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.toBareJid()), isTypeGroupChat, packet)) {
mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateConversationUi();

View file

@ -448,7 +448,7 @@ public class JingleConnection implements Transferable {
} }
this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
if (mXmppAxolotlMessage != null) { if (mXmppAxolotlMessage != null) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage); XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage, false);
if (transportMessage != null) { if (transportMessage != null) {
message.setEncryption(Message.ENCRYPTION_AXOLOTL); message.setEncryption(Message.ENCRYPTION_AXOLOTL);
this.file.setKey(transportMessage.getKey()); this.file.setKey(transportMessage.getKey());