1package eu.siacs.conversations.parser;
2
3import android.util.Log;
4import android.util.Pair;
5import androidx.annotation.NonNull;
6import com.google.common.base.CharMatcher;
7import com.google.common.io.BaseEncoding;
8import eu.siacs.conversations.Config;
9import eu.siacs.conversations.crypto.axolotl.AxolotlService;
10import eu.siacs.conversations.services.XmppConnectionService;
11import eu.siacs.conversations.xml.Element;
12import eu.siacs.conversations.xml.Namespace;
13import eu.siacs.conversations.xmpp.XmppConnection;
14import eu.siacs.conversations.xmpp.manager.BlockingManager;
15import eu.siacs.conversations.xmpp.manager.DiscoManager;
16import eu.siacs.conversations.xmpp.manager.EntityTimeManager;
17import eu.siacs.conversations.xmpp.manager.PingManager;
18import eu.siacs.conversations.xmpp.manager.RosterManager;
19import eu.siacs.conversations.xmpp.manager.UnifiedPushManager;
20import im.conversations.android.xmpp.model.blocking.Block;
21import im.conversations.android.xmpp.model.blocking.Unblock;
22import im.conversations.android.xmpp.model.disco.info.InfoQuery;
23import im.conversations.android.xmpp.model.error.Condition;
24import im.conversations.android.xmpp.model.error.Error;
25import im.conversations.android.xmpp.model.ibb.InBandByteStream;
26import im.conversations.android.xmpp.model.ping.Ping;
27import im.conversations.android.xmpp.model.roster.Query;
28import im.conversations.android.xmpp.model.stanza.Iq;
29import im.conversations.android.xmpp.model.time.Time;
30import im.conversations.android.xmpp.model.up.Push;
31import im.conversations.android.xmpp.model.version.Version;
32import java.io.ByteArrayInputStream;
33import java.security.cert.CertificateException;
34import java.security.cert.CertificateFactory;
35import java.security.cert.X509Certificate;
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.HashSet;
39import java.util.List;
40import java.util.Map;
41import java.util.Set;
42import java.util.function.Consumer;
43import org.whispersystems.libsignal.IdentityKey;
44import org.whispersystems.libsignal.InvalidKeyException;
45import org.whispersystems.libsignal.ecc.Curve;
46import org.whispersystems.libsignal.ecc.ECPublicKey;
47import org.whispersystems.libsignal.state.PreKeyBundle;
48
49public class IqParser extends AbstractParser implements Consumer<Iq> {
50
51 public IqParser(final XmppConnectionService service, final XmppConnection connection) {
52 super(service, connection);
53 }
54
55 public static String avatarData(final Iq packet) {
56 final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
57 if (pubsub == null) {
58 return null;
59 }
60 final Element items = pubsub.findChild("items");
61 if (items == null) {
62 return null;
63 }
64 return AbstractParser.avatarData(items);
65 }
66
67 public static Element getItem(final Iq packet) {
68 final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
69 if (pubsub == null) {
70 return null;
71 }
72 final Element items = pubsub.findChild("items");
73 if (items == null) {
74 return null;
75 }
76 return items.findChild("item");
77 }
78
79 @NonNull
80 public static Set<Integer> deviceIds(final Element item) {
81 Set<Integer> deviceIds = new HashSet<>();
82 if (item != null) {
83 final Element list = item.findChild("list");
84 if (list != null) {
85 for (Element device : list.getChildren()) {
86 if (!device.getName().equals("device")) {
87 continue;
88 }
89 try {
90 Integer id = Integer.valueOf(device.getAttribute("id"));
91 deviceIds.add(id);
92 } catch (NumberFormatException e) {
93 Log.e(
94 Config.LOGTAG,
95 AxolotlService.LOGPREFIX
96 + " : "
97 + "Encountered invalid <device> node in PEP ("
98 + e.getMessage()
99 + "):"
100 + device
101 + ", skipping...");
102 }
103 }
104 }
105 }
106 return deviceIds;
107 }
108
109 private static Integer signedPreKeyId(final Element bundle) {
110 final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
111 if (signedPreKeyPublic == null) {
112 return null;
113 }
114 try {
115 return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId"));
116 } catch (NumberFormatException e) {
117 return null;
118 }
119 }
120
121 private static ECPublicKey signedPreKeyPublic(final Element bundle) {
122 ECPublicKey publicKey = null;
123 final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic");
124 if (signedPreKeyPublic == null) {
125 return null;
126 }
127 try {
128 publicKey = Curve.decodePoint(base64decode(signedPreKeyPublic), 0);
129 } catch (final IllegalArgumentException | InvalidKeyException e) {
130 Log.e(
131 Config.LOGTAG,
132 AxolotlService.LOGPREFIX
133 + " : "
134 + "Invalid signedPreKeyPublic in PEP: "
135 + e.getMessage());
136 }
137 return publicKey;
138 }
139
140 private static byte[] signedPreKeySignature(final Element bundle) {
141 final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature");
142 if (signedPreKeySignature == null) {
143 return null;
144 }
145 try {
146 return base64decode(signedPreKeySignature);
147 } catch (final IllegalArgumentException e) {
148 Log.e(
149 Config.LOGTAG,
150 AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature");
151 return null;
152 }
153 }
154
155 private static IdentityKey identityKey(final Element bundle) {
156 final String identityKey = bundle.findChildContent("identityKey");
157 if (identityKey == null) {
158 return null;
159 }
160 try {
161 return new IdentityKey(base64decode(identityKey), 0);
162 } catch (final IllegalArgumentException | InvalidKeyException e) {
163 Log.e(
164 Config.LOGTAG,
165 AxolotlService.LOGPREFIX
166 + " : "
167 + "Invalid identityKey in PEP: "
168 + e.getMessage());
169 return null;
170 }
171 }
172
173 public static Map<Integer, ECPublicKey> preKeyPublics(final Iq packet) {
174 Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
175 Element item = getItem(packet);
176 if (item == null) {
177 Log.d(
178 Config.LOGTAG,
179 AxolotlService.LOGPREFIX
180 + " : "
181 + "Couldn't find <item> in bundle IQ packet: "
182 + packet);
183 return null;
184 }
185 final Element bundleElement = item.findChild("bundle");
186 if (bundleElement == null) {
187 return null;
188 }
189 final Element prekeysElement = bundleElement.findChild("prekeys");
190 if (prekeysElement == null) {
191 Log.d(
192 Config.LOGTAG,
193 AxolotlService.LOGPREFIX
194 + " : "
195 + "Couldn't find <prekeys> in bundle IQ packet: "
196 + packet);
197 return null;
198 }
199 for (Element preKeyPublicElement : prekeysElement.getChildren()) {
200 if (!preKeyPublicElement.getName().equals("preKeyPublic")) {
201 Log.d(
202 Config.LOGTAG,
203 AxolotlService.LOGPREFIX
204 + " : "
205 + "Encountered unexpected tag in prekeys list: "
206 + preKeyPublicElement);
207 continue;
208 }
209 final String preKey = preKeyPublicElement.getContent();
210 if (preKey == null) {
211 continue;
212 }
213 Integer preKeyId = null;
214 try {
215 preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId"));
216 final ECPublicKey preKeyPublic = Curve.decodePoint(base64decode(preKey), 0);
217 preKeyRecords.put(preKeyId, preKeyPublic);
218 } catch (NumberFormatException e) {
219 Log.e(
220 Config.LOGTAG,
221 AxolotlService.LOGPREFIX
222 + " : "
223 + "could not parse preKeyId from preKey "
224 + preKeyPublicElement);
225 } catch (Throwable e) {
226 Log.e(
227 Config.LOGTAG,
228 AxolotlService.LOGPREFIX
229 + " : "
230 + "Invalid preKeyPublic (ID="
231 + preKeyId
232 + ") in PEP: "
233 + e.getMessage()
234 + ", skipping...");
235 }
236 }
237 return preKeyRecords;
238 }
239
240 private static byte[] base64decode(String input) {
241 return BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(input));
242 }
243
244 public static Pair<X509Certificate[], byte[]> verification(final Iq packet) {
245 Element item = getItem(packet);
246 Element verification =
247 item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
248 Element chain = verification != null ? verification.findChild("chain") : null;
249 String signature = verification != null ? verification.findChildContent("signature") : null;
250 if (chain != null && signature != null) {
251 List<Element> certElements = chain.getChildren();
252 X509Certificate[] certificates = new X509Certificate[certElements.size()];
253 try {
254 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
255 int i = 0;
256 for (final Element certElement : certElements) {
257 final String cert = certElement.getContent();
258 if (cert == null) {
259 continue;
260 }
261 certificates[i] =
262 (X509Certificate)
263 certificateFactory.generateCertificate(
264 new ByteArrayInputStream(
265 BaseEncoding.base64().decode(cert)));
266 ++i;
267 }
268 return new Pair<>(certificates, BaseEncoding.base64().decode(signature));
269 } catch (CertificateException e) {
270 return null;
271 }
272 } else {
273 return null;
274 }
275 }
276
277 public static PreKeyBundle bundle(final Iq bundle) {
278 final Element bundleItem = getItem(bundle);
279 if (bundleItem == null) {
280 return null;
281 }
282 final Element bundleElement = bundleItem.findChild("bundle");
283 if (bundleElement == null) {
284 return null;
285 }
286 final ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
287 final Integer signedPreKeyId = signedPreKeyId(bundleElement);
288 final byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
289 final IdentityKey identityKey = identityKey(bundleElement);
290 if (signedPreKeyId == null
291 || signedPreKeyPublic == null
292 || identityKey == null
293 || signedPreKeySignature == null
294 || signedPreKeySignature.length == 0) {
295 return null;
296 }
297 return new PreKeyBundle(
298 0,
299 0,
300 0,
301 null,
302 signedPreKeyId,
303 signedPreKeyPublic,
304 signedPreKeySignature,
305 identityKey);
306 }
307
308 public static List<PreKeyBundle> preKeys(final Iq preKeys) {
309 List<PreKeyBundle> bundles = new ArrayList<>();
310 Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
311 if (preKeyPublics != null) {
312 for (Integer preKeyId : preKeyPublics.keySet()) {
313 ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId);
314 bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, 0, null, null, null));
315 }
316 }
317
318 return bundles;
319 }
320
321 @Override
322 public void accept(final Iq packet) {
323 final var type = packet.getType();
324 switch (type) {
325 case SET -> acceptPush(packet);
326 case GET -> acceptRequest(packet);
327 default ->
328 throw new AssertionError(
329 "IQ results and errors should are handled in callbacks");
330 }
331 }
332
333 private void acceptPush(final Iq packet) {
334 if (packet.hasExtension(Query.class)) {
335 this.getManager(RosterManager.class).push(packet);
336 } else if (packet.hasExtension(Block.class)) {
337 this.getManager(BlockingManager.class).pushBlock(packet);
338 } else if (packet.hasExtension(Unblock.class)) {
339 this.getManager(BlockingManager.class).pushUnblock(packet);
340 } else if (packet.hasExtension(InBandByteStream.class)) {
341 mXmppConnectionService
342 .getJingleConnectionManager()
343 .deliverIbbPacket(getAccount(), packet);
344 } else if (packet.hasExtension(Push.class)) {
345 this.getManager(UnifiedPushManager.class).push(packet);
346 } else {
347 this.connection.sendErrorFor(
348 packet, Error.Type.CANCEL, new Condition.FeatureNotImplemented());
349 }
350 }
351
352 private void acceptRequest(final Iq packet) {
353 if (packet.hasExtension(InfoQuery.class)) {
354 this.getManager(DiscoManager.class).handleInfoQuery(packet);
355 } else if (packet.hasExtension(Version.class)) {
356 this.getManager(DiscoManager.class).handleVersionRequest(packet);
357 } else if (packet.hasExtension(Time.class)) {
358 this.getManager(EntityTimeManager.class).request(packet);
359 } else if (packet.hasExtension(Ping.class)) {
360 this.getManager(PingManager.class).pong(packet);
361 } else {
362 this.connection.sendErrorFor(
363 packet, Error.Type.CANCEL, new Condition.FeatureNotImplemented());
364 }
365 }
366}