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}