IqGenerator.java

  1package eu.siacs.conversations.generator;
  2
  3import android.os.Bundle;
  4import android.util.Base64;
  5import android.util.Log;
  6import eu.siacs.conversations.Config;
  7import eu.siacs.conversations.crypto.axolotl.AxolotlService;
  8import eu.siacs.conversations.services.MessageArchiveService;
  9import eu.siacs.conversations.services.XmppConnectionService;
 10import eu.siacs.conversations.xml.Element;
 11import eu.siacs.conversations.xml.Namespace;
 12import eu.siacs.conversations.xmpp.Jid;
 13import eu.siacs.conversations.xmpp.forms.Data;
 14import im.conversations.android.xmpp.model.stanza.Iq;
 15import java.security.cert.CertificateEncodingException;
 16import java.security.cert.X509Certificate;
 17import java.util.Set;
 18import org.whispersystems.libsignal.IdentityKey;
 19import org.whispersystems.libsignal.ecc.ECPublicKey;
 20import org.whispersystems.libsignal.state.PreKeyRecord;
 21import org.whispersystems.libsignal.state.SignedPreKeyRecord;
 22
 23public class IqGenerator extends AbstractGenerator {
 24
 25    public IqGenerator(final XmppConnectionService service) {
 26        super(service);
 27    }
 28
 29    protected Iq publish(final String node, final Element item, final Bundle options) {
 30        final var packet = new Iq(Iq.Type.SET);
 31        final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
 32        final Element publish = pubsub.addChild("publish");
 33        publish.setAttribute("node", node);
 34        publish.addChild(item);
 35        if (options != null) {
 36            final Element publishOptions = pubsub.addChild("publish-options");
 37            publishOptions.addChild(Data.create(Namespace.PUBSUB_PUBLISH_OPTIONS, options));
 38        }
 39        return packet;
 40    }
 41
 42    protected Iq publish(final String node, final Element item) {
 43        return publish(node, item, null);
 44    }
 45
 46    private Iq retrieve(String node, Element item) {
 47        final var packet = new Iq(Iq.Type.GET);
 48        final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
 49        final Element items = pubsub.addChild("items");
 50        items.setAttribute("node", node);
 51        if (item != null) {
 52            items.addChild(item);
 53        }
 54        return packet;
 55    }
 56
 57    public Iq retrieveDeviceIds(final Jid to) {
 58        final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
 59        if (to != null) {
 60            packet.setTo(to);
 61        }
 62        return packet;
 63    }
 64
 65    public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) {
 66        final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
 67        packet.setTo(to);
 68        return packet;
 69    }
 70
 71    public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) {
 72        final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
 73        packet.setTo(to);
 74        return packet;
 75    }
 76
 77    public Iq publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
 78        final Element item = new Element("item");
 79        item.setAttribute("id", "current");
 80        final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
 81        for (Integer id : ids) {
 82            final Element device = new Element("device");
 83            device.setAttribute("id", id);
 84            list.addChild(device);
 85        }
 86        return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
 87    }
 88
 89    public Iq publishBundles(
 90            final SignedPreKeyRecord signedPreKeyRecord,
 91            final IdentityKey identityKey,
 92            final Set<PreKeyRecord> preKeyRecords,
 93            final int deviceId,
 94            Bundle publishOptions) {
 95        final Element item = new Element("item");
 96        item.setAttribute("id", "current");
 97        final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
 98        final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
 99        signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
100        ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
101        signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.NO_WRAP));
102        final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
103        signedPreKeySignature.setContent(
104                Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP));
105        final Element identityKeyElement = bundle.addChild("identityKey");
106        identityKeyElement.setContent(
107                Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP));
108
109        final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
110        for (PreKeyRecord preKeyRecord : preKeyRecords) {
111            final Element prekey = prekeys.addChild("preKeyPublic");
112            prekey.setAttribute("preKeyId", preKeyRecord.getId());
113            prekey.setContent(
114                    Base64.encodeToString(
115                            preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP));
116        }
117
118        return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
119    }
120
121    public Iq publishVerification(
122            byte[] signature, X509Certificate[] certificates, final int deviceId) {
123        final Element item = new Element("item");
124        item.setAttribute("id", "current");
125        final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
126        final Element chain = verification.addChild("chain");
127        for (int i = 0; i < certificates.length; ++i) {
128            try {
129                Element certificate = chain.addChild("certificate");
130                certificate.setContent(
131                        Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP));
132                certificate.setAttribute("index", i);
133            } catch (CertificateEncodingException e) {
134                Log.d(Config.LOGTAG, "could not encode certificate");
135            }
136        }
137        verification
138                .addChild("signature")
139                .setContent(Base64.encodeToString(signature, Base64.NO_WRAP));
140        return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
141    }
142
143    public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
144        final Iq packet = new Iq(Iq.Type.SET);
145        final Element query = packet.addChild("query", mam.version.namespace);
146        query.setAttribute("queryid", mam.getQueryId());
147        final Data data = new Data();
148        data.setFormType(mam.version.namespace);
149        if (mam.muc()) {
150            packet.setTo(mam.getWith());
151        } else if (mam.getWith() != null) {
152            data.put("with", mam.getWith().toString());
153        }
154        final long start = mam.getStart();
155        final long end = mam.getEnd();
156        if (start != 0) {
157            data.put("start", getTimestamp(start));
158        }
159        if (end != 0) {
160            data.put("end", getTimestamp(end));
161        }
162        data.submit();
163        query.addChild(data);
164        Element set = query.addChild("set", "http://jabber.org/protocol/rsm");
165        if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
166            set.addChild("before").setContent(mam.getReference());
167        } else if (mam.getReference() != null) {
168            set.addChild("after").setContent(mam.getReference());
169        }
170        set.addChild("max").setContent(String.valueOf(Config.PAGE_SIZE));
171        return packet;
172    }
173
174    public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId) {
175        return pushTokenToAppServer(appServer, token, deviceId, null);
176    }
177
178    public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
179        final Iq packet = new Iq(Iq.Type.SET);
180        packet.setTo(appServer);
181        final Element command = packet.addChild("command", Namespace.COMMANDS);
182        command.setAttribute("node", "register-push-fcm");
183        command.setAttribute("action", "execute");
184        final Data data = new Data();
185        data.put("token", token);
186        data.put("android-id", deviceId);
187        if (muc != null) {
188            data.put("muc", muc.toString());
189        }
190        data.submit();
191        command.addChild(data);
192        return packet;
193    }
194
195    public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
196        final Iq packet = new Iq(Iq.Type.SET);
197        packet.setTo(appServer);
198        final Element command = packet.addChild("command", Namespace.COMMANDS);
199        command.setAttribute("node", "unregister-push-fcm");
200        command.setAttribute("action", "execute");
201        final Data data = new Data();
202        data.put("channel", channel);
203        data.put("android-id", deviceId);
204        data.submit();
205        command.addChild(data);
206        return packet;
207    }
208
209    public Iq enablePush(final Jid jid, final String node, final String secret) {
210        final Iq packet = new Iq(Iq.Type.SET);
211        Element enable = packet.addChild("enable", Namespace.PUSH);
212        enable.setAttribute("jid", jid);
213        enable.setAttribute("node", node);
214        if (secret != null) {
215            Data data = new Data();
216            data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
217            data.put("secret", secret);
218            data.submit();
219            enable.addChild(data);
220        }
221        return packet;
222    }
223
224    public Iq disablePush(final Jid jid, final String node) {
225        Iq packet = new Iq(Iq.Type.SET);
226        Element disable = packet.addChild("disable", Namespace.PUSH);
227        disable.setAttribute("jid", jid);
228        disable.setAttribute("node", node);
229        return packet;
230    }
231
232    public Iq requestPubsubConfiguration(Jid jid, String node) {
233        return pubsubConfiguration(jid, node, null);
234    }
235
236    public Iq publishPubsubConfiguration(Jid jid, String node, Data data) {
237        return pubsubConfiguration(jid, node, data);
238    }
239
240    private Iq pubsubConfiguration(Jid jid, String node, Data data) {
241        final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET);
242        packet.setTo(jid);
243        Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
244        Element configure = pubsub.addChild("configure").setAttribute("node", node);
245        if (data != null) {
246            configure.addChild(data);
247        }
248        return packet;
249    }
250}