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}