001/**
002 *
003 * Copyright 2017 Paul Schaub
004 *
005 * This file is part of smack-omemo-signal.
006 *
007 * smack-omemo-signal is free software; you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License as published by
009 * the Free Software Foundation; either version 3 of the License, or
010 * (at your option) any later version.
011 *
012 * This program is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with this program; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
020 */
021package org.jivesoftware.smackx.omemo.signal;
022
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.List;
026import java.util.TreeMap;
027import java.util.logging.Level;
028import java.util.logging.Logger;
029
030import org.jivesoftware.smackx.omemo.OmemoManager;
031import org.jivesoftware.smackx.omemo.OmemoStore;
032import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
033import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
034
035import org.jxmpp.jid.BareJid;
036import org.jxmpp.jid.impl.JidCreate;
037import org.jxmpp.stringprep.XmppStringprepException;
038import org.whispersystems.libsignal.IdentityKey;
039import org.whispersystems.libsignal.IdentityKeyPair;
040import org.whispersystems.libsignal.InvalidKeyIdException;
041import org.whispersystems.libsignal.SessionCipher;
042import org.whispersystems.libsignal.SignalProtocolAddress;
043import org.whispersystems.libsignal.ecc.ECPublicKey;
044import org.whispersystems.libsignal.state.IdentityKeyStore;
045import org.whispersystems.libsignal.state.PreKeyBundle;
046import org.whispersystems.libsignal.state.PreKeyRecord;
047import org.whispersystems.libsignal.state.PreKeyStore;
048import org.whispersystems.libsignal.state.SessionRecord;
049import org.whispersystems.libsignal.state.SessionStore;
050import org.whispersystems.libsignal.state.SignedPreKeyRecord;
051import org.whispersystems.libsignal.state.SignedPreKeyStore;
052
053/**
054 * Class that adapts libsignal-protocol-java's Store classes to the OmemoStore class.
055 *
056 * @author Paul Schaub
057 */
058public class SignalOmemoStoreConnector
059        implements IdentityKeyStore, SessionStore, PreKeyStore, SignedPreKeyStore {
060
061    private static final Logger LOGGER = Logger.getLogger(SignalOmemoStoreConnector.class.getName());
062
063    private final OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
064            SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
065            omemoStore;
066    private final OmemoManager omemoManager;
067
068    public SignalOmemoStoreConnector(OmemoManager omemoManager, OmemoStore<IdentityKeyPair,
069            IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey,
070            PreKeyBundle, SessionCipher> store) {
071        this.omemoManager = omemoManager;
072        this.omemoStore = store;
073    }
074
075    OmemoDevice getOurDevice() {
076        return omemoManager.getOwnDevice();
077    }
078
079    @Override
080    public IdentityKeyPair getIdentityKeyPair() {
081        try {
082            return omemoStore.loadOmemoIdentityKeyPair(getOurDevice());
083        } catch (CorruptedOmemoKeyException | IOException e) {
084            LOGGER.log(Level.SEVERE, "IdentityKeyPair seems to be invalid.", e);
085            return null;
086        }
087    }
088
089    /**
090     * The OMEMO protocol does not make use of a local registration ID, so we can simply return 0 here.
091     *
092     * @return local registration id.
093     */
094    @Override
095    public int getLocalRegistrationId() {
096        return 0;
097    }
098
099    @Override
100    public boolean saveIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
101        OmemoDevice device;
102        try {
103            device = asOmemoDevice(signalProtocolAddress);
104        } catch (XmppStringprepException e) {
105            throw new AssertionError(e);
106        }
107
108        try {
109            omemoStore.storeOmemoIdentityKey(getOurDevice(), device, identityKey);
110        } catch (IOException e) {
111            throw new IllegalStateException(e);
112        }
113        return true;
114    }
115
116    @Override
117    public boolean isTrustedIdentity(SignalProtocolAddress signalProtocolAddress,
118                                     IdentityKey identityKey,
119                                     Direction direction) {
120        // Disable internal trust management. Instead we use OmemoStore.isTrustedOmemoIdentity() before encrypting
121        // for a recipient.
122        return true;
123    }
124
125    @Override
126    public IdentityKey getIdentity(SignalProtocolAddress address) {
127        OmemoDevice device;
128        try {
129            device = asOmemoDevice(address);
130        } catch (XmppStringprepException e) {
131            throw new AssertionError(e);
132        }
133
134        try {
135            return omemoStore.loadOmemoIdentityKey(getOurDevice(), device);
136        } catch (IOException | CorruptedOmemoKeyException e) {
137            throw new IllegalStateException(e);
138        }
139    }
140
141    @Override
142    public PreKeyRecord loadPreKey(int i) throws InvalidKeyIdException {
143        PreKeyRecord preKey;
144        try {
145            preKey = omemoStore.loadOmemoPreKey(getOurDevice(), i);
146        } catch (IOException e) {
147            throw new IllegalStateException(e);
148        }
149
150        if (preKey == null) {
151            throw new InvalidKeyIdException("No PreKey with Id " + i + " found.");
152        }
153
154        return preKey;
155    }
156
157    @Override
158    public void storePreKey(int i, PreKeyRecord preKeyRecord) {
159        try {
160            omemoStore.storeOmemoPreKey(getOurDevice(), i, preKeyRecord);
161        } catch (IOException e) {
162            throw new IllegalStateException(e);
163        }
164    }
165
166    @Override
167    public boolean containsPreKey(int i) {
168        try {
169            return loadPreKey(i) != null;
170        } catch (InvalidKeyIdException e) {
171            return false;
172        }
173    }
174
175    @Override
176    public void removePreKey(int i) {
177        omemoStore.removeOmemoPreKey(getOurDevice(), i);
178    }
179
180    @Override
181    public SessionRecord loadSession(SignalProtocolAddress signalProtocolAddress) {
182        OmemoDevice device;
183        try {
184            device = asOmemoDevice(signalProtocolAddress);
185        } catch (XmppStringprepException e) {
186            throw new AssertionError(e);
187        }
188
189        SessionRecord record;
190        try {
191            record = omemoStore.loadRawSession(getOurDevice(), device);
192        } catch (IOException e) {
193            throw new IllegalStateException(e);
194        }
195
196        if (record != null) {
197            return record;
198        } else {
199            return new SessionRecord();
200        }
201    }
202
203    @Override
204    public List<Integer> getSubDeviceSessions(String s) {
205        BareJid jid;
206        try {
207            jid = JidCreate.bareFrom(s);
208        } catch (XmppStringprepException e) {
209            throw new AssertionError(e);
210        }
211
212        try {
213            return new ArrayList<>(omemoStore.loadAllRawSessionsOf(getOurDevice(), jid).keySet());
214        } catch (IOException e) {
215            throw new IllegalStateException(e);
216        }
217    }
218
219    @Override
220    public void storeSession(SignalProtocolAddress signalProtocolAddress, SessionRecord sessionRecord) {
221        OmemoDevice device;
222        try {
223            device = asOmemoDevice(signalProtocolAddress);
224        } catch (XmppStringprepException e) {
225            throw new AssertionError(e);
226        }
227
228        try {
229            omemoStore.storeRawSession(getOurDevice(), device, sessionRecord);
230        } catch (IOException e) {
231            throw new IllegalStateException(e);
232        }
233    }
234
235    @Override
236    public boolean containsSession(SignalProtocolAddress signalProtocolAddress) {
237        OmemoDevice device;
238        try {
239            device = asOmemoDevice(signalProtocolAddress);
240        } catch (XmppStringprepException e) {
241            throw new AssertionError(e);
242        }
243
244        return omemoStore.containsRawSession(getOurDevice(), device);
245    }
246
247    @Override
248    public void deleteSession(SignalProtocolAddress signalProtocolAddress) {
249        OmemoDevice device;
250        try {
251            device = asOmemoDevice(signalProtocolAddress);
252        } catch (XmppStringprepException e) {
253            throw new AssertionError(e);
254        }
255
256        omemoStore.removeRawSession(getOurDevice(), device);
257    }
258
259    @Override
260    public void deleteAllSessions(String s) {
261        BareJid jid;
262        try {
263            jid = JidCreate.bareFrom(s);
264        } catch (XmppStringprepException e) {
265            throw new AssertionError(e);
266        }
267
268        omemoStore.removeAllRawSessionsOf(getOurDevice(), jid);
269    }
270
271    @Override
272    public SignedPreKeyRecord loadSignedPreKey(int i) throws InvalidKeyIdException {
273        SignedPreKeyRecord signedPreKeyRecord;
274        try {
275            signedPreKeyRecord = omemoStore.loadOmemoSignedPreKey(getOurDevice(), i);
276        } catch (IOException e) {
277            throw new IllegalStateException(e);
278        }
279        if (signedPreKeyRecord == null) {
280            throw new InvalidKeyIdException("No signed preKey with id " + i + " found.");
281        }
282        return signedPreKeyRecord;
283    }
284
285    @Override
286    public List<SignedPreKeyRecord> loadSignedPreKeys() {
287
288        TreeMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap;
289        try {
290            signedPreKeyRecordHashMap = omemoStore.loadOmemoSignedPreKeys(getOurDevice());
291        } catch (IOException e) {
292            throw new IllegalStateException(e);
293        }
294        return new ArrayList<>(signedPreKeyRecordHashMap.values());
295    }
296
297    @Override
298    public void storeSignedPreKey(int i, SignedPreKeyRecord signedPreKeyRecord) {
299        try {
300            omemoStore.storeOmemoSignedPreKey(getOurDevice(), i, signedPreKeyRecord);
301        } catch (IOException e) {
302            throw new IllegalStateException(e);
303        }
304    }
305
306    @Override
307    public boolean containsSignedPreKey(int i) {
308        try {
309            return loadSignedPreKey(i) != null;
310        } catch (InvalidKeyIdException e) {
311            LOGGER.log(Level.WARNING, "containsSignedPreKey has failed: " + e.getMessage());
312            return false;
313        }
314    }
315
316    @Override
317    public void removeSignedPreKey(int i) {
318        omemoStore.removeOmemoSignedPreKey(getOurDevice(), i);
319    }
320
321    private static OmemoDevice asOmemoDevice(SignalProtocolAddress address) throws XmppStringprepException {
322        return new OmemoDevice(JidCreate.bareFrom(address.getName()), address.getDeviceId());
323    }
324
325    public static SignalProtocolAddress asAddress(OmemoDevice device) {
326        return new SignalProtocolAddress(device.getJid().toString(), device.getDeviceId());
327    }
328}