2015-05-29 09:17:26 +00:00
package eu.siacs.conversations.crypto.axolotl ;
2015-07-05 20:53:34 +00:00
import android.support.annotation.NonNull ;
2015-06-29 12:22:26 +00:00
import android.support.annotation.Nullable ;
2015-05-29 09:17:26 +00:00
import android.util.Log ;
import org.whispersystems.libaxolotl.AxolotlAddress ;
2015-06-25 14:56:34 +00:00
import org.whispersystems.libaxolotl.DuplicateMessageException ;
2015-05-29 09:17:26 +00:00
import org.whispersystems.libaxolotl.IdentityKey ;
import org.whispersystems.libaxolotl.IdentityKeyPair ;
import org.whispersystems.libaxolotl.InvalidKeyException ;
import org.whispersystems.libaxolotl.InvalidKeyIdException ;
2015-06-25 14:56:34 +00:00
import org.whispersystems.libaxolotl.InvalidMessageException ;
import org.whispersystems.libaxolotl.InvalidVersionException ;
import org.whispersystems.libaxolotl.LegacyMessageException ;
import org.whispersystems.libaxolotl.NoSessionException ;
import org.whispersystems.libaxolotl.SessionBuilder ;
import org.whispersystems.libaxolotl.SessionCipher ;
import org.whispersystems.libaxolotl.UntrustedIdentityException ;
2015-05-29 09:17:26 +00:00
import org.whispersystems.libaxolotl.ecc.Curve ;
import org.whispersystems.libaxolotl.ecc.ECKeyPair ;
2015-06-25 14:56:34 +00:00
import org.whispersystems.libaxolotl.ecc.ECPublicKey ;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage ;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage ;
import org.whispersystems.libaxolotl.protocol.WhisperMessage ;
2015-05-29 09:17:26 +00:00
import org.whispersystems.libaxolotl.state.AxolotlStore ;
2015-06-25 14:56:34 +00:00
import org.whispersystems.libaxolotl.state.PreKeyBundle ;
2015-05-29 09:17:26 +00:00
import org.whispersystems.libaxolotl.state.PreKeyRecord ;
import org.whispersystems.libaxolotl.state.SessionRecord ;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord ;
import org.whispersystems.libaxolotl.util.KeyHelper ;
2015-06-29 12:18:11 +00:00
import java.util.Arrays ;
2015-05-29 09:17:26 +00:00
import java.util.HashMap ;
2015-06-25 14:56:34 +00:00
import java.util.HashSet ;
2015-05-29 09:17:26 +00:00
import java.util.List ;
import java.util.Map ;
2015-06-25 14:56:34 +00:00
import java.util.Random ;
import java.util.Set ;
2015-05-29 09:17:26 +00:00
import eu.siacs.conversations.Config ;
import eu.siacs.conversations.entities.Account ;
2015-06-25 14:56:34 +00:00
import eu.siacs.conversations.entities.Contact ;
2015-05-29 09:17:26 +00:00
import eu.siacs.conversations.entities.Conversation ;
2015-06-29 12:22:26 +00:00
import eu.siacs.conversations.entities.Message ;
2015-06-25 14:58:24 +00:00
import eu.siacs.conversations.parser.IqParser ;
2015-05-29 09:17:26 +00:00
import eu.siacs.conversations.services.XmppConnectionService ;
2015-06-29 12:22:26 +00:00
import eu.siacs.conversations.utils.SerialSingleThreadExecutor ;
2015-06-25 14:58:24 +00:00
import eu.siacs.conversations.xml.Element ;
import eu.siacs.conversations.xmpp.OnIqPacketReceived ;
2015-05-29 09:17:26 +00:00
import eu.siacs.conversations.xmpp.jid.InvalidJidException ;
import eu.siacs.conversations.xmpp.jid.Jid ;
2015-06-25 14:58:24 +00:00
import eu.siacs.conversations.xmpp.stanzas.IqPacket ;
2015-06-29 12:22:26 +00:00
import eu.siacs.conversations.xmpp.stanzas.MessagePacket ;
2015-05-29 09:17:26 +00:00
public class AxolotlService {
2015-06-26 13:41:02 +00:00
public static final String PEP_PREFIX = " eu.siacs.conversations.axolotl " ;
public static final String PEP_DEVICE_LIST = PEP_PREFIX + " .devicelist " ;
2015-06-29 12:18:11 +00:00
public static final String PEP_BUNDLES = PEP_PREFIX + " .bundles " ;
2015-07-08 15:44:24 +00:00
public static final String LOGPREFIX = " AxolotlService " ;
2015-06-29 12:18:11 +00:00
public static final int NUM_KEYS_TO_PUBLISH = 10 ;
2015-06-26 13:41:02 +00:00
private final Account account ;
private final XmppConnectionService mXmppConnectionService ;
private final SQLiteAxolotlStore axolotlStore ;
private final SessionMap sessions ;
2015-06-29 12:18:11 +00:00
private final Map < Jid , Set < Integer > > deviceIds ;
2015-07-03 11:31:14 +00:00
private final Map < String , MessagePacket > messageCache ;
2015-06-29 12:22:26 +00:00
private final FetchStatusMap fetchStatusMap ;
private final SerialSingleThreadExecutor executor ;
2015-06-26 13:41:02 +00:00
public static class SQLiteAxolotlStore implements AxolotlStore {
public static final String PREKEY_TABLENAME = " prekeys " ;
public static final String SIGNED_PREKEY_TABLENAME = " signed_prekeys " ;
public static final String SESSION_TABLENAME = " sessions " ;
2015-06-29 11:53:39 +00:00
public static final String IDENTITIES_TABLENAME = " identities " ;
2015-06-26 13:41:02 +00:00
public static final String ACCOUNT = " account " ;
public static final String DEVICE_ID = " device_id " ;
public static final String ID = " id " ;
public static final String KEY = " key " ;
2015-07-09 12:23:17 +00:00
public static final String FINGERPRINT = " fingerprint " ;
2015-06-26 13:41:02 +00:00
public static final String NAME = " name " ;
public static final String TRUSTED = " trusted " ;
2015-06-29 11:53:39 +00:00
public static final String OWN = " ownkey " ;
2015-06-26 13:41:02 +00:00
public static final String JSONKEY_REGISTRATION_ID = " axolotl_reg_id " ;
public static final String JSONKEY_CURRENT_PREKEY_ID = " axolotl_cur_prekey_id " ;
private final Account account ;
private final XmppConnectionService mXmppConnectionService ;
2015-06-29 11:53:39 +00:00
private IdentityKeyPair identityKeyPair ;
2015-07-10 00:18:01 +00:00
private int localRegistrationId ;
2015-06-26 13:41:02 +00:00
private int currentPreKeyId = 0 ;
2015-07-09 12:23:17 +00:00
public enum Trust {
UNDECIDED , // 0
TRUSTED ,
2015-07-20 20:18:24 +00:00
UNTRUSTED ,
COMPROMISED ;
2015-07-09 12:23:17 +00:00
public String toString ( ) {
switch ( this ) {
case UNDECIDED :
return " Trust undecided " ;
case TRUSTED :
return " Trusted " ;
case UNTRUSTED :
default :
return " Untrusted " ;
}
}
2015-07-19 16:36:28 +00:00
public static Trust fromBoolean ( Boolean trusted ) {
return trusted ? TRUSTED : UNTRUSTED ;
}
2015-07-09 12:23:17 +00:00
} ;
2015-06-26 13:41:02 +00:00
private static IdentityKeyPair generateIdentityKeyPair ( ) {
2015-07-08 15:44:24 +00:00
Log . i ( Config . LOGTAG , AxolotlService . LOGPREFIX + " : " + " Generating axolotl IdentityKeyPair... " ) ;
2015-06-26 13:41:02 +00:00
ECKeyPair identityKeyPairKeys = Curve . generateKeyPair ( ) ;
IdentityKeyPair ownKey = new IdentityKeyPair ( new IdentityKey ( identityKeyPairKeys . getPublicKey ( ) ) ,
identityKeyPairKeys . getPrivateKey ( ) ) ;
return ownKey ;
}
private static int generateRegistrationId ( ) {
2015-07-08 15:44:24 +00:00
Log . i ( Config . LOGTAG , AxolotlService . LOGPREFIX + " : " + " Generating axolotl registration ID... " ) ;
2015-07-08 16:13:49 +00:00
int reg_id = KeyHelper . generateRegistrationId ( true ) ;
2015-06-26 13:41:02 +00:00
return reg_id ;
}
public SQLiteAxolotlStore ( Account account , XmppConnectionService service ) {
this . account = account ;
this . mXmppConnectionService = service ;
this . localRegistrationId = loadRegistrationId ( ) ;
this . currentPreKeyId = loadCurrentPreKeyId ( ) ;
2015-06-29 11:53:39 +00:00
for ( SignedPreKeyRecord record : loadSignedPreKeys ( ) ) {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Got Axolotl signed prekey record: " + record . getId ( ) ) ;
2015-06-26 13:41:02 +00:00
}
}
public int getCurrentPreKeyId ( ) {
return currentPreKeyId ;
}
// --------------------------------------
// IdentityKeyStore
// --------------------------------------
private IdentityKeyPair loadIdentityKeyPair ( ) {
2015-06-29 11:53:39 +00:00
String ownName = account . getJid ( ) . toBareJid ( ) . toString ( ) ;
IdentityKeyPair ownKey = mXmppConnectionService . databaseBackend . loadOwnIdentityKeyPair ( account ,
ownName ) ;
if ( ownKey ! = null ) {
return ownKey ;
} else {
2015-07-08 15:44:24 +00:00
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Could not retrieve axolotl key for account " + ownName ) ;
2015-06-26 13:41:02 +00:00
ownKey = generateIdentityKeyPair ( ) ;
2015-06-29 11:53:39 +00:00
mXmppConnectionService . databaseBackend . storeOwnIdentityKeyPair ( account , ownName , ownKey ) ;
}
2015-06-26 13:41:02 +00:00
return ownKey ;
}
private int loadRegistrationId ( ) {
2015-07-10 00:18:01 +00:00
return loadRegistrationId ( false ) ;
}
private int loadRegistrationId ( boolean regenerate ) {
2015-06-26 13:41:02 +00:00
String regIdString = this . account . getKey ( JSONKEY_REGISTRATION_ID ) ;
int reg_id ;
2015-07-10 00:18:01 +00:00
if ( ! regenerate & & regIdString ! = null ) {
2015-06-26 13:41:02 +00:00
reg_id = Integer . valueOf ( regIdString ) ;
} else {
2015-07-08 15:44:24 +00:00
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Could not retrieve axolotl registration id for account " + account . getJid ( ) ) ;
2015-06-26 13:41:02 +00:00
reg_id = generateRegistrationId ( ) ;
2015-06-29 11:53:39 +00:00
boolean success = this . account . setKey ( JSONKEY_REGISTRATION_ID , Integer . toString ( reg_id ) ) ;
if ( success ) {
2015-06-26 13:41:02 +00:00
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
} else {
2015-07-08 15:44:24 +00:00
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Failed to write new key to the database! " ) ;
2015-06-26 13:41:02 +00:00
}
}
return reg_id ;
}
private int loadCurrentPreKeyId ( ) {
String regIdString = this . account . getKey ( JSONKEY_CURRENT_PREKEY_ID ) ;
int reg_id ;
if ( regIdString ! = null ) {
reg_id = Integer . valueOf ( regIdString ) ;
} else {
2015-07-08 15:44:24 +00:00
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Could not retrieve current prekey id for account " + account . getJid ( ) ) ;
2015-06-26 13:41:02 +00:00
reg_id = 0 ;
}
return reg_id ;
}
2015-07-07 17:36:22 +00:00
public void regenerate ( ) {
mXmppConnectionService . databaseBackend . wipeAxolotlDb ( account ) ;
account . setKey ( JSONKEY_CURRENT_PREKEY_ID , Integer . toString ( 0 ) ) ;
identityKeyPair = loadIdentityKeyPair ( ) ;
2015-07-10 00:18:01 +00:00
localRegistrationId = loadRegistrationId ( true ) ;
2015-07-07 17:36:22 +00:00
currentPreKeyId = 0 ;
mXmppConnectionService . updateAccountUi ( ) ;
}
2015-06-26 13:41:02 +00:00
/ * *
* Get the local client ' s identity key pair .
*
* @return The local client ' s persistent identity key pair .
* /
@Override
public IdentityKeyPair getIdentityKeyPair ( ) {
2015-06-29 11:53:39 +00:00
if ( identityKeyPair = = null ) {
identityKeyPair = loadIdentityKeyPair ( ) ;
}
2015-06-26 13:41:02 +00:00
return identityKeyPair ;
}
/ * *
* Return the local client ' s registration ID .
* < p / >
* Clients should maintain a registration ID , a random number
* between 1 and 16380 that ' s generated once at install time .
*
* @return the local client ' s registration ID .
* /
@Override
public int getLocalRegistrationId ( ) {
return localRegistrationId ;
}
/ * *
* Save a remote client ' s identity key
* < p / >
* Store a remote client ' s identity key as trusted .
*
* @param name The name of the remote client .
* @param identityKey The remote client ' s identity key .
* /
@Override
public void saveIdentity ( String name , IdentityKey identityKey ) {
2015-06-29 11:53:39 +00:00
if ( ! mXmppConnectionService . databaseBackend . loadIdentityKeys ( account , name ) . contains ( identityKey ) ) {
mXmppConnectionService . databaseBackend . storeIdentityKey ( account , name , identityKey ) ;
2015-06-26 13:41:02 +00:00
}
}
/ * *
* Verify a remote client ' s identity key .
* < p / >
* Determine whether a remote client ' s identity is trusted . Convention is
* that the TextSecure protocol is ' trust on first use . ' This means that
* an identity key is considered ' trusted ' if there is no entry for the recipient
* in the local store , or if it matches the saved key for a recipient in the local
* store . Only if it mismatches an entry in the local store is it considered
* ' untrusted . '
*
* @param name The name of the remote client .
* @param identityKey The identity key to verify .
* @return true if trusted , false if untrusted .
* /
@Override
public boolean isTrustedIdentity ( String name , IdentityKey identityKey ) {
2015-07-05 20:10:43 +00:00
return true ;
2015-06-26 13:41:02 +00:00
}
2015-07-15 14:32:42 +00:00
public Trust getFingerprintTrust ( String fingerprint ) {
return mXmppConnectionService . databaseBackend . isIdentityKeyTrusted ( account , fingerprint ) ;
2015-07-09 12:23:17 +00:00
}
2015-07-15 14:32:42 +00:00
public void setFingerprintTrust ( String fingerprint , Trust trust ) {
mXmppConnectionService . databaseBackend . setIdentityKeyTrust ( account , fingerprint , trust ) ;
2015-07-09 12:23:17 +00:00
}
2015-07-20 20:35:07 +00:00
public Set < IdentityKey > getContactUndecidedKeys ( String bareJid , Trust trust ) {
return mXmppConnectionService . databaseBackend . loadIdentityKeys ( account , bareJid , trust ) ;
2015-07-19 16:36:28 +00:00
}
2015-07-20 12:56:41 +00:00
public long getContactNumTrustedKeys ( String bareJid ) {
return mXmppConnectionService . databaseBackend . numTrustedKeys ( account , bareJid ) ;
}
2015-06-26 13:41:02 +00:00
// --------------------------------------
// SessionStore
// --------------------------------------
/ * *
* Returns a copy of the { @link SessionRecord } corresponding to the recipientId + deviceId tuple ,
* or a new SessionRecord if one does not currently exist .
* < p / >
* It is important that implementations return a copy of the current durable information . The
* returned SessionRecord may be modified , but those changes should not have an effect on the
* durable session state ( what is returned by subsequent calls to this method ) without the
* store method being called here first .
*
* @param address The name and device ID of the remote client .
* @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple , or
* a new SessionRecord if one does not currently exist .
* /
@Override
public SessionRecord loadSession ( AxolotlAddress address ) {
SessionRecord session = mXmppConnectionService . databaseBackend . loadSession ( this . account , address ) ;
2015-06-29 11:55:45 +00:00
return ( session ! = null ) ? session : new SessionRecord ( ) ;
2015-06-26 13:41:02 +00:00
}
/ * *
* Returns all known devices with active sessions for a recipient
*
* @param name the name of the client .
* @return all known sub - devices with active sessions .
* /
@Override
public List < Integer > getSubDeviceSessions ( String name ) {
return mXmppConnectionService . databaseBackend . getSubDeviceSessions ( account ,
2015-06-29 11:55:45 +00:00
new AxolotlAddress ( name , 0 ) ) ;
2015-06-26 13:41:02 +00:00
}
/ * *
* Commit to storage the { @link SessionRecord } for a given recipientId + deviceId tuple .
*
* @param address the address of the remote client .
* @param record the current SessionRecord for the remote client .
* /
@Override
public void storeSession ( AxolotlAddress address , SessionRecord record ) {
mXmppConnectionService . databaseBackend . storeSession ( account , address , record ) ;
}
/ * *
* Determine whether there is a committed { @link SessionRecord } for a recipientId + deviceId tuple .
*
* @param address the address of the remote client .
* @return true if a { @link SessionRecord } exists , false otherwise .
* /
@Override
public boolean containsSession ( AxolotlAddress address ) {
return mXmppConnectionService . databaseBackend . containsSession ( account , address ) ;
}
/ * *
* Remove a { @link SessionRecord } for a recipientId + deviceId tuple .
*
* @param address the address of the remote client .
* /
@Override
public void deleteSession ( AxolotlAddress address ) {
mXmppConnectionService . databaseBackend . deleteSession ( account , address ) ;
}
/ * *
* Remove the { @link SessionRecord } s corresponding to all devices of a recipientId .
*
* @param name the name of the remote client .
* /
@Override
public void deleteAllSessions ( String name ) {
mXmppConnectionService . databaseBackend . deleteAllSessions ( account ,
new AxolotlAddress ( name , 0 ) ) ;
}
// --------------------------------------
// PreKeyStore
// --------------------------------------
/ * *
* Load a local PreKeyRecord .
*
* @param preKeyId the ID of the local PreKeyRecord .
* @return the corresponding PreKeyRecord .
* @throws InvalidKeyIdException when there is no corresponding PreKeyRecord .
* /
@Override
public PreKeyRecord loadPreKey ( int preKeyId ) throws InvalidKeyIdException {
PreKeyRecord record = mXmppConnectionService . databaseBackend . loadPreKey ( account , preKeyId ) ;
2015-06-29 11:55:45 +00:00
if ( record = = null ) {
2015-06-26 13:41:02 +00:00
throw new InvalidKeyIdException ( " No such PreKeyRecord: " + preKeyId ) ;
}
return record ;
}
/ * *
* Store a local PreKeyRecord .
*
* @param preKeyId the ID of the PreKeyRecord to store .
* @param record the PreKeyRecord .
* /
@Override
public void storePreKey ( int preKeyId , PreKeyRecord record ) {
mXmppConnectionService . databaseBackend . storePreKey ( account , record ) ;
currentPreKeyId = preKeyId ;
2015-06-29 11:55:45 +00:00
boolean success = this . account . setKey ( JSONKEY_CURRENT_PREKEY_ID , Integer . toString ( preKeyId ) ) ;
if ( success ) {
2015-06-26 13:41:02 +00:00
mXmppConnectionService . databaseBackend . updateAccount ( account ) ;
} else {
2015-07-08 15:44:24 +00:00
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Failed to write new prekey id to the database! " ) ;
2015-06-26 13:41:02 +00:00
}
}
/ * *
* @param preKeyId A PreKeyRecord ID .
* @return true if the store has a record for the preKeyId , otherwise false .
* /
@Override
public boolean containsPreKey ( int preKeyId ) {
return mXmppConnectionService . databaseBackend . containsPreKey ( account , preKeyId ) ;
}
/ * *
* Delete a PreKeyRecord from local storage .
*
* @param preKeyId The ID of the PreKeyRecord to remove .
* /
@Override
public void removePreKey ( int preKeyId ) {
mXmppConnectionService . databaseBackend . deletePreKey ( account , preKeyId ) ;
}
// --------------------------------------
// SignedPreKeyStore
// --------------------------------------
/ * *
* Load a local SignedPreKeyRecord .
*
* @param signedPreKeyId the ID of the local SignedPreKeyRecord .
* @return the corresponding SignedPreKeyRecord .
* @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord .
* /
@Override
public SignedPreKeyRecord loadSignedPreKey ( int signedPreKeyId ) throws InvalidKeyIdException {
SignedPreKeyRecord record = mXmppConnectionService . databaseBackend . loadSignedPreKey ( account , signedPreKeyId ) ;
2015-06-29 11:55:45 +00:00
if ( record = = null ) {
2015-06-26 13:41:02 +00:00
throw new InvalidKeyIdException ( " No such SignedPreKeyRecord: " + signedPreKeyId ) ;
}
return record ;
}
/ * *
* Load all local SignedPreKeyRecords .
*
* @return All stored SignedPreKeyRecords .
* /
@Override
public List < SignedPreKeyRecord > loadSignedPreKeys ( ) {
return mXmppConnectionService . databaseBackend . loadSignedPreKeys ( account ) ;
}
/ * *
* Store a local SignedPreKeyRecord .
*
* @param signedPreKeyId the ID of the SignedPreKeyRecord to store .
* @param record the SignedPreKeyRecord .
* /
@Override
public void storeSignedPreKey ( int signedPreKeyId , SignedPreKeyRecord record ) {
mXmppConnectionService . databaseBackend . storeSignedPreKey ( account , record ) ;
}
/ * *
* @param signedPreKeyId A SignedPreKeyRecord ID .
* @return true if the store has a record for the signedPreKeyId , otherwise false .
* /
@Override
public boolean containsSignedPreKey ( int signedPreKeyId ) {
return mXmppConnectionService . databaseBackend . containsSignedPreKey ( account , signedPreKeyId ) ;
}
/ * *
* Delete a SignedPreKeyRecord from local storage .
*
* @param signedPreKeyId The ID of the SignedPreKeyRecord to remove .
* /
@Override
public void removeSignedPreKey ( int signedPreKeyId ) {
mXmppConnectionService . databaseBackend . deleteSignedPreKey ( account , signedPreKeyId ) ;
}
}
public static class XmppAxolotlSession {
2015-07-08 15:45:37 +00:00
private final SessionCipher cipher ;
2015-07-03 11:34:34 +00:00
private Integer preKeyId = null ;
2015-07-08 15:45:37 +00:00
private final SQLiteAxolotlStore sqLiteAxolotlStore ;
private final AxolotlAddress remoteAddress ;
2015-07-08 15:44:24 +00:00
private final Account account ;
2015-07-09 12:23:17 +00:00
private String fingerprint = null ;
public XmppAxolotlSession ( Account account , SQLiteAxolotlStore store , AxolotlAddress remoteAddress , String fingerprint ) {
this ( account , store , remoteAddress ) ;
this . fingerprint = fingerprint ;
}
2015-06-26 13:41:02 +00:00
2015-07-08 15:44:24 +00:00
public XmppAxolotlSession ( Account account , SQLiteAxolotlStore store , AxolotlAddress remoteAddress ) {
2015-06-26 13:41:02 +00:00
this . cipher = new SessionCipher ( store , remoteAddress ) ;
this . remoteAddress = remoteAddress ;
this . sqLiteAxolotlStore = store ;
2015-07-08 15:44:24 +00:00
this . account = account ;
2015-06-26 13:41:02 +00:00
}
2015-07-03 11:34:34 +00:00
public Integer getPreKeyId ( ) {
return preKeyId ;
}
public void resetPreKeyId ( ) {
2015-07-09 12:23:17 +00:00
2015-07-03 11:34:34 +00:00
preKeyId = null ;
}
2015-07-09 12:23:17 +00:00
public String getFingerprint ( ) {
return fingerprint ;
}
2015-07-20 20:18:24 +00:00
private SQLiteAxolotlStore . Trust getTrust ( ) {
return sqLiteAxolotlStore . getFingerprintTrust ( fingerprint ) ;
}
@Nullable
2015-06-26 13:41:02 +00:00
public byte [ ] processReceiving ( XmppAxolotlMessage . XmppAxolotlMessageHeader incomingHeader ) {
byte [ ] plaintext = null ;
2015-07-20 20:18:24 +00:00
switch ( getTrust ( ) ) {
case UNDECIDED :
case TRUSTED :
try {
try {
PreKeyWhisperMessage message = new PreKeyWhisperMessage ( incomingHeader . getContents ( ) ) ;
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " PreKeyWhisperMessage received, new session ID: " + message . getSignedPreKeyId ( ) + " / " + message . getPreKeyId ( ) ) ;
String fingerprint = message . getIdentityKey ( ) . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ;
if ( this . fingerprint ! = null & & ! this . fingerprint . equals ( fingerprint ) ) {
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Had session with fingerprint " + this . fingerprint + " , received message with fingerprint " + fingerprint ) ;
} else {
this . fingerprint = fingerprint ;
plaintext = cipher . decrypt ( message ) ;
if ( message . getPreKeyId ( ) . isPresent ( ) ) {
preKeyId = message . getPreKeyId ( ) . get ( ) ;
}
}
} catch ( InvalidMessageException | InvalidVersionException e ) {
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " WhisperMessage received " ) ;
WhisperMessage message = new WhisperMessage ( incomingHeader . getContents ( ) ) ;
plaintext = cipher . decrypt ( message ) ;
} catch ( InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e ) {
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Error decrypting axolotl header, " + e . getClass ( ) . getName ( ) + " : " + e . getMessage ( ) ) ;
2015-07-09 12:23:17 +00:00
}
2015-07-20 20:18:24 +00:00
} catch ( LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e ) {
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Error decrypting axolotl header, " + e . getClass ( ) . getName ( ) + " : " + e . getMessage ( ) ) ;
2015-07-03 11:34:34 +00:00
}
2015-07-20 20:18:24 +00:00
break ;
case COMPROMISED :
case UNTRUSTED :
default :
// ignore
break ;
2015-06-26 13:41:02 +00:00
}
return plaintext ;
}
2015-07-20 20:18:24 +00:00
@Nullable
public XmppAxolotlMessage . XmppAxolotlMessageHeader processSending ( @NonNull byte [ ] outgoingMessage ) {
SQLiteAxolotlStore . Trust trust = getTrust ( ) ;
if ( trust = = SQLiteAxolotlStore . Trust . TRUSTED ) {
CiphertextMessage ciphertextMessage = cipher . encrypt ( outgoingMessage ) ;
XmppAxolotlMessage . XmppAxolotlMessageHeader header =
new XmppAxolotlMessage . XmppAxolotlMessageHeader ( remoteAddress . getDeviceId ( ) ,
ciphertextMessage . serialize ( ) ) ;
return header ;
} else {
return null ;
}
2015-06-26 13:41:02 +00:00
}
}
private static class AxolotlAddressMap < T > {
2015-06-29 11:55:45 +00:00
protected Map < String , Map < Integer , T > > map ;
2015-06-26 13:41:02 +00:00
protected final Object MAP_LOCK = new Object ( ) ;
public AxolotlAddressMap ( ) {
this . map = new HashMap < > ( ) ;
}
public void put ( AxolotlAddress address , T value ) {
synchronized ( MAP_LOCK ) {
Map < Integer , T > devices = map . get ( address . getName ( ) ) ;
if ( devices = = null ) {
devices = new HashMap < > ( ) ;
map . put ( address . getName ( ) , devices ) ;
}
devices . put ( address . getDeviceId ( ) , value ) ;
}
}
public T get ( AxolotlAddress address ) {
synchronized ( MAP_LOCK ) {
Map < Integer , T > devices = map . get ( address . getName ( ) ) ;
2015-06-29 11:55:45 +00:00
if ( devices = = null ) {
2015-06-26 13:41:02 +00:00
return null ;
}
return devices . get ( address . getDeviceId ( ) ) ;
}
}
public Map < Integer , T > getAll ( AxolotlAddress address ) {
synchronized ( MAP_LOCK ) {
Map < Integer , T > devices = map . get ( address . getName ( ) ) ;
2015-06-29 11:55:45 +00:00
if ( devices = = null ) {
2015-06-26 13:41:02 +00:00
return new HashMap < > ( ) ;
}
return devices ;
}
}
public boolean hasAny ( AxolotlAddress address ) {
synchronized ( MAP_LOCK ) {
Map < Integer , T > devices = map . get ( address . getName ( ) ) ;
return devices ! = null & & ! devices . isEmpty ( ) ;
}
}
2015-07-10 00:18:01 +00:00
public void clear ( ) {
map . clear ( ) ;
}
2015-06-26 13:41:02 +00:00
}
private static class SessionMap extends AxolotlAddressMap < XmppAxolotlSession > {
2015-07-10 00:36:29 +00:00
private final XmppConnectionService xmppConnectionService ;
private final Account account ;
2015-06-26 13:41:02 +00:00
2015-07-10 00:36:29 +00:00
public SessionMap ( XmppConnectionService service , SQLiteAxolotlStore store , Account account ) {
2015-06-26 13:41:02 +00:00
super ( ) ;
2015-07-10 00:36:29 +00:00
this . xmppConnectionService = service ;
this . account = account ;
this . fillMap ( store ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-10 00:36:29 +00:00
private void fillMap ( SQLiteAxolotlStore store ) {
2015-06-29 11:55:45 +00:00
for ( Contact contact : account . getRoster ( ) . getContacts ( ) ) {
2015-06-26 13:41:02 +00:00
Jid bareJid = contact . getJid ( ) . toBareJid ( ) ;
2015-06-29 11:55:45 +00:00
if ( bareJid = = null ) {
2015-06-26 13:41:02 +00:00
continue ; // FIXME: handle this?
}
String address = bareJid . toString ( ) ;
List < Integer > deviceIDs = store . getSubDeviceSessions ( address ) ;
2015-06-29 11:55:45 +00:00
for ( Integer deviceId : deviceIDs ) {
2015-06-26 13:41:02 +00:00
AxolotlAddress axolotlAddress = new AxolotlAddress ( address , deviceId ) ;
2015-07-09 12:23:17 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Building session for remote address: " + axolotlAddress . toString ( ) ) ;
String fingerprint = store . loadSession ( axolotlAddress ) . getSessionState ( ) . getRemoteIdentityKey ( ) . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ;
this . put ( axolotlAddress , new XmppAxolotlSession ( account , store , axolotlAddress , fingerprint ) ) ;
2015-06-26 13:41:02 +00:00
}
}
}
2015-07-10 00:36:29 +00:00
@Override
public void put ( AxolotlAddress address , XmppAxolotlSession value ) {
super . put ( address , value ) ;
xmppConnectionService . syncRosterToDisk ( account ) ;
}
2015-06-26 13:41:02 +00:00
}
2015-06-29 12:22:26 +00:00
private static enum FetchStatus {
PENDING ,
SUCCESS ,
ERROR
}
private static class FetchStatusMap extends AxolotlAddressMap < FetchStatus > {
2015-06-26 13:41:02 +00:00
}
2015-07-08 15:44:24 +00:00
public static String getLogprefix ( Account account ) {
return LOGPREFIX + " ( " + account . getJid ( ) . toBareJid ( ) . toString ( ) + " ): " ;
}
2015-06-26 13:41:02 +00:00
public AxolotlService ( Account account , XmppConnectionService connectionService ) {
this . mXmppConnectionService = connectionService ;
this . account = account ;
this . axolotlStore = new SQLiteAxolotlStore ( this . account , this . mXmppConnectionService ) ;
2015-06-29 12:18:11 +00:00
this . deviceIds = new HashMap < > ( ) ;
2015-07-03 11:31:14 +00:00
this . messageCache = new HashMap < > ( ) ;
2015-07-10 00:36:29 +00:00
this . sessions = new SessionMap ( mXmppConnectionService , axolotlStore , account ) ;
2015-06-29 12:22:26 +00:00
this . fetchStatusMap = new FetchStatusMap ( ) ;
this . executor = new SerialSingleThreadExecutor ( ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-07 17:36:22 +00:00
public IdentityKey getOwnPublicKey ( ) {
return axolotlStore . getIdentityKeyPair ( ) . getPublicKey ( ) ;
}
2015-07-20 20:35:07 +00:00
public Set < IdentityKey > getKeysWithTrust ( SQLiteAxolotlStore . Trust trust ) {
return axolotlStore . getContactUndecidedKeys ( account . getJid ( ) . toBareJid ( ) . toString ( ) , trust ) ;
2015-07-19 16:36:28 +00:00
}
2015-07-20 20:35:07 +00:00
public Set < IdentityKey > getKeysWithTrust ( SQLiteAxolotlStore . Trust trust , Contact contact ) {
return axolotlStore . getContactUndecidedKeys ( contact . getJid ( ) . toBareJid ( ) . toString ( ) , trust ) ;
2015-07-19 16:36:28 +00:00
}
2015-07-20 12:56:41 +00:00
public long getNumTrustedKeys ( Contact contact ) {
return axolotlStore . getContactNumTrustedKeys ( contact . getJid ( ) . toBareJid ( ) . toString ( ) ) ;
}
2015-06-26 13:41:02 +00:00
private AxolotlAddress getAddressForJid ( Jid jid ) {
return new AxolotlAddress ( jid . toString ( ) , 0 ) ;
}
private Set < XmppAxolotlSession > findOwnSessions ( ) {
2015-06-29 12:19:17 +00:00
AxolotlAddress ownAddress = getAddressForJid ( account . getJid ( ) . toBareJid ( ) ) ;
2015-06-26 13:41:02 +00:00
Set < XmppAxolotlSession > ownDeviceSessions = new HashSet < > ( this . sessions . getAll ( ownAddress ) . values ( ) ) ;
return ownDeviceSessions ;
}
private Set < XmppAxolotlSession > findSessionsforContact ( Contact contact ) {
AxolotlAddress contactAddress = getAddressForJid ( contact . getJid ( ) ) ;
Set < XmppAxolotlSession > sessions = new HashSet < > ( this . sessions . getAll ( contactAddress ) . values ( ) ) ;
return sessions ;
}
private boolean hasAny ( Contact contact ) {
AxolotlAddress contactAddress = getAddressForJid ( contact . getJid ( ) ) ;
return sessions . hasAny ( contactAddress ) ;
}
2015-07-07 17:36:22 +00:00
public void regenerateKeys ( ) {
axolotlStore . regenerate ( ) ;
2015-07-10 00:18:01 +00:00
sessions . clear ( ) ;
fetchStatusMap . clear ( ) ;
2015-07-07 17:36:22 +00:00
publishBundlesIfNeeded ( ) ;
2015-07-10 00:18:01 +00:00
publishOwnDeviceIdIfNeeded ( ) ;
2015-07-07 17:36:22 +00:00
}
2015-06-26 13:41:02 +00:00
public int getOwnDeviceId ( ) {
2015-07-10 00:18:01 +00:00
return axolotlStore . loadRegistrationId ( ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-07 17:36:22 +00:00
public Set < Integer > getOwnDeviceIds ( ) {
return this . deviceIds . get ( account . getJid ( ) . toBareJid ( ) ) ;
}
2015-07-05 20:53:34 +00:00
public void registerDevices ( final Jid jid , @NonNull final Set < Integer > deviceIds ) {
2015-07-07 17:32:52 +00:00
if ( deviceIds . contains ( getOwnDeviceId ( ) ) ) {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Skipping own Device ID: " + jid + " : " + getOwnDeviceId ( ) ) ;
2015-07-07 17:32:52 +00:00
deviceIds . remove ( getOwnDeviceId ( ) ) ;
}
2015-06-29 12:18:11 +00:00
for ( Integer i : deviceIds ) {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Adding Device ID: " + jid + " : " + i ) ;
2015-06-26 13:41:02 +00:00
}
2015-06-29 12:18:11 +00:00
this . deviceIds . put ( jid , deviceIds ) ;
2015-07-07 17:32:52 +00:00
publishOwnDeviceIdIfNeeded ( ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-07 17:36:22 +00:00
public void wipeOtherPepDevices ( ) {
Set < Integer > deviceIds = new HashSet < > ( ) ;
deviceIds . add ( getOwnDeviceId ( ) ) ;
IqPacket publish = mXmppConnectionService . getIqGenerator ( ) . publishDeviceIds ( deviceIds ) ;
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Wiping all other devices from Pep: " + publish ) ;
2015-07-07 17:36:22 +00:00
mXmppConnectionService . sendIqPacket ( account , publish , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
// TODO: implement this!
}
} ) ;
}
2015-07-20 20:18:24 +00:00
public void purgeKey ( IdentityKey identityKey ) {
axolotlStore . setFingerprintTrust ( identityKey . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) , SQLiteAxolotlStore . Trust . COMPROMISED ) ;
}
2015-06-26 13:41:02 +00:00
public void publishOwnDeviceIdIfNeeded ( ) {
IqPacket packet = mXmppConnectionService . getIqGenerator ( ) . retrieveDeviceIds ( account . getJid ( ) . toBareJid ( ) ) ;
mXmppConnectionService . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
Element item = mXmppConnectionService . getIqParser ( ) . getItem ( packet ) ;
2015-06-29 12:18:11 +00:00
Set < Integer > deviceIds = mXmppConnectionService . getIqParser ( ) . deviceIds ( item ) ;
if ( deviceIds = = null ) {
deviceIds = new HashSet < Integer > ( ) ;
2015-06-26 13:41:02 +00:00
}
2015-06-29 12:18:11 +00:00
if ( ! deviceIds . contains ( getOwnDeviceId ( ) ) ) {
2015-06-26 13:41:02 +00:00
deviceIds . add ( getOwnDeviceId ( ) ) ;
IqPacket publish = mXmppConnectionService . getIqGenerator ( ) . publishDeviceIds ( deviceIds ) ;
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Own device " + getOwnDeviceId ( ) + " not in PEP devicelist. Publishing: " + publish ) ;
2015-06-26 13:41:02 +00:00
mXmppConnectionService . sendIqPacket ( account , publish , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
// TODO: implement this!
}
} ) ;
}
}
} ) ;
}
2015-06-29 12:18:11 +00:00
public void publishBundlesIfNeeded ( ) {
2015-07-10 00:18:01 +00:00
IqPacket packet = mXmppConnectionService . getIqGenerator ( ) . retrieveBundlesForDevice ( account . getJid ( ) . toBareJid ( ) , getOwnDeviceId ( ) ) ;
2015-06-26 13:41:02 +00:00
mXmppConnectionService . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
PreKeyBundle bundle = mXmppConnectionService . getIqParser ( ) . bundle ( packet ) ;
2015-06-29 12:18:11 +00:00
Map < Integer , ECPublicKey > keys = mXmppConnectionService . getIqParser ( ) . preKeyPublics ( packet ) ;
2015-07-03 11:20:27 +00:00
boolean flush = false ;
if ( bundle = = null ) {
2015-07-08 15:44:24 +00:00
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Received invalid bundle: " + packet ) ;
2015-07-03 11:20:27 +00:00
bundle = new PreKeyBundle ( - 1 , - 1 , - 1 , null , - 1 , null , null , null ) ;
flush = true ;
}
if ( keys = = null ) {
2015-07-08 15:44:24 +00:00
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Received invalid prekeys: " + packet ) ;
2015-07-03 11:20:27 +00:00
}
try {
boolean changed = false ;
// Validate IdentityKey
IdentityKeyPair identityKeyPair = axolotlStore . getIdentityKeyPair ( ) ;
if ( flush | | ! identityKeyPair . getPublicKey ( ) . equals ( bundle . getIdentityKey ( ) ) ) {
2015-07-08 15:44:24 +00:00
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Adding own IdentityKey " + identityKeyPair . getPublicKey ( ) + " to PEP. " ) ;
2015-07-03 11:20:27 +00:00
changed = true ;
}
// Validate signedPreKeyRecord + ID
SignedPreKeyRecord signedPreKeyRecord ;
2015-06-26 13:41:02 +00:00
int numSignedPreKeys = axolotlStore . loadSignedPreKeys ( ) . size ( ) ;
try {
2015-07-03 11:20:27 +00:00
signedPreKeyRecord = axolotlStore . loadSignedPreKey ( bundle . getSignedPreKeyId ( ) ) ;
if ( flush
| | ! bundle . getSignedPreKey ( ) . equals ( signedPreKeyRecord . getKeyPair ( ) . getPublicKey ( ) )
| | ! Arrays . equals ( bundle . getSignedPreKeySignature ( ) , signedPreKeyRecord . getSignature ( ) ) ) {
2015-07-08 15:44:24 +00:00
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Adding new signedPreKey with ID " + ( numSignedPreKeys + 1 ) + " to PEP. " ) ;
2015-07-03 11:20:27 +00:00
signedPreKeyRecord = KeyHelper . generateSignedPreKey ( identityKeyPair , numSignedPreKeys + 1 ) ;
axolotlStore . storeSignedPreKey ( signedPreKeyRecord . getId ( ) , signedPreKeyRecord ) ;
changed = true ;
}
} catch ( InvalidKeyIdException e ) {
2015-07-08 15:44:24 +00:00
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Adding new signedPreKey with ID " + ( numSignedPreKeys + 1 ) + " to PEP. " ) ;
2015-07-03 11:20:27 +00:00
signedPreKeyRecord = KeyHelper . generateSignedPreKey ( identityKeyPair , numSignedPreKeys + 1 ) ;
2015-06-26 13:41:02 +00:00
axolotlStore . storeSignedPreKey ( signedPreKeyRecord . getId ( ) , signedPreKeyRecord ) ;
2015-07-03 11:20:27 +00:00
changed = true ;
}
2015-06-29 12:18:11 +00:00
2015-07-03 11:20:27 +00:00
// Validate PreKeys
Set < PreKeyRecord > preKeyRecords = new HashSet < > ( ) ;
if ( keys ! = null ) {
for ( Integer id : keys . keySet ( ) ) {
try {
PreKeyRecord preKeyRecord = axolotlStore . loadPreKey ( id ) ;
if ( preKeyRecord . getKeyPair ( ) . getPublicKey ( ) . equals ( keys . get ( id ) ) ) {
preKeyRecords . add ( preKeyRecord ) ;
}
} catch ( InvalidKeyIdException ignored ) {
}
}
}
int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords . size ( ) ;
if ( newKeys > 0 ) {
List < PreKeyRecord > newRecords = KeyHelper . generatePreKeys (
axolotlStore . getCurrentPreKeyId ( ) + 1 , newKeys ) ;
preKeyRecords . addAll ( newRecords ) ;
for ( PreKeyRecord record : newRecords ) {
2015-06-29 12:18:11 +00:00
axolotlStore . storePreKey ( record . getId ( ) , record ) ;
}
2015-07-03 11:20:27 +00:00
changed = true ;
2015-07-08 15:44:24 +00:00
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Adding " + newKeys + " new preKeys to PEP. " ) ;
2015-07-03 11:20:27 +00:00
}
2015-06-29 12:18:11 +00:00
2015-07-03 11:20:27 +00:00
if ( changed ) {
2015-06-29 12:18:11 +00:00
IqPacket publish = mXmppConnectionService . getIqGenerator ( ) . publishBundles (
2015-06-26 13:41:02 +00:00
signedPreKeyRecord , axolotlStore . getIdentityKeyPair ( ) . getPublicKey ( ) ,
2015-07-10 00:18:01 +00:00
preKeyRecords , getOwnDeviceId ( ) ) ;
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " : Bundle " + getOwnDeviceId ( ) + " in PEP not current. Publishing: " + publish ) ;
2015-06-26 13:41:02 +00:00
mXmppConnectionService . sendIqPacket ( account , publish , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
// TODO: implement this!
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Published bundle, got: " + packet ) ;
2015-06-26 13:41:02 +00:00
}
} ) ;
2015-07-03 11:20:27 +00:00
}
} catch ( InvalidKeyException e ) {
2015-07-08 15:44:24 +00:00
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Failed to publish bundle " + getOwnDeviceId ( ) + " , reason: " + e . getMessage ( ) ) ;
2015-06-29 12:18:11 +00:00
return ;
2015-06-26 13:41:02 +00:00
}
}
} ) ;
}
2015-06-29 12:18:11 +00:00
public boolean isContactAxolotlCapable ( Contact contact ) {
2015-07-09 12:23:17 +00:00
2015-06-29 12:18:11 +00:00
Jid jid = contact . getJid ( ) . toBareJid ( ) ;
AxolotlAddress address = new AxolotlAddress ( jid . toString ( ) , 0 ) ;
return sessions . hasAny ( address ) | |
( deviceIds . containsKey ( jid ) & & ! deviceIds . get ( jid ) . isEmpty ( ) ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-15 14:32:42 +00:00
public SQLiteAxolotlStore . Trust getFingerprintTrust ( String fingerprint ) {
return axolotlStore . getFingerprintTrust ( fingerprint ) ;
2015-07-09 12:23:17 +00:00
}
2015-07-15 14:32:42 +00:00
public void setFingerprintTrust ( String fingerprint , SQLiteAxolotlStore . Trust trust ) {
axolotlStore . setFingerprintTrust ( fingerprint , trust ) ;
2015-07-09 12:23:17 +00:00
}
2015-06-26 13:41:02 +00:00
2015-07-19 16:36:28 +00:00
private void buildSessionFromPEP ( final Conversation conversation , final AxolotlAddress address , final boolean flushWaitingQueueAfterFetch ) {
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Building new sesstion for " + address . getDeviceId ( ) ) ;
2015-06-29 12:18:11 +00:00
try {
IqPacket bundlesPacket = mXmppConnectionService . getIqGenerator ( ) . retrieveBundlesForDevice (
Jid . fromString ( address . getName ( ) ) , address . getDeviceId ( ) ) ;
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Retrieving bundle: " + bundlesPacket ) ;
2015-06-29 12:18:11 +00:00
mXmppConnectionService . sendIqPacket ( account , bundlesPacket , new OnIqPacketReceived ( ) {
2015-07-19 16:36:28 +00:00
private void finish ( ) {
AxolotlAddress ownAddress = new AxolotlAddress ( conversation . getAccount ( ) . getJid ( ) . toBareJid ( ) . toString ( ) , 0 ) ;
AxolotlAddress foreignAddress = new AxolotlAddress ( conversation . getJid ( ) . toBareJid ( ) . toString ( ) , 0 ) ;
if ( ! fetchStatusMap . getAll ( ownAddress ) . containsValue ( FetchStatus . PENDING )
& & ! fetchStatusMap . getAll ( foreignAddress ) . containsValue ( FetchStatus . PENDING ) ) {
if ( flushWaitingQueueAfterFetch ) {
conversation . findUnsentMessagesWithEncryption ( Message . ENCRYPTION_AXOLOTL ,
new Conversation . OnMessageFound ( ) {
@Override
public void onMessageFound ( Message message ) {
2015-07-20 16:11:33 +00:00
processSending ( message , false ) ;
2015-07-19 16:36:28 +00:00
}
} ) ;
}
mXmppConnectionService . newKeysAvailable ( ) ;
}
}
2015-06-29 12:18:11 +00:00
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Received preKey IQ packet, processing... " ) ;
2015-06-29 12:18:11 +00:00
final IqParser parser = mXmppConnectionService . getIqParser ( ) ;
final List < PreKeyBundle > preKeyBundleList = parser . preKeys ( packet ) ;
final PreKeyBundle bundle = parser . bundle ( packet ) ;
if ( preKeyBundleList . isEmpty ( ) | | bundle = = null ) {
2015-07-08 15:44:24 +00:00
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " preKey IQ packet invalid: " + packet ) ;
2015-06-29 12:18:11 +00:00
fetchStatusMap . put ( address , FetchStatus . ERROR ) ;
2015-07-19 16:36:28 +00:00
finish ( ) ;
2015-06-29 12:18:11 +00:00
return ;
}
Random random = new Random ( ) ;
final PreKeyBundle preKey = preKeyBundleList . get ( random . nextInt ( preKeyBundleList . size ( ) ) ) ;
if ( preKey = = null ) {
//should never happen
fetchStatusMap . put ( address , FetchStatus . ERROR ) ;
2015-07-19 16:36:28 +00:00
finish ( ) ;
2015-06-29 12:18:11 +00:00
return ;
}
2015-06-26 13:41:02 +00:00
2015-06-29 12:18:11 +00:00
final PreKeyBundle preKeyBundle = new PreKeyBundle ( 0 , address . getDeviceId ( ) ,
preKey . getPreKeyId ( ) , preKey . getPreKey ( ) ,
bundle . getSignedPreKeyId ( ) , bundle . getSignedPreKey ( ) ,
bundle . getSignedPreKeySignature ( ) , bundle . getIdentityKey ( ) ) ;
axolotlStore . saveIdentity ( address . getName ( ) , bundle . getIdentityKey ( ) ) ;
2015-06-26 13:41:02 +00:00
2015-06-29 12:18:11 +00:00
try {
SessionBuilder builder = new SessionBuilder ( axolotlStore , address ) ;
builder . process ( preKeyBundle ) ;
2015-07-09 12:23:17 +00:00
XmppAxolotlSession session = new XmppAxolotlSession ( account , axolotlStore , address , bundle . getIdentityKey ( ) . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ) ;
2015-06-29 12:18:11 +00:00
sessions . put ( address , session ) ;
fetchStatusMap . put ( address , FetchStatus . SUCCESS ) ;
} catch ( UntrustedIdentityException | InvalidKeyException e ) {
2015-07-08 15:44:24 +00:00
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Error building session for " + address + " : "
2015-06-29 12:18:11 +00:00
+ e . getClass ( ) . getName ( ) + " , " + e . getMessage ( ) ) ;
fetchStatusMap . put ( address , FetchStatus . ERROR ) ;
}
2015-06-26 13:41:02 +00:00
2015-07-19 16:36:28 +00:00
finish ( ) ;
2015-06-29 12:18:11 +00:00
}
} ) ;
} catch ( InvalidJidException e ) {
2015-07-08 15:44:24 +00:00
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Got address with invalid jid: " + address . getName ( ) ) ;
2015-06-29 12:18:11 +00:00
}
2015-06-26 13:41:02 +00:00
}
2015-07-19 16:36:28 +00:00
public Set < AxolotlAddress > findDevicesWithoutSession ( final Conversation conversation ) {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Finding devices without session for " + conversation . getContact ( ) . getJid ( ) . toBareJid ( ) ) ;
2015-06-29 12:22:26 +00:00
Jid contactJid = conversation . getContact ( ) . getJid ( ) . toBareJid ( ) ;
Set < AxolotlAddress > addresses = new HashSet < > ( ) ;
if ( deviceIds . get ( contactJid ) ! = null ) {
for ( Integer foreignId : this . deviceIds . get ( contactJid ) ) {
2015-07-19 16:36:28 +00:00
AxolotlAddress address = new AxolotlAddress ( contactJid . toString ( ) , foreignId ) ;
if ( sessions . get ( address ) = = null ) {
IdentityKey identityKey = axolotlStore . loadSession ( address ) . getSessionState ( ) . getRemoteIdentityKey ( ) ;
if ( identityKey ! = null ) {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Already have session for " + address . toString ( ) + " , adding to cache... " ) ;
XmppAxolotlSession session = new XmppAxolotlSession ( account , axolotlStore , address , identityKey . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ) ;
sessions . put ( address , session ) ;
} else {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Found device " + account . getJid ( ) . toBareJid ( ) + " : " + foreignId ) ;
addresses . add ( new AxolotlAddress ( contactJid . toString ( ) , foreignId ) ) ;
}
}
2015-06-29 12:22:26 +00:00
}
} else {
2015-07-09 12:23:17 +00:00
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Have no target devices in PEP! " ) ;
2015-06-29 12:22:26 +00:00
}
if ( deviceIds . get ( account . getJid ( ) . toBareJid ( ) ) ! = null ) {
for ( Integer ownId : this . deviceIds . get ( account . getJid ( ) . toBareJid ( ) ) ) {
2015-07-19 16:36:28 +00:00
AxolotlAddress address = new AxolotlAddress ( account . getJid ( ) . toBareJid ( ) . toString ( ) , ownId ) ;
if ( sessions . get ( address ) = = null ) {
IdentityKey identityKey = axolotlStore . loadSession ( address ) . getSessionState ( ) . getRemoteIdentityKey ( ) ;
if ( identityKey ! = null ) {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Already have session for " + address . toString ( ) + " , adding to cache... " ) ;
XmppAxolotlSession session = new XmppAxolotlSession ( account , axolotlStore , address , identityKey . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ) ;
sessions . put ( address , session ) ;
} else {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Found device " + account . getJid ( ) . toBareJid ( ) + " : " + ownId ) ;
addresses . add ( new AxolotlAddress ( account . getJid ( ) . toBareJid ( ) . toString ( ) , ownId ) ) ;
}
}
2015-06-26 13:41:02 +00:00
}
}
2015-07-19 16:36:28 +00:00
return addresses ;
}
public boolean createSessionsIfNeeded ( final Conversation conversation , final boolean flushWaitingQueueAfterFetch ) {
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Creating axolotl sessions if needed... " ) ;
boolean newSessions = false ;
Set < AxolotlAddress > addresses = findDevicesWithoutSession ( conversation ) ;
2015-06-29 12:22:26 +00:00
for ( AxolotlAddress address : addresses ) {
2015-07-19 16:36:28 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Processing device: " + address . toString ( ) ) ;
2015-06-29 12:22:26 +00:00
FetchStatus status = fetchStatusMap . get ( address ) ;
2015-07-19 16:36:28 +00:00
if ( status = = null | | status = = FetchStatus . ERROR ) {
2015-07-10 00:36:29 +00:00
fetchStatusMap . put ( address , FetchStatus . PENDING ) ;
2015-07-19 16:36:28 +00:00
this . buildSessionFromPEP ( conversation , address , flushWaitingQueueAfterFetch ) ;
2015-07-10 00:36:29 +00:00
newSessions = true ;
2015-06-29 12:22:26 +00:00
} else {
2015-07-19 16:36:28 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Already fetching bundle for " + address . toString ( ) ) ;
2015-06-29 12:22:26 +00:00
}
2015-06-26 13:41:02 +00:00
}
2015-07-19 16:36:28 +00:00
2015-06-29 12:22:26 +00:00
return newSessions ;
2015-06-26 13:41:02 +00:00
}
2015-07-19 16:36:28 +00:00
public boolean hasPendingKeyFetches ( Conversation conversation ) {
AxolotlAddress ownAddress = new AxolotlAddress ( account . getJid ( ) . toBareJid ( ) . toString ( ) , 0 ) ;
AxolotlAddress foreignAddress = new AxolotlAddress ( conversation . getJid ( ) . toBareJid ( ) . toString ( ) , 0 ) ;
return fetchStatusMap . getAll ( ownAddress ) . containsValue ( FetchStatus . PENDING )
| | fetchStatusMap . getAll ( foreignAddress ) . containsValue ( FetchStatus . PENDING ) ;
}
2015-06-29 12:22:26 +00:00
@Nullable
public XmppAxolotlMessage encrypt ( Message message ) {
2015-07-17 17:44:45 +00:00
final String content ;
if ( message . hasFileOnRemoteHost ( ) ) {
content = message . getFileParams ( ) . url . toString ( ) ;
} else {
content = message . getBody ( ) ;
}
2015-07-03 11:27:35 +00:00
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage ( message . getContact ( ) . getJid ( ) . toBareJid ( ) ,
2015-07-17 17:44:45 +00:00
getOwnDeviceId ( ) , content ) ;
2015-06-26 13:41:02 +00:00
2015-07-03 11:27:35 +00:00
if ( findSessionsforContact ( message . getContact ( ) ) . isEmpty ( ) ) {
2015-06-29 12:22:26 +00:00
return null ;
}
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Building axolotl foreign headers... " ) ;
2015-07-03 11:27:35 +00:00
for ( XmppAxolotlSession session : findSessionsforContact ( message . getContact ( ) ) ) {
2015-07-08 15:44:24 +00:00
Log . v ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + session . remoteAddress . toString ( ) ) ;
2015-06-29 12:22:26 +00:00
//if(!session.isTrusted()) {
// TODO: handle this properly
// continue;
// }
axolotlMessage . addHeader ( session . processSending ( axolotlMessage . getInnerKey ( ) ) ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Building axolotl own headers... " ) ;
2015-06-29 12:22:26 +00:00
for ( XmppAxolotlSession session : findOwnSessions ( ) ) {
2015-07-08 15:44:24 +00:00
Log . v ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + session . remoteAddress . toString ( ) ) ;
2015-06-29 12:22:26 +00:00
// if(!session.isTrusted()) {
// TODO: handle this properly
// continue;
// }
axolotlMessage . addHeader ( session . processSending ( axolotlMessage . getInnerKey ( ) ) ) ;
2015-06-26 13:41:02 +00:00
}
2015-06-29 12:22:26 +00:00
return axolotlMessage ;
}
2015-07-20 16:11:33 +00:00
private void processSending ( final Message message , final boolean delay ) {
2015-06-29 12:22:26 +00:00
executor . execute ( new Runnable ( ) {
@Override
public void run ( ) {
MessagePacket packet = mXmppConnectionService . getMessageGenerator ( )
. generateAxolotlChat ( message ) ;
if ( packet = = null ) {
mXmppConnectionService . markMessage ( message , Message . STATUS_SEND_FAILED ) ;
2015-07-03 11:31:14 +00:00
//mXmppConnectionService.updateConversationUi();
2015-06-29 12:22:26 +00:00
} else {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Generated message, caching: " + message . getUuid ( ) ) ;
2015-07-03 11:31:14 +00:00
messageCache . put ( message . getUuid ( ) , packet ) ;
2015-07-20 16:11:33 +00:00
mXmppConnectionService . resendMessage ( message , delay ) ;
2015-06-29 12:22:26 +00:00
}
}
} ) ;
}
2015-07-20 16:11:33 +00:00
public void prepareMessage ( final Message message , final boolean delay ) {
2015-07-03 11:31:14 +00:00
if ( ! messageCache . containsKey ( message . getUuid ( ) ) ) {
2015-07-19 16:36:28 +00:00
boolean newSessions = createSessionsIfNeeded ( message . getConversation ( ) , true ) ;
2015-07-03 11:31:14 +00:00
if ( ! newSessions ) {
2015-07-20 16:11:33 +00:00
this . processSending ( message , delay ) ;
2015-07-03 11:31:14 +00:00
}
}
}
public MessagePacket fetchPacketFromCache ( Message message ) {
MessagePacket packet = messageCache . get ( message . getUuid ( ) ) ;
if ( packet ! = null ) {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Cache hit: " + message . getUuid ( ) ) ;
2015-07-03 11:31:14 +00:00
messageCache . remove ( message . getUuid ( ) ) ;
} else {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Cache miss: " + message . getUuid ( ) ) ;
2015-06-29 12:22:26 +00:00
}
2015-07-03 11:31:14 +00:00
return packet ;
2015-06-26 13:41:02 +00:00
}
public XmppAxolotlMessage . XmppAxolotlPlaintextMessage processReceiving ( XmppAxolotlMessage message ) {
XmppAxolotlMessage . XmppAxolotlPlaintextMessage plaintextMessage = null ;
2015-07-03 11:27:35 +00:00
AxolotlAddress senderAddress = new AxolotlAddress ( message . getFrom ( ) . toString ( ) ,
2015-06-26 13:41:02 +00:00
message . getSenderDeviceId ( ) ) ;
2015-07-05 20:54:28 +00:00
boolean newSession = false ;
2015-06-26 13:41:02 +00:00
XmppAxolotlSession session = sessions . get ( senderAddress ) ;
if ( session = = null ) {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Account: " + account . getJid ( ) + " No axolotl session found while parsing received message " + message ) ;
2015-06-26 13:41:02 +00:00
// TODO: handle this properly
2015-07-10 00:36:29 +00:00
IdentityKey identityKey = axolotlStore . loadSession ( senderAddress ) . getSessionState ( ) . getRemoteIdentityKey ( ) ;
if ( identityKey ! = null ) {
session = new XmppAxolotlSession ( account , axolotlStore , senderAddress , identityKey . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ) ;
} else {
session = new XmppAxolotlSession ( account , axolotlStore , senderAddress ) ;
}
2015-07-05 20:54:28 +00:00
newSession = true ;
2015-06-26 13:41:02 +00:00
}
2015-06-29 12:33:43 +00:00
for ( XmppAxolotlMessage . XmppAxolotlMessageHeader header : message . getHeaders ( ) ) {
2015-07-10 00:18:01 +00:00
if ( header . getRecipientDeviceId ( ) = = getOwnDeviceId ( ) ) {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Found axolotl header matching own device ID, processing... " ) ;
2015-06-26 13:41:02 +00:00
byte [ ] payloadKey = session . processReceiving ( header ) ;
if ( payloadKey ! = null ) {
2015-07-08 15:44:24 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Got payload key from axolotl header. Decrypting message... " ) ;
2015-07-09 12:23:17 +00:00
plaintextMessage = message . decrypt ( session , payloadKey , session . getFingerprint ( ) ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-03 11:34:34 +00:00
Integer preKeyId = session . getPreKeyId ( ) ;
if ( preKeyId ! = null ) {
publishBundlesIfNeeded ( ) ;
session . resetPreKeyId ( ) ;
}
break ;
2015-06-26 13:41:02 +00:00
}
}
2015-07-05 20:54:28 +00:00
if ( newSession & & plaintextMessage ! = null ) {
2015-07-10 00:36:29 +00:00
sessions . put ( senderAddress , session ) ;
2015-07-05 20:54:28 +00:00
}
2015-06-26 13:41:02 +00:00
return plaintextMessage ;
}
2015-05-29 09:17:26 +00:00
}