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 ;
2015-07-20 23:15:32 +00:00
import org.bouncycastle.jce.provider.BouncyCastleProvider ;
2015-05-29 09:17:26 +00:00
import org.whispersystems.libaxolotl.AxolotlAddress ;
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.SessionBuilder ;
import org.whispersystems.libaxolotl.UntrustedIdentityException ;
import org.whispersystems.libaxolotl.ecc.ECPublicKey ;
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.SignedPreKeyRecord ;
import org.whispersystems.libaxolotl.util.KeyHelper ;
2015-07-20 23:15:32 +00:00
import java.security.Security ;
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-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-08-07 10:28:45 +00:00
public static final int NUM_KEYS_TO_PUBLISH = 100 ;
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-20 21:13:28 +00:00
private final Map < String , XmppAxolotlMessage > 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
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-21 11:51:15 +00:00
private void putDevicesForJid ( String bareJid , List < Integer > deviceIds , SQLiteAxolotlStore store ) {
for ( Integer deviceId : deviceIds ) {
AxolotlAddress axolotlAddress = new AxolotlAddress ( bareJid , deviceId ) ;
2015-07-31 16:05:32 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Building session for remote address: " + axolotlAddress . toString ( ) ) ;
2015-07-21 11:51:15 +00:00
String fingerprint = store . loadSession ( axolotlAddress ) . getSessionState ( ) . getRemoteIdentityKey ( ) . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ;
this . put ( axolotlAddress , new XmppAxolotlSession ( account , store , axolotlAddress , fingerprint ) ) ;
}
}
2015-07-10 00:36:29 +00:00
private void fillMap ( SQLiteAxolotlStore store ) {
2015-07-21 11:51:15 +00:00
List < Integer > deviceIds = store . getSubDeviceSessions ( account . getJid ( ) . toBareJid ( ) . toString ( ) ) ;
putDevicesForJid ( account . getJid ( ) . toBareJid ( ) . toString ( ) , deviceIds , 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 ( ) ;
2015-07-21 11:51:15 +00:00
deviceIds = store . getSubDeviceSessions ( address ) ;
putDevicesForJid ( address , deviceIds , store ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-21 11:51:15 +00:00
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 ) ;
2015-07-31 21:28:09 +00:00
value . setNotFresh ( ) ;
2015-07-10 00:36:29 +00:00
xmppConnectionService . syncRosterToDisk ( account ) ;
}
2015-07-31 21:28:09 +00:00
public void put ( XmppAxolotlSession session ) {
this . put ( session . getRemoteAddress ( ) , session ) ;
}
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-31 16:05:32 +00:00
2015-07-08 15:44:24 +00:00
public static String getLogprefix ( Account account ) {
2015-07-31 16:05:32 +00:00
return LOGPREFIX + " ( " + account . getJid ( ) . toBareJid ( ) . toString ( ) + " ): " ;
2015-07-08 15:44:24 +00:00
}
2015-06-26 13:41:02 +00:00
public AxolotlService ( Account account , XmppConnectionService connectionService ) {
2015-07-20 23:15:32 +00:00
if ( Security . getProvider ( " BC " ) = = null ) {
Security . addProvider ( new BouncyCastleProvider ( ) ) ;
}
2015-06-26 13:41:02 +00:00
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-08-01 16:27:52 +00:00
public Set < IdentityKey > getKeysWithTrust ( XmppAxolotlSession . Trust trust ) {
2015-07-28 20:00:54 +00:00
return axolotlStore . getContactKeysWithTrust ( account . getJid ( ) . toBareJid ( ) . toString ( ) , trust ) ;
2015-07-19 16:36:28 +00:00
}
2015-08-01 16:27:52 +00:00
public Set < IdentityKey > getKeysWithTrust ( XmppAxolotlSession . Trust trust , Contact contact ) {
2015-07-28 20:00:54 +00:00
return axolotlStore . getContactKeysWithTrust ( 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-28 20:00:54 +00:00
return axolotlStore . getLocalRegistrationId ( ) ;
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-21 12:18:16 +00:00
private void setTrustOnSessions ( final Jid jid , @NonNull final Set < Integer > deviceIds ,
2015-08-01 16:27:52 +00:00
final XmppAxolotlSession . Trust from ,
final XmppAxolotlSession . Trust to ) {
2015-07-31 16:05:32 +00:00
for ( Integer deviceId : deviceIds ) {
2015-07-21 12:18:16 +00:00
AxolotlAddress address = new AxolotlAddress ( jid . toBareJid ( ) . toString ( ) , deviceId ) ;
XmppAxolotlSession session = sessions . get ( address ) ;
if ( session ! = null & & session . getFingerprint ( ) ! = null
& & session . getTrust ( ) = = from ) {
session . setTrust ( to ) ;
}
}
}
2015-07-05 20:53:34 +00:00
public void registerDevices ( final Jid jid , @NonNull final Set < Integer > deviceIds ) {
2015-07-31 16:05:32 +00:00
if ( jid . toBareJid ( ) . equals ( account . getJid ( ) . toBareJid ( ) ) ) {
2015-07-22 13:02:53 +00:00
if ( deviceIds . contains ( getOwnDeviceId ( ) ) ) {
deviceIds . remove ( getOwnDeviceId ( ) ) ;
}
2015-07-31 16:05:32 +00:00
for ( Integer deviceId : deviceIds ) {
AxolotlAddress ownDeviceAddress = new AxolotlAddress ( jid . toBareJid ( ) . toString ( ) , deviceId ) ;
if ( sessions . get ( ownDeviceAddress ) = = null ) {
2015-07-31 19:12:34 +00:00
buildSessionFromPEP ( ownDeviceAddress ) ;
2015-07-22 13:02:53 +00:00
}
}
2015-07-07 17:32:52 +00:00
}
2015-07-21 12:18:16 +00:00
Set < Integer > expiredDevices = new HashSet < > ( axolotlStore . getSubDeviceSessions ( jid . toBareJid ( ) . toString ( ) ) ) ;
expiredDevices . removeAll ( deviceIds ) ;
2015-08-01 16:27:52 +00:00
setTrustOnSessions ( jid , expiredDevices , XmppAxolotlSession . Trust . TRUSTED ,
XmppAxolotlSession . Trust . INACTIVE_TRUSTED ) ;
setTrustOnSessions ( jid , expiredDevices , XmppAxolotlSession . Trust . UNDECIDED ,
XmppAxolotlSession . Trust . INACTIVE_UNDECIDED ) ;
setTrustOnSessions ( jid , expiredDevices , XmppAxolotlSession . Trust . UNTRUSTED ,
XmppAxolotlSession . Trust . INACTIVE_UNTRUSTED ) ;
2015-07-21 12:18:16 +00:00
Set < Integer > newDevices = new HashSet < > ( deviceIds ) ;
2015-08-01 16:27:52 +00:00
setTrustOnSessions ( jid , newDevices , XmppAxolotlSession . Trust . INACTIVE_TRUSTED ,
XmppAxolotlSession . Trust . TRUSTED ) ;
setTrustOnSessions ( jid , newDevices , XmppAxolotlSession . Trust . INACTIVE_UNDECIDED ,
XmppAxolotlSession . Trust . UNDECIDED ) ;
setTrustOnSessions ( jid , newDevices , XmppAxolotlSession . Trust . INACTIVE_UNTRUSTED ,
XmppAxolotlSession . Trust . UNTRUSTED ) ;
2015-06-29 12:18:11 +00:00
this . deviceIds . put ( jid , deviceIds ) ;
2015-07-21 12:18:16 +00:00
mXmppConnectionService . keyStatusUpdated ( ) ;
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-31 16:05:32 +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 ) {
2015-08-01 16:27:52 +00:00
axolotlStore . setFingerprintTrust ( identityKey . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) , XmppAxolotlSession . Trust . COMPROMISED ) ;
2015-07-20 20:18:24 +00:00
}
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 ) {
2015-08-23 11:23:10 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
Element item = mXmppConnectionService . getIqParser ( ) . getItem ( packet ) ;
Set < Integer > deviceIds = mXmppConnectionService . getIqParser ( ) . deviceIds ( item ) ;
if ( deviceIds = = null ) {
deviceIds = new HashSet < Integer > ( ) ;
}
if ( ! deviceIds . contains ( getOwnDeviceId ( ) ) ) {
deviceIds . add ( getOwnDeviceId ( ) ) ;
IqPacket publish = mXmppConnectionService . getIqGenerator ( ) . publishDeviceIds ( deviceIds ) ;
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Own device " + getOwnDeviceId ( ) + " not in PEP devicelist. Publishing: " + publish ) ;
mXmppConnectionService . sendIqPacket ( account , publish , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
// TODO: implement this!
}
} ) ;
}
} else {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Error received while publishing device ID: " + packet . findChild ( " error " ) ) ;
2015-06-26 13:41:02 +00:00
}
}
} ) ;
}
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 ) {
2015-08-23 11:23:10 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
PreKeyBundle bundle = mXmppConnectionService . getIqParser ( ) . bundle ( packet ) ;
Map < Integer , ECPublicKey > keys = mXmppConnectionService . getIqParser ( ) . preKeyPublics ( packet ) ;
boolean flush = false ;
if ( bundle = = null ) {
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Received invalid bundle: " + packet ) ;
bundle = new PreKeyBundle ( - 1 , - 1 , - 1 , null , - 1 , null , null , null ) ;
flush = true ;
}
if ( keys = = null ) {
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Received invalid prekeys: " + packet ) ;
2015-07-03 11:20:27 +00:00
}
2015-06-26 13:41:02 +00:00
try {
2015-08-23 11:23:10 +00:00
boolean changed = false ;
// Validate IdentityKey
IdentityKeyPair identityKeyPair = axolotlStore . getIdentityKeyPair ( ) ;
if ( flush | | ! identityKeyPair . getPublicKey ( ) . equals ( bundle . getIdentityKey ( ) ) ) {
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Adding own IdentityKey " + identityKeyPair . getPublicKey ( ) + " to PEP. " ) ;
changed = true ;
}
// Validate signedPreKeyRecord + ID
SignedPreKeyRecord signedPreKeyRecord ;
int numSignedPreKeys = axolotlStore . loadSignedPreKeys ( ) . size ( ) ;
try {
signedPreKeyRecord = axolotlStore . loadSignedPreKey ( bundle . getSignedPreKeyId ( ) ) ;
if ( flush
| | ! bundle . getSignedPreKey ( ) . equals ( signedPreKeyRecord . getKeyPair ( ) . getPublicKey ( ) )
| | ! Arrays . equals ( bundle . getSignedPreKeySignature ( ) , signedPreKeyRecord . getSignature ( ) ) ) {
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Adding new signedPreKey with ID " + ( numSignedPreKeys + 1 ) + " to PEP. " ) ;
signedPreKeyRecord = KeyHelper . generateSignedPreKey ( identityKeyPair , numSignedPreKeys + 1 ) ;
axolotlStore . storeSignedPreKey ( signedPreKeyRecord . getId ( ) , signedPreKeyRecord ) ;
changed = true ;
}
} catch ( InvalidKeyIdException e ) {
2015-07-31 16:05:32 +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 ;
}
2015-06-29 12:18:11 +00:00
2015-08-23 11:23:10 +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 ) {
2015-07-03 11:20:27 +00:00
}
}
}
2015-08-23 11:23:10 +00:00
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 ) {
axolotlStore . storePreKey ( record . getId ( ) , record ) ;
}
changed = true ;
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Adding " + newKeys + " new preKeys to PEP. " ) ;
2015-06-29 12:18:11 +00:00
}
2015-07-03 11:20:27 +00:00
2015-08-23 11:23:10 +00:00
if ( changed ) {
IqPacket publish = mXmppConnectionService . getIqGenerator ( ) . publishBundles (
signedPreKeyRecord , axolotlStore . getIdentityKeyPair ( ) . getPublicKey ( ) ,
preKeyRecords , getOwnDeviceId ( ) ) ;
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " : Bundle " + getOwnDeviceId ( ) + " in PEP not current. Publishing: " + publish ) ;
mXmppConnectionService . sendIqPacket ( account , publish , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
// TODO: implement this!
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Published bundle, got: " + packet ) ;
}
} ) ;
}
} catch ( InvalidKeyException e ) {
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Failed to publish bundle " + getOwnDeviceId ( ) + " , reason: " + e . getMessage ( ) ) ;
return ;
2015-07-03 11:20:27 +00:00
}
2015-08-23 11:23:10 +00:00
} else {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Error received while publishing Bundle: " + packet . findChild ( " error " ) ) ;
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 ) | |
2015-07-31 16:05:32 +00:00
( deviceIds . containsKey ( jid ) & & ! deviceIds . get ( jid ) . isEmpty ( ) ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-31 16:05:32 +00:00
2015-08-01 16:27:52 +00:00
public XmppAxolotlSession . Trust getFingerprintTrust ( String fingerprint ) {
2015-07-15 14:32:42 +00:00
return axolotlStore . getFingerprintTrust ( fingerprint ) ;
2015-07-09 12:23:17 +00:00
}
2015-08-01 16:27:52 +00:00
public void setFingerprintTrust ( String fingerprint , XmppAxolotlSession . Trust trust ) {
2015-07-15 14:32:42 +00:00
axolotlStore . setFingerprintTrust ( fingerprint , trust ) ;
2015-07-09 12:23:17 +00:00
}
2015-06-26 13:41:02 +00:00
2015-07-31 19:12:34 +00:00
private void buildSessionFromPEP ( final AxolotlAddress address ) {
2015-07-19 16:36:28 +00:00
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-31 16:05:32 +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 ( ) {
2015-07-31 16:05:32 +00:00
AxolotlAddress ownAddress = new AxolotlAddress ( account . getJid ( ) . toBareJid ( ) . toString ( ) , 0 ) ;
2015-07-19 16:36:28 +00:00
if ( ! fetchStatusMap . getAll ( ownAddress ) . containsValue ( FetchStatus . PENDING )
2015-07-22 13:02:53 +00:00
& & ! fetchStatusMap . getAll ( address ) . containsValue ( FetchStatus . PENDING ) ) {
2015-07-21 12:18:16 +00:00
mXmppConnectionService . keyStatusUpdated ( ) ;
2015-07-19 16:36:28 +00:00
}
}
2015-06-29 12:18:11 +00:00
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
2015-08-23 11:23:10 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Received preKey IQ packet, processing... " ) ;
final IqParser parser = mXmppConnectionService . getIqParser ( ) ;
final List < PreKeyBundle > preKeyBundleList = parser . preKeys ( packet ) ;
final PreKeyBundle bundle = parser . bundle ( packet ) ;
if ( preKeyBundleList . isEmpty ( ) | | bundle = = null ) {
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " preKey IQ packet invalid: " + packet ) ;
fetchStatusMap . put ( address , FetchStatus . ERROR ) ;
finish ( ) ;
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 ) ;
finish ( ) ;
return ;
}
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 ( ) ) ;
try {
SessionBuilder builder = new SessionBuilder ( axolotlStore , address ) ;
builder . process ( preKeyBundle ) ;
XmppAxolotlSession session = new XmppAxolotlSession ( account , axolotlStore , address , bundle . getIdentityKey ( ) . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ) ;
sessions . put ( address , session ) ;
fetchStatusMap . put ( address , FetchStatus . SUCCESS ) ;
} catch ( UntrustedIdentityException | InvalidKeyException e ) {
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Error building session for " + address + " : "
+ e . getClass ( ) . getName ( ) + " , " + e . getMessage ( ) ) ;
fetchStatusMap . put ( address , FetchStatus . ERROR ) ;
}
2015-07-19 16:36:28 +00:00
finish ( ) ;
2015-08-23 11:23:10 +00:00
} else {
2015-06-29 12:18:11 +00:00
fetchStatusMap . put ( address , FetchStatus . ERROR ) ;
2015-08-23 11:23:10 +00:00
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Error received while building session: " + packet . findChild ( " error " ) ) ;
2015-07-19 16:36:28 +00:00
finish ( ) ;
2015-06-29 12:18:11 +00:00
return ;
}
}
} ) ;
} catch ( InvalidJidException e ) {
2015-07-31 16:05:32 +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 < > ( ) ;
2015-07-31 16:05:32 +00:00
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 ) ;
2015-07-31 16:05:32 +00:00
if ( sessions . get ( address ) = = null ) {
2015-07-19 16:36:28 +00:00
IdentityKey identityKey = axolotlStore . loadSession ( address ) . getSessionState ( ) . getRemoteIdentityKey ( ) ;
2015-07-31 16:05:32 +00:00
if ( identityKey ! = null ) {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Already have session for " + address . toString ( ) + " , adding to cache... " ) ;
2015-07-19 16:36:28 +00:00
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
}
2015-07-31 16:05:32 +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 ) ;
2015-07-31 16:05:32 +00:00
if ( sessions . get ( address ) = = null ) {
2015-07-19 16:36:28 +00:00
IdentityKey identityKey = axolotlStore . loadSession ( address ) . getSessionState ( ) . getRemoteIdentityKey ( ) ;
2015-07-31 16:05:32 +00:00
if ( identityKey ! = null ) {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Already have session for " + address . toString ( ) + " , adding to cache... " ) ;
2015-07-19 16:36:28 +00:00
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 ;
}
2015-07-31 19:12:34 +00:00
public boolean createSessionsIfNeeded ( final Conversation conversation ) {
2015-07-19 16:36:28 +00:00
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-31 16:05:32 +00:00
if ( status = = null | | status = = FetchStatus . ERROR ) {
fetchStatusMap . put ( address , FetchStatus . PENDING ) ;
2015-07-31 19:12:34 +00:00
this . buildSessionFromPEP ( address ) ;
newSessions = true ;
} else if ( status = = FetchStatus . PENDING ) {
2015-07-31 16:05:32 +00:00
newSessions = true ;
2015-06-29 12:22:26 +00:00
} else {
2015-07-31 16:05:32 +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 ) {
2015-07-31 16:05:32 +00:00
AxolotlAddress ownAddress = new AxolotlAddress ( account . getJid ( ) . toBareJid ( ) . toString ( ) , 0 ) ;
AxolotlAddress foreignAddress = new AxolotlAddress ( conversation . getJid ( ) . toBareJid ( ) . toString ( ) , 0 ) ;
2015-07-19 16:36:28 +00:00
return fetchStatusMap . getAll ( ownAddress ) . containsValue ( FetchStatus . PENDING )
2015-07-31 16:05:32 +00:00
| | fetchStatusMap . getAll ( foreignAddress ) . containsValue ( FetchStatus . PENDING ) ;
2015-07-19 16:36:28 +00:00
}
2015-06-29 12:22:26 +00:00
@Nullable
2015-07-31 19:12:34 +00:00
private XmppAxolotlMessage buildHeader ( Contact contact ) {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage (
contact . getJid ( ) . toBareJid ( ) , getOwnDeviceId ( ) ) ;
2015-06-26 13:41:02 +00:00
2015-07-31 19:12:34 +00:00
Set < XmppAxolotlSession > contactSessions = findSessionsforContact ( contact ) ;
Set < XmppAxolotlSession > ownSessions = findOwnSessions ( ) ;
if ( contactSessions . isEmpty ( ) ) {
2015-06-29 12:22:26 +00:00
return null ;
}
2015-07-31 16:05:32 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Building axolotl foreign keyElements... " ) ;
2015-07-31 19:12:34 +00:00
for ( XmppAxolotlSession session : contactSessions ) {
2015-07-31 16:05:32 +00:00
Log . v ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + session . getRemoteAddress ( ) . toString ( ) ) ;
2015-07-31 19:12:34 +00:00
axolotlMessage . addDevice ( session ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-31 16:05:32 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Building axolotl own keyElements... " ) ;
2015-07-31 19:12:34 +00:00
for ( XmppAxolotlSession session : ownSessions ) {
2015-07-31 16:05:32 +00:00
Log . v ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + session . getRemoteAddress ( ) . toString ( ) ) ;
2015-07-31 19:12:34 +00:00
axolotlMessage . addDevice ( session ) ;
}
return axolotlMessage ;
}
@Nullable
public XmppAxolotlMessage encrypt ( Message message ) {
XmppAxolotlMessage axolotlMessage = buildHeader ( message . getContact ( ) ) ;
if ( axolotlMessage ! = null ) {
final String content ;
if ( message . hasFileOnRemoteHost ( ) ) {
content = message . getFileParams ( ) . url . toString ( ) ;
} else {
content = message . getBody ( ) ;
}
try {
axolotlMessage . encrypt ( content ) ;
} catch ( CryptoFailedException e ) {
Log . w ( Config . LOGTAG , getLogprefix ( account ) + " Failed to encrypt message: " + e . getMessage ( ) ) ;
return null ;
}
2015-06-26 13:41:02 +00:00
}
2015-06-29 12:22:26 +00:00
return axolotlMessage ;
}
2015-07-31 19:12:34 +00:00
public void preparePayloadMessage ( final Message message , final boolean delay ) {
2015-06-29 12:22:26 +00:00
executor . execute ( new Runnable ( ) {
@Override
public void run ( ) {
2015-07-20 21:13:28 +00:00
XmppAxolotlMessage axolotlMessage = encrypt ( message ) ;
if ( axolotlMessage = = null ) {
2015-06-29 12:22:26 +00:00
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-31 16:05:32 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Generated message, caching: " + message . getUuid ( ) ) ;
2015-07-20 21:13:28 +00:00
messageCache . put ( message . getUuid ( ) , axolotlMessage ) ;
2015-07-31 16:05:32 +00:00
mXmppConnectionService . resendMessage ( message , delay ) ;
2015-06-29 12:22:26 +00:00
}
}
} ) ;
}
2015-07-31 19:12:34 +00:00
public void prepareKeyTransportMessage ( final Contact contact , final OnMessageCreatedCallback onMessageCreatedCallback ) {
executor . execute ( new Runnable ( ) {
@Override
public void run ( ) {
XmppAxolotlMessage axolotlMessage = buildHeader ( contact ) ;
onMessageCreatedCallback . run ( axolotlMessage ) ;
2015-07-03 11:31:14 +00:00
}
2015-07-31 19:12:34 +00:00
} ) ;
2015-07-03 11:31:14 +00:00
}
2015-07-20 21:13:28 +00:00
public XmppAxolotlMessage fetchAxolotlMessageFromCache ( Message message ) {
XmppAxolotlMessage axolotlMessage = messageCache . get ( message . getUuid ( ) ) ;
if ( axolotlMessage ! = null ) {
2015-07-31 16:05:32 +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-31 16:05:32 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Cache miss: " + message . getUuid ( ) ) ;
2015-06-29 12:22:26 +00:00
}
2015-07-20 21:13:28 +00:00
return axolotlMessage ;
2015-06-26 13:41:02 +00:00
}
2015-07-31 21:28:09 +00:00
private XmppAxolotlSession recreateUncachedSession ( AxolotlAddress address ) {
IdentityKey identityKey = axolotlStore . loadSession ( address ) . getSessionState ( ) . getRemoteIdentityKey ( ) ;
return ( identityKey ! = null )
? new XmppAxolotlSession ( account , axolotlStore , address ,
identityKey . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) )
: null ;
}
private XmppAxolotlSession getReceivingSession ( XmppAxolotlMessage message ) {
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 ( ) ) ;
XmppAxolotlSession session = sessions . get ( senderAddress ) ;
if ( session = = null ) {
2015-07-31 16:05:32 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Account: " + account . getJid ( ) + " No axolotl session found while parsing received message " + message ) ;
2015-07-31 21:28:09 +00:00
session = recreateUncachedSession ( senderAddress ) ;
if ( session = = null ) {
2015-07-10 00:36:29 +00:00
session = new XmppAxolotlSession ( account , axolotlStore , senderAddress ) ;
}
2015-06-26 13:41:02 +00:00
}
2015-07-31 21:28:09 +00:00
return session ;
}
2015-06-26 13:41:02 +00:00
2015-07-31 21:28:09 +00:00
public XmppAxolotlMessage . XmppAxolotlPlaintextMessage processReceivingPayloadMessage ( XmppAxolotlMessage message ) {
XmppAxolotlMessage . XmppAxolotlPlaintextMessage plaintextMessage = null ;
XmppAxolotlSession session = getReceivingSession ( message ) ;
2015-07-31 19:12:34 +00:00
try {
plaintextMessage = message . decrypt ( session , getOwnDeviceId ( ) ) ;
Integer preKeyId = session . getPreKeyId ( ) ;
if ( preKeyId ! = null ) {
publishBundlesIfNeeded ( ) ;
session . resetPreKeyId ( ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-31 19:12:34 +00:00
} catch ( CryptoFailedException e ) {
Log . w ( Config . LOGTAG , getLogprefix ( account ) + " Failed to decrypt message: " + e . getMessage ( ) ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-31 21:28:09 +00:00
if ( session . isFresh ( ) & & plaintextMessage ! = null ) {
sessions . put ( session ) ;
2015-07-05 20:54:28 +00:00
}
2015-06-26 13:41:02 +00:00
return plaintextMessage ;
}
2015-07-31 21:28:09 +00:00
public XmppAxolotlMessage . XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage ( XmppAxolotlMessage message ) {
XmppAxolotlMessage . XmppAxolotlKeyTransportMessage keyTransportMessage = null ;
XmppAxolotlSession session = getReceivingSession ( message ) ;
keyTransportMessage = message . getParameters ( session , getOwnDeviceId ( ) ) ;
if ( session . isFresh ( ) & & keyTransportMessage ! = null ) {
sessions . put ( session ) ;
}
return keyTransportMessage ;
}
2015-05-29 09:17:26 +00:00
}