code cleanup in bundle parsing

also switch to guavas base64 parser to avoid potential ROM bugs
This commit is contained in:
Daniel Gultsch 2020-05-03 09:55:09 +02:00
parent 4f8715a349
commit 5a5f887229

View file

@ -2,11 +2,13 @@ package eu.siacs.conversations.parser;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.common.io.BaseEncoding;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPublicKey; import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.state.PreKeyBundle;
@ -27,12 +29,10 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.entities.Room;
import eu.siacs.conversations.services.ChannelDiscoveryService;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
@ -46,6 +46,56 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
super(service); super(service);
} }
public static List<Jid> items(IqPacket packet) {
ArrayList<Jid> items = new ArrayList<>();
final Element query = packet.findChild("query", Namespace.DISCO_ITEMS);
if (query == null) {
return items;
}
for (Element child : query.getChildren()) {
if ("item".equals(child.getName())) {
Jid jid = child.getAttributeAsJid("jid");
if (jid != null) {
items.add(jid);
}
}
}
return items;
}
public static Room parseRoom(IqPacket packet) {
final Element query = packet.findChild("query", Namespace.DISCO_INFO);
if (query == null) {
return null;
}
final Element x = query.findChild("x");
if (x == null) {
return null;
}
final Element identity = query.findChild("identity");
Data data = Data.parse(x);
String address = packet.getFrom().toEscapedString();
String name = identity == null ? null : identity.getAttribute("name");
String roomName = data.getValue("muc#roomconfig_roomname");
String description = data.getValue("muc#roominfo_description");
String language = data.getValue("muc#roominfo_lang");
String occupants = data.getValue("muc#roominfo_occupants");
int nusers;
try {
nusers = occupants == null ? 0 : Integer.parseInt(occupants);
} catch (NumberFormatException e) {
nusers = 0;
}
return new Room(
address,
TextUtils.isEmpty(roomName) ? name : roomName,
description,
language,
nusers
);
}
private void rosterItems(final Account account, final Element query) { private void rosterItems(final Account account, final Element query) {
final String version = query.getAttribute("ver"); final String version = query.getAttribute("ver");
if (version != null) { if (version != null) {
@ -130,7 +180,6 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
deviceIds.add(id); deviceIds.add(id);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid <device> node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping..."); Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid <device> node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping...");
continue;
} }
} }
} }
@ -138,7 +187,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return deviceIds; return deviceIds;
} }
public Integer signedPreKeyId(final Element bundle) { private Integer signedPreKeyId(final Element bundle) {
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
if (signedPreKeyPublic == null) { if (signedPreKeyPublic == null) {
return null; return null;
@ -150,45 +199,44 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
} }
public ECPublicKey signedPreKeyPublic(final Element bundle) { private ECPublicKey signedPreKeyPublic(final Element bundle) {
ECPublicKey publicKey = null; ECPublicKey publicKey = null;
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic");
if (signedPreKeyPublic == null) { if (signedPreKeyPublic == null) {
return null; return null;
} }
try { try {
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(), Base64.DEFAULT), 0); publicKey = Curve.decodePoint(BaseEncoding.base64().decode(signedPreKeyPublic), 0);
} catch (Throwable e) { } catch (final IllegalArgumentException | InvalidKeyException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid signedPreKeyPublic in PEP: " + e.getMessage()); Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid signedPreKeyPublic in PEP: " + e.getMessage());
} }
return publicKey; return publicKey;
} }
public byte[] signedPreKeySignature(final Element bundle) { private byte[] signedPreKeySignature(final Element bundle) {
final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature"); final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature");
if (signedPreKeySignature == null) { if (signedPreKeySignature == null) {
return null; return null;
} }
try { try {
return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT); return BaseEncoding.base64().decode(signedPreKeySignature);
} catch (Throwable e) { } catch (final IllegalArgumentException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature"); Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature");
return null; return null;
} }
} }
public IdentityKey identityKey(final Element bundle) { private IdentityKey identityKey(final Element bundle) {
IdentityKey identityKey = null; final String identityKey = bundle.findChildContent("identityKey");
final Element identityKeyElement = bundle.findChild("identityKey"); if (identityKey == null) {
if (identityKeyElement == null) {
return null; return null;
} }
try { try {
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); return new IdentityKey(BaseEncoding.base64().decode(identityKey), 0);
} catch (Throwable e) { } catch (final IllegalArgumentException | InvalidKeyException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid identityKey in PEP: " + e.getMessage()); Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid identityKey in PEP: " + e.getMessage());
return null;
} }
return identityKey;
} }
public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) { public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) {
@ -215,7 +263,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
Integer preKeyId = null; Integer preKeyId = null;
try { try {
preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId"));
final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); final ECPublicKey preKeyPublic = Curve.decodePoint(BaseEncoding.base64().decode(preKeyPublicElement.getContent()), 0);
preKeyRecords.put(preKeyId, preKeyPublic); preKeyRecords.put(preKeyId, preKeyPublic);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " + preKeyPublicElement.toString()); Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " + preKeyPublicElement.toString());
@ -230,18 +278,22 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
Element item = getItem(packet); Element item = getItem(packet);
Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
Element chain = verification != null ? verification.findChild("chain") : null; Element chain = verification != null ? verification.findChild("chain") : null;
Element signature = verification != null ? verification.findChild("signature") : null; String signature = verification != null ? verification.findChildContent("signature") : null;
if (chain != null && signature != null) { if (chain != null && signature != null) {
List<Element> certElements = chain.getChildren(); List<Element> certElements = chain.getChildren();
X509Certificate[] certificates = new X509Certificate[certElements.size()]; X509Certificate[] certificates = new X509Certificate[certElements.size()];
try { try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
int i = 0; int i = 0;
for (Element cert : certElements) { for (final Element certElement : certElements) {
certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(), Base64.DEFAULT))); final String cert = certElement.getContent();
if (cert == null) {
continue;
}
certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(BaseEncoding.base64().decode(cert)));
++i; ++i;
} }
return new Pair<>(certificates, Base64.decode(signature.getContent(), Base64.DEFAULT)); return new Pair<>(certificates, BaseEncoding.base64().decode(signature));
} catch (CertificateException e) { } catch (CertificateException e) {
return null; return null;
} }
@ -251,7 +303,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
public PreKeyBundle bundle(final IqPacket bundle) { public PreKeyBundle bundle(final IqPacket bundle) {
Element bundleItem = getItem(bundle); final Element bundleItem = getItem(bundle);
if (bundleItem == null) { if (bundleItem == null) {
return null; return null;
} }
@ -259,14 +311,17 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
if (bundleElement == null) { if (bundleElement == null) {
return null; return null;
} }
ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); final ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
Integer signedPreKeyId = signedPreKeyId(bundleElement); final Integer signedPreKeyId = signedPreKeyId(bundleElement);
byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); final byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
IdentityKey identityKey = identityKey(bundleElement); final IdentityKey identityKey = identityKey(bundleElement);
if (signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) { if (signedPreKeyId == null
|| signedPreKeyPublic == null
|| identityKey == null
|| signedPreKeySignature == null
|| signedPreKeySignature.length == 0) {
return null; return null;
} }
return new PreKeyBundle(0, 0, 0, null, return new PreKeyBundle(0, 0, 0, null,
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
} }
@ -398,55 +453,4 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
} }
public static List<Jid> items(IqPacket packet) {
ArrayList<Jid> items = new ArrayList<>();
final Element query = packet.findChild("query", Namespace.DISCO_ITEMS);
if (query == null) {
return items;
}
for(Element child : query.getChildren()) {
if ("item".equals(child.getName())) {
Jid jid = child.getAttributeAsJid("jid");
if (jid != null) {
items.add(jid);
}
}
}
return items;
}
public static Room parseRoom(IqPacket packet) {
final Element query = packet.findChild("query", Namespace.DISCO_INFO);
if(query == null) {
return null;
}
final Element x = query.findChild("x");
if (x == null) {
return null;
}
final Element identity = query.findChild("identity");
Data data = Data.parse(x);
String address = packet.getFrom().toEscapedString();
String name = identity == null ? null : identity.getAttribute("name");
String roomName = data.getValue("muc#roomconfig_roomname");;
String description = data.getValue("muc#roominfo_description");
String language = data.getValue("muc#roominfo_lang");
String occupants = data.getValue("muc#roominfo_occupants");
int nusers;
try {
nusers = occupants == null ? 0 : Integer.parseInt(occupants);
} catch (NumberFormatException e) {
nusers = 0;
}
return new Room(
address,
TextUtils.isEmpty(roomName) ? name : roomName,
description,
language,
nusers
);
}
} }