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 PreKeyRecord loadPreKey(int i) throws InvalidKeyIdException {
127        PreKeyRecord preKey;
128        try {
129            preKey = omemoStore.loadOmemoPreKey(getOurDevice(), i);
130        } catch (IOException e) {
131            throw new IllegalStateException(e);
132        }
133
134        if (preKey == null) {
135            throw new InvalidKeyIdException("No PreKey with Id " + i + " found.");
136        }
137
138        return preKey;
139    }
140
141    @Override
142    public void storePreKey(int i, PreKeyRecord preKeyRecord) {
143        try {
144            omemoStore.storeOmemoPreKey(getOurDevice(), i, preKeyRecord);
145        } catch (IOException e) {
146            throw new IllegalStateException(e);
147        }
148    }
149
150    @Override
151    public boolean containsPreKey(int i) {
152        try {
153            return loadPreKey(i) != null;
154        } catch (InvalidKeyIdException e) {
155            return false;
156        }
157    }
158
159    @Override
160    public void removePreKey(int i) {
161        omemoStore.removeOmemoPreKey(getOurDevice(), i);
162    }
163
164    @Override
165    public SessionRecord loadSession(SignalProtocolAddress signalProtocolAddress) {
166        OmemoDevice device;
167        try {
168            device = asOmemoDevice(signalProtocolAddress);
169        } catch (XmppStringprepException e) {
170            throw new AssertionError(e);
171        }
172
173        SessionRecord record;
174        try {
175            record = omemoStore.loadRawSession(getOurDevice(), device);
176        } catch (IOException e) {
177            throw new IllegalStateException(e);
178        }
179
180        if (record != null) {
181            return record;
182        } else {
183            return new SessionRecord();
184        }
185    }
186
187    @Override
188    public List<Integer> getSubDeviceSessions(String s) {
189        BareJid jid;
190        try {
191            jid = JidCreate.bareFrom(s);
192        } catch (XmppStringprepException e) {
193            throw new AssertionError(e);
194        }
195
196        try {
197            return new ArrayList<>(omemoStore.loadAllRawSessionsOf(getOurDevice(), jid).keySet());
198        } catch (IOException e) {
199            throw new IllegalStateException(e);
200        }
201    }
202
203    @Override
204    public void storeSession(SignalProtocolAddress signalProtocolAddress, SessionRecord sessionRecord) {
205        OmemoDevice device;
206        try {
207            device = asOmemoDevice(signalProtocolAddress);
208        } catch (XmppStringprepException e) {
209            throw new AssertionError(e);
210        }
211
212        try {
213            omemoStore.storeRawSession(getOurDevice(), device, sessionRecord);
214        } catch (IOException e) {
215            throw new IllegalStateException(e);
216        }
217    }
218
219    @Override
220    public boolean containsSession(SignalProtocolAddress signalProtocolAddress) {
221        OmemoDevice device;
222        try {
223            device = asOmemoDevice(signalProtocolAddress);
224        } catch (XmppStringprepException e) {
225            throw new AssertionError(e);
226        }
227
228        return omemoStore.containsRawSession(getOurDevice(), device);
229    }
230
231    @Override
232    public void deleteSession(SignalProtocolAddress signalProtocolAddress) {
233        OmemoDevice device;
234        try {
235            device = asOmemoDevice(signalProtocolAddress);
236        } catch (XmppStringprepException e) {
237            throw new AssertionError(e);
238        }
239
240        omemoStore.removeRawSession(getOurDevice(), device);
241    }
242
243    @Override
244    public void deleteAllSessions(String s) {
245        BareJid jid;
246        try {
247            jid = JidCreate.bareFrom(s);
248        } catch (XmppStringprepException e) {
249            throw new AssertionError(e);
250        }
251
252        omemoStore.removeAllRawSessionsOf(getOurDevice(), jid);
253    }
254
255    @Override
256    public SignedPreKeyRecord loadSignedPreKey(int i) throws InvalidKeyIdException {
257        SignedPreKeyRecord signedPreKeyRecord;
258        try {
259            signedPreKeyRecord = omemoStore.loadOmemoSignedPreKey(getOurDevice(), i);
260        } catch (IOException e) {
261            throw new IllegalStateException(e);
262        }
263        if (signedPreKeyRecord == null) {
264            throw new InvalidKeyIdException("No signed preKey with id " + i + " found.");
265        }
266        return signedPreKeyRecord;
267    }
268
269    @Override
270    public List<SignedPreKeyRecord> loadSignedPreKeys() {
271
272        TreeMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap;
273        try {
274            signedPreKeyRecordHashMap = omemoStore.loadOmemoSignedPreKeys(getOurDevice());
275        } catch (IOException e) {
276            throw new IllegalStateException(e);
277        }
278        return new ArrayList<>(signedPreKeyRecordHashMap.values());
279    }
280
281    @Override
282    public void storeSignedPreKey(int i, SignedPreKeyRecord signedPreKeyRecord) {
283        try {
284            omemoStore.storeOmemoSignedPreKey(getOurDevice(), i, signedPreKeyRecord);
285        } catch (IOException e) {
286            throw new IllegalStateException(e);
287        }
288    }
289
290    @Override
291    public boolean containsSignedPreKey(int i) {
292        try {
293            return loadSignedPreKey(i) != null;
294        } catch (InvalidKeyIdException e) {
295            LOGGER.log(Level.WARNING, "containsSignedPreKey has failed: " + e.getMessage());
296            return false;
297        }
298    }
299
300    @Override
301    public void removeSignedPreKey(int i) {
302        omemoStore.removeOmemoSignedPreKey(getOurDevice(), i);
303    }
304
305    private static OmemoDevice asOmemoDevice(SignalProtocolAddress address) throws XmppStringprepException {
306        return new OmemoDevice(JidCreate.bareFrom(address.getName()), address.getDeviceId());
307    }
308
309    public static SignalProtocolAddress asAddress(OmemoDevice device) {
310        return new SignalProtocolAddress(device.getJid().toString(), device.getDeviceId());
311    }
312}