2015-05-29 09:17:26 +00:00
package eu.siacs.conversations.crypto.axolotl ;
2016-02-02 12:43:20 +00:00
import android.os.Bundle ;
2015-10-11 13:48:58 +00:00
import android.security.KeyChain ;
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-10-16 21:48:42 +00:00
import android.util.Pair ;
2015-05-29 09:17:26 +00:00
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-10-11 13:48:58 +00:00
import java.security.PrivateKey ;
2015-07-20 23:15:32 +00:00
import java.security.Security ;
2015-10-11 13:48:58 +00:00
import java.security.Signature ;
import java.security.cert.X509Certificate ;
2016-11-19 12:34:54 +00:00
import java.util.ArrayList ;
2015-06-29 12:18:11 +00:00
import java.util.Arrays ;
2016-11-19 12:34:54 +00:00
import java.util.Collection ;
import java.util.Collections ;
import java.util.Comparator ;
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 ;
2016-02-02 12:43:20 +00:00
import eu.siacs.conversations.utils.CryptoHelper ;
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 ;
2015-10-16 21:48:42 +00:00
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded ;
2015-06-25 14:58:24 +00:00
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
2015-10-16 21:48:42 +00:00
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
2015-05-29 09:17:26 +00:00
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 " ;
2016-05-12 12:20:11 +00:00
public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + " +notify " ;
2015-06-29 12:18:11 +00:00
public static final String PEP_BUNDLES = PEP_PREFIX + " .bundles " ;
2015-10-11 13:48:58 +00:00
public static final String PEP_VERIFICATION = PEP_PREFIX + " .verification " ;
2015-06-29 12:18:11 +00:00
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-08-25 16:41:30 +00:00
public static final int publishTriesThreshold = 3 ;
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-08-25 16:41:30 +00:00
private int numPublishTriesOnEmptyPep = 0 ;
private boolean pepBroken = false ;
2015-06-26 13:41:02 +00:00
2015-10-16 21:48:42 +00:00
@Override
public void onAdvancedStreamFeaturesAvailable ( Account account ) {
2016-05-25 19:55:01 +00:00
if ( Config . supportOmemo ( )
& & account . getXmppConnection ( ) ! = null
& & account . getXmppConnection ( ) . getFeatures ( ) . pep ( ) ) {
2015-10-16 21:48:42 +00:00
publishBundlesIfNeeded ( true , false ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : skipping OMEMO initialization " ) ;
}
}
2016-02-29 12:18:07 +00:00
public boolean fetchMapHasErrors ( List < Jid > jids ) {
for ( Jid jid : jids ) {
if ( deviceIds . get ( jid ) ! = null ) {
for ( Integer foreignId : this . deviceIds . get ( jid ) ) {
AxolotlAddress address = new AxolotlAddress ( jid . toString ( ) , foreignId ) ;
if ( fetchStatusMap . getAll ( address ) . containsValue ( FetchStatus . ERROR ) ) {
return true ;
}
2016-01-23 10:39:02 +00:00
}
}
}
return false ;
}
2016-11-17 19:09:42 +00:00
public void preVerifyFingerprint ( Contact contact , String fingerprint ) {
axolotlStore . preVerifyFingerprint ( contact . getAccount ( ) , contact . getJid ( ) . toBareJid ( ) . toPreppedString ( ) , fingerprint ) ;
}
2016-11-17 21:28:45 +00:00
public void preVerifyFingerprint ( Account account , String fingerprint ) {
axolotlStore . preVerifyFingerprint ( account , account . getJid ( ) . toBareJid ( ) . toPreppedString ( ) , fingerprint ) ;
}
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-12-19 14:44:11 +00:00
IdentityKey identityKey = store . loadSession ( axolotlAddress ) . getSessionState ( ) . getRemoteIdentityKey ( ) ;
2016-02-02 12:43:20 +00:00
if ( Config . X509_VERIFICATION ) {
X509Certificate certificate = store . getFingerprintCertificate ( identityKey . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ) ;
if ( certificate ! = null ) {
Bundle information = CryptoHelper . extractCertificateInformation ( certificate ) ;
try {
final String cn = information . getString ( " subject_cn " ) ;
final Jid jid = Jid . fromString ( bareJid ) ;
Log . d ( Config . LOGTAG , " setting common name for " + jid + " to " + cn ) ;
account . getRoster ( ) . getContact ( jid ) . setCommonName ( cn ) ;
} catch ( final InvalidJidException ignored ) {
//ignored
}
}
}
2015-12-19 14:44:11 +00:00
this . put ( axolotlAddress , new XmppAxolotlSession ( account , store , axolotlAddress , identityKey ) ) ;
2015-07-21 11:51:15 +00:00
}
}
2015-07-10 00:36:29 +00:00
private void fillMap ( SQLiteAxolotlStore store ) {
2016-10-20 15:31:46 +00:00
List < Integer > deviceIds = store . getSubDeviceSessions ( account . getJid ( ) . toBareJid ( ) . toPreppedString ( ) ) ;
putDevicesForJid ( account . getJid ( ) . toBareJid ( ) . toPreppedString ( ) , 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 ( ) ;
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 ( ) ;
2016-11-17 19:09:42 +00:00
xmppConnectionService . syncRosterToDisk ( account ) ; //TODO why?
2015-07-10 00:36:29 +00:00
}
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-10-17 12:09:26 +00:00
public enum FetchStatus {
2015-06-29 12:22:26 +00:00
PENDING ,
SUCCESS ,
2015-10-17 12:09:26 +00:00
SUCCESS_VERIFIED ,
2015-10-17 13:51:21 +00:00
TIMEOUT ,
2015-06-29 12:22:26 +00:00
ERROR
}
private static class FetchStatusMap extends AxolotlAddressMap < FetchStatus > {
2015-06-26 13:41:02 +00:00
2016-05-19 08:47:27 +00:00
public void clearErrorFor ( Jid jid ) {
synchronized ( MAP_LOCK ) {
2016-10-20 15:31:46 +00:00
Map < Integer , FetchStatus > devices = this . map . get ( jid . toBareJid ( ) . toPreppedString ( ) ) ;
2016-05-19 08:47:27 +00:00
if ( devices = = null ) {
return ;
}
for ( Map . Entry < Integer , FetchStatus > entry : devices . entrySet ( ) ) {
if ( entry . getValue ( ) = = FetchStatus . ERROR ) {
Log . d ( Config . LOGTAG , " resetting error for " + jid . toBareJid ( ) + " ( " + entry . getKey ( ) + " ) " ) ;
entry . setValue ( FetchStatus . TIMEOUT ) ;
}
}
}
}
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-09-06 13:08:42 +00:00
public String getOwnFingerprint ( ) {
return axolotlStore . getIdentityKeyPair ( ) . getPublicKey ( ) . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ;
2015-07-07 17:36:22 +00:00
}
2016-11-14 21:27:41 +00:00
public Set < IdentityKey > getKeysWithTrust ( FingerprintStatus status ) {
return axolotlStore . getContactKeysWithTrust ( account . getJid ( ) . toBareJid ( ) . toPreppedString ( ) , status ) ;
2015-07-19 16:36:28 +00:00
}
2016-11-14 21:27:41 +00:00
public Set < IdentityKey > getKeysWithTrust ( FingerprintStatus status , Jid jid ) {
return axolotlStore . getContactKeysWithTrust ( jid . toBareJid ( ) . toPreppedString ( ) , status ) ;
2016-02-29 12:18:07 +00:00
}
2016-11-14 21:27:41 +00:00
public Set < IdentityKey > getKeysWithTrust ( FingerprintStatus status , List < Jid > jids ) {
2016-02-29 12:18:07 +00:00
Set < IdentityKey > keys = new HashSet < > ( ) ;
for ( Jid jid : jids ) {
2016-11-14 21:27:41 +00:00
keys . addAll ( axolotlStore . getContactKeysWithTrust ( jid . toPreppedString ( ) , status ) ) ;
2016-02-29 12:18:07 +00:00
}
return keys ;
2015-07-19 16:36:28 +00:00
}
2016-02-29 12:18:07 +00:00
public long getNumTrustedKeys ( Jid jid ) {
2016-10-20 15:31:46 +00:00
return axolotlStore . getContactNumTrustedKeys ( jid . toBareJid ( ) . toPreppedString ( ) ) ;
2016-02-29 12:18:07 +00:00
}
public boolean anyTargetHasNoTrustedKeys ( List < Jid > jids ) {
for ( Jid jid : jids ) {
2016-10-20 15:31:46 +00:00
if ( axolotlStore . getContactNumTrustedKeys ( jid . toBareJid ( ) . toPreppedString ( ) ) = = 0 ) {
2016-02-29 12:18:07 +00:00
return true ;
}
}
return false ;
2015-07-20 12:56:41 +00:00
}
2015-06-26 13:41:02 +00:00
private AxolotlAddress getAddressForJid ( Jid jid ) {
2016-10-20 15:31:46 +00:00
return new AxolotlAddress ( jid . toPreppedString ( ) , 0 ) ;
2015-06-26 13:41:02 +00:00
}
2016-11-19 12:34:54 +00:00
public Collection < XmppAxolotlSession > findOwnSessions ( ) {
2015-06-29 12:19:17 +00:00
AxolotlAddress ownAddress = getAddressForJid ( account . getJid ( ) . toBareJid ( ) ) ;
2016-11-19 12:34:54 +00:00
ArrayList < XmppAxolotlSession > s = new ArrayList < > ( this . sessions . getAll ( ownAddress ) . values ( ) ) ;
Collections . sort ( s ) ;
return s ;
2015-06-26 13:41:02 +00:00
}
2016-11-17 21:28:45 +00:00
2016-11-19 12:34:54 +00:00
public Collection < XmppAxolotlSession > findSessionsForContact ( Contact contact ) {
2015-06-26 13:41:02 +00:00
AxolotlAddress contactAddress = getAddressForJid ( contact . getJid ( ) ) ;
2016-11-19 12:34:54 +00:00
ArrayList < XmppAxolotlSession > s = new ArrayList < > ( this . sessions . getAll ( contactAddress ) . values ( ) ) ;
Collections . sort ( s ) ;
return s ;
2015-06-26 13:41:02 +00:00
}
2016-02-29 12:18:07 +00:00
private Set < XmppAxolotlSession > findSessionsForConversation ( Conversation conversation ) {
HashSet < XmppAxolotlSession > sessions = new HashSet < > ( ) ;
2016-03-01 10:26:59 +00:00
for ( Jid jid : conversation . getAcceptedCryptoTargets ( ) ) {
2016-02-29 12:18:07 +00:00
sessions . addAll ( this . sessions . getAll ( getAddressForJid ( jid ) ) . values ( ) ) ;
}
return sessions ;
}
private boolean hasAny ( Jid jid ) {
return sessions . hasAny ( getAddressForJid ( jid ) ) ;
2015-06-26 13:41:02 +00:00
}
2015-09-06 17:40:28 +00:00
public boolean isPepBroken ( ) {
return this . pepBroken ;
}
2016-04-05 11:31:03 +00:00
public void resetBrokenness ( ) {
this . pepBroken = false ;
numPublishTriesOnEmptyPep = 0 ;
}
2016-05-19 08:47:27 +00:00
public void clearErrorsInFetchStatusMap ( Jid jid ) {
fetchStatusMap . clearErrorFor ( jid ) ;
}
2015-10-11 13:48:58 +00:00
public void regenerateKeys ( boolean wipeOther ) {
2015-07-07 17:36:22 +00:00
axolotlStore . regenerate ( ) ;
2015-07-10 00:18:01 +00:00
sessions . clear ( ) ;
fetchStatusMap . clear ( ) ;
2015-10-11 13:48:58 +00:00
publishBundlesIfNeeded ( true , wipeOther ) ;
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-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-08-25 16:41:30 +00:00
if ( ! deviceIds . isEmpty ( ) ) {
2016-05-04 08:29:29 +00:00
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Received non-empty own device list. Resetting publish attempts and pepBroken status. " ) ;
2015-08-25 16:41:30 +00:00
pepBroken = false ;
numPublishTriesOnEmptyPep = 0 ;
}
2015-07-22 13:02:53 +00:00
if ( deviceIds . contains ( getOwnDeviceId ( ) ) ) {
deviceIds . remove ( getOwnDeviceId ( ) ) ;
2015-08-25 10:17:09 +00:00
} else {
publishOwnDeviceId ( deviceIds ) ;
2015-07-22 13:02:53 +00:00
}
2015-07-31 16:05:32 +00:00
for ( Integer deviceId : deviceIds ) {
2016-10-20 15:31:46 +00:00
AxolotlAddress ownDeviceAddress = new AxolotlAddress ( jid . toBareJid ( ) . toPreppedString ( ) , deviceId ) ;
2015-07-31 16:05:32 +00:00
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
}
2016-10-20 15:31:46 +00:00
Set < Integer > expiredDevices = new HashSet < > ( axolotlStore . getSubDeviceSessions ( jid . toBareJid ( ) . toPreppedString ( ) ) ) ;
2015-07-21 12:18:16 +00:00
expiredDevices . removeAll ( deviceIds ) ;
2016-11-14 21:27:41 +00:00
for ( Integer deviceId : expiredDevices ) {
AxolotlAddress address = new AxolotlAddress ( jid . toBareJid ( ) . toPreppedString ( ) , deviceId ) ;
XmppAxolotlSession session = sessions . get ( address ) ;
if ( session ! = null & & session . getFingerprint ( ) ! = null ) {
if ( session . getTrust ( ) . isActive ( ) ) {
session . setTrust ( session . getTrust ( ) . toInactive ( ) ) ;
}
}
}
2015-07-21 12:18:16 +00:00
Set < Integer > newDevices = new HashSet < > ( deviceIds ) ;
2016-11-14 21:27:41 +00:00
for ( Integer deviceId : newDevices ) {
AxolotlAddress address = new AxolotlAddress ( jid . toBareJid ( ) . toPreppedString ( ) , deviceId ) ;
XmppAxolotlSession session = sessions . get ( address ) ;
if ( session ! = null & & session . getFingerprint ( ) ! = null ) {
if ( ! session . getTrust ( ) . isActive ( ) ) {
session . setTrust ( session . getTrust ( ) . toActive ( ) ) ;
}
}
}
2015-06-29 12:18:11 +00:00
this . deviceIds . put ( jid , deviceIds ) ;
2015-10-17 12:09:26 +00:00
mXmppConnectionService . keyStatusUpdated ( null ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-07 17:36:22 +00:00
public void wipeOtherPepDevices ( ) {
2015-08-25 16:41:30 +00:00
if ( pepBroken ) {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " wipeOtherPepDevices called, but PEP is broken. Ignoring... " ) ;
return ;
}
2015-07-07 17:36:22 +00:00
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 ) ;
2016-11-18 20:49:52 +00:00
mXmppConnectionService . sendIqPacket ( account , publish , null ) ;
2015-07-07 17:36:22 +00:00
}
2015-09-06 13:08:42 +00:00
public void purgeKey ( final String fingerprint ) {
2016-11-17 19:09:42 +00:00
axolotlStore . setFingerprintStatus ( fingerprint . replaceAll ( " \\ s " , " " ) , FingerprintStatus . createCompromised ( ) ) ;
2015-07-20 20:18:24 +00:00
}
2015-06-26 13:41:02 +00:00
public void publishOwnDeviceIdIfNeeded ( ) {
2015-08-25 16:41:30 +00:00
if ( pepBroken ) {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... " ) ;
return ;
}
2015-06-26 13:41:02 +00:00
IqPacket packet = mXmppConnectionService . getIqGenerator ( ) . retrieveDeviceIds ( account . getJid ( ) . toBareJid ( ) ) ;
mXmppConnectionService . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
2015-08-26 18:51:18 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . TIMEOUT ) {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Timeout received while retrieving own Device Ids. " ) ;
} else {
2015-08-23 11:23:10 +00:00
Element item = mXmppConnectionService . getIqParser ( ) . getItem ( packet ) ;
Set < Integer > deviceIds = mXmppConnectionService . getIqParser ( ) . deviceIds ( item ) ;
if ( ! deviceIds . contains ( getOwnDeviceId ( ) ) ) {
2015-08-25 10:17:09 +00:00
publishOwnDeviceId ( deviceIds ) ;
2015-08-23 11:23:10 +00:00
}
2015-06-26 13:41:02 +00:00
}
}
} ) ;
}
2015-08-25 10:17:09 +00:00
public void publishOwnDeviceId ( Set < Integer > deviceIds ) {
2015-09-05 15:25:46 +00:00
Set < Integer > deviceIdsCopy = new HashSet < > ( deviceIds ) ;
if ( ! deviceIdsCopy . contains ( getOwnDeviceId ( ) ) ) {
2015-08-25 16:41:30 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Own device " + getOwnDeviceId ( ) + " not in PEP devicelist. " ) ;
2015-09-05 15:25:46 +00:00
if ( deviceIdsCopy . isEmpty ( ) ) {
2015-08-25 16:41:30 +00:00
if ( numPublishTriesOnEmptyPep > = publishTriesThreshold ) {
Log . w ( Config . LOGTAG , getLogprefix ( account ) + " Own device publish attempt threshold exceeded, aborting... " ) ;
pepBroken = true ;
return ;
} else {
numPublishTriesOnEmptyPep + + ;
Log . w ( Config . LOGTAG , getLogprefix ( account ) + " Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + " ) " ) ;
2015-08-25 10:17:09 +00:00
}
2015-08-25 16:41:30 +00:00
} else {
numPublishTriesOnEmptyPep = 0 ;
2015-08-25 10:17:09 +00:00
}
2015-09-05 15:25:46 +00:00
deviceIdsCopy . add ( getOwnDeviceId ( ) ) ;
IqPacket publish = mXmppConnectionService . getIqGenerator ( ) . publishDeviceIds ( deviceIdsCopy ) ;
2015-08-25 16:41:30 +00:00
mXmppConnectionService . sendIqPacket ( account , publish , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
2016-04-05 11:31:03 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . ERROR ) {
pepBroken = true ;
2015-08-25 16:41:30 +00:00
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Error received while publishing own device id " + packet . findChild ( " error " ) ) ;
}
}
} ) ;
}
2015-08-25 10:17:09 +00:00
}
2015-10-11 13:48:58 +00:00
public void publishDeviceVerificationAndBundle ( final SignedPreKeyRecord signedPreKeyRecord ,
final Set < PreKeyRecord > preKeyRecords ,
final boolean announceAfter ,
final boolean wipe ) {
try {
IdentityKey axolotlPublicKey = axolotlStore . getIdentityKeyPair ( ) . getPublicKey ( ) ;
PrivateKey x509PrivateKey = KeyChain . getPrivateKey ( mXmppConnectionService , account . getPrivateKeyAlias ( ) ) ;
X509Certificate [ ] chain = KeyChain . getCertificateChain ( mXmppConnectionService , account . getPrivateKeyAlias ( ) ) ;
Signature verifier = Signature . getInstance ( " sha256WithRSA " ) ;
verifier . initSign ( x509PrivateKey , mXmppConnectionService . getRNG ( ) ) ;
verifier . update ( axolotlPublicKey . serialize ( ) ) ;
byte [ ] signature = verifier . sign ( ) ;
IqPacket packet = mXmppConnectionService . getIqGenerator ( ) . publishVerification ( signature , chain , getOwnDeviceId ( ) ) ;
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " : publish verification for device " + getOwnDeviceId ( ) ) ;
mXmppConnectionService . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
publishDeviceBundle ( signedPreKeyRecord , preKeyRecords , announceAfter , wipe ) ;
}
} ) ;
2015-10-12 10:36:54 +00:00
} catch ( Exception e ) {
2015-10-11 13:48:58 +00:00
e . printStackTrace ( ) ;
}
}
public void publishBundlesIfNeeded ( final boolean announce , final boolean wipe ) {
2015-08-25 22:27:39 +00:00
if ( pepBroken ) {
2015-08-25 16:41:30 +00:00
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " publishBundlesIfNeeded called, but PEP is broken. Ignoring... " ) ;
return ;
}
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-09-17 12:18:06 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . TIMEOUT ) {
return ; //ignore timeout. do nothing
}
if ( packet . getType ( ) = = IqPacket . TYPE . ERROR ) {
Element error = packet . findChild ( " error " ) ;
if ( error = = null | | ! error . hasChild ( " item-not-found " ) ) {
pepBroken = true ;
Log . w ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " request for device bundles came back with something other than item-not-found " + packet ) ;
return ;
}
}
2015-08-25 22:27:39 +00:00
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 ) ;
}
try {
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 ;
2015-07-03 11:20:27 +00:00
}
2015-08-23 11:23:10 +00:00
2015-08-25 22:27:39 +00:00
// 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 ( ) ) ) {
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-08-25 22:27:39 +00:00
} catch ( InvalidKeyIdException e ) {
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 ;
}
2015-06-29 12:18:11 +00:00
2015-08-25 22:27:39 +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 ) ;
2015-07-03 11:20:27 +00:00
}
2015-08-25 22:27:39 +00:00
} catch ( InvalidKeyIdException ignored ) {
2015-07-03 11:20:27 +00:00
}
}
2015-08-25 22:27:39 +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 ) ;
2015-06-29 12:18:11 +00:00
}
2015-08-25 22:27:39 +00:00
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-25 22:27:39 +00:00
if ( changed ) {
2015-10-12 11:18:20 +00:00
if ( account . getPrivateKeyAlias ( ) ! = null & & Config . X509_VERIFICATION ) {
2015-10-29 12:41:08 +00:00
mXmppConnectionService . publishDisplayName ( account ) ;
2015-10-11 13:48:58 +00:00
publishDeviceVerificationAndBundle ( signedPreKeyRecord , preKeyRecords , announce , wipe ) ;
2015-10-12 11:18:20 +00:00
} else {
publishDeviceBundle ( signedPreKeyRecord , preKeyRecords , announce , wipe ) ;
2015-10-11 13:48:58 +00:00
}
2015-08-26 13:32:49 +00:00
} else {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Bundle " + getOwnDeviceId ( ) + " in PEP was current " ) ;
2015-10-11 13:48:58 +00:00
if ( wipe ) {
wipeOtherPepDevices ( ) ;
} else if ( announce ) {
2015-08-26 13:32:49 +00:00
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Announcing device " + getOwnDeviceId ( ) ) ;
publishOwnDeviceIdIfNeeded ( ) ;
}
2015-07-03 11:20:27 +00:00
}
2015-08-25 22:27:39 +00:00
} catch ( InvalidKeyException e ) {
Log . e ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Failed to publish bundle " + getOwnDeviceId ( ) + " , reason: " + e . getMessage ( ) ) ;
2015-06-26 13:41:02 +00:00
}
}
} ) ;
}
2015-10-11 13:48:58 +00:00
private void publishDeviceBundle ( SignedPreKeyRecord signedPreKeyRecord ,
Set < PreKeyRecord > preKeyRecords ,
final boolean announceAfter ,
final boolean wipe ) {
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 ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Successfully published bundle. " ) ;
if ( wipe ) {
wipeOtherPepDevices ( ) ;
} else if ( announceAfter ) {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Announcing device " + getOwnDeviceId ( ) ) ;
publishOwnDeviceIdIfNeeded ( ) ;
}
2016-04-05 11:31:03 +00:00
} else if ( packet . getType ( ) = = IqPacket . TYPE . ERROR ) {
pepBroken = true ;
2015-10-11 13:48:58 +00:00
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " Error received while publishing bundle: " + packet . findChild ( " error " ) ) ;
}
}
} ) ;
}
2016-09-08 09:01:27 +00:00
public enum AxolotlCapability {
FULL ,
MISSING_PRESENCE ,
MISSING_KEYS ,
WRONG_CONFIGURATION ,
NO_MEMBERS
}
2016-02-29 12:18:07 +00:00
public boolean isConversationAxolotlCapable ( Conversation conversation ) {
2016-09-08 09:01:27 +00:00
return isConversationAxolotlCapableDetailed ( conversation ) . first = = AxolotlCapability . FULL ;
}
public Pair < AxolotlCapability , Jid > isConversationAxolotlCapableDetailed ( Conversation conversation ) {
2016-10-03 19:04:10 +00:00
if ( conversation . getMode ( ) = = Conversation . MODE_SINGLE
| | ( conversation . getMucOptions ( ) . membersOnly ( ) & & conversation . getMucOptions ( ) . nonanonymous ( ) ) ) {
2016-10-03 08:42:43 +00:00
final List < Jid > jids = getCryptoTargets ( conversation ) ;
for ( Jid jid : jids ) {
if ( ! hasAny ( jid ) & & ( ! deviceIds . containsKey ( jid ) | | deviceIds . get ( jid ) . isEmpty ( ) ) ) {
2016-10-07 08:05:08 +00:00
if ( conversation . getAccount ( ) . getRoster ( ) . getContact ( jid ) . mutualPresenceSubscription ( ) ) {
2016-10-03 08:42:43 +00:00
return new Pair < > ( AxolotlCapability . MISSING_KEYS , jid ) ;
} else {
return new Pair < > ( AxolotlCapability . MISSING_PRESENCE , jid ) ;
}
2016-09-08 09:01:27 +00:00
}
}
2016-10-03 08:42:43 +00:00
if ( jids . size ( ) > 0 ) {
return new Pair < > ( AxolotlCapability . FULL , null ) ;
2016-09-08 09:01:27 +00:00
} else {
2016-10-03 08:42:43 +00:00
return new Pair < > ( AxolotlCapability . NO_MEMBERS , null ) ;
2016-02-29 12:18:07 +00:00
}
2016-10-03 08:42:43 +00:00
} else {
return new Pair < > ( AxolotlCapability . WRONG_CONFIGURATION , null ) ;
2016-02-29 12:18:07 +00:00
}
}
public List < Jid > getCryptoTargets ( Conversation conversation ) {
final List < Jid > jids ;
if ( conversation . getMode ( ) = = Conversation . MODE_SINGLE ) {
jids = Arrays . asList ( conversation . getJid ( ) . toBareJid ( ) ) ;
} else {
jids = conversation . getMucOptions ( ) . getMembers ( ) ;
}
return jids ;
2015-06-26 13:41:02 +00:00
}
2015-07-31 16:05:32 +00:00
2016-11-14 21:27:41 +00:00
public FingerprintStatus getFingerprintTrust ( String fingerprint ) {
return axolotlStore . getFingerprintStatus ( fingerprint ) ;
2015-07-09 12:23:17 +00:00
}
2015-12-23 18:18:53 +00:00
public X509Certificate getFingerprintCertificate ( String fingerprint ) {
return axolotlStore . getFingerprintCertificate ( fingerprint ) ;
}
2016-11-14 21:27:41 +00:00
public void setFingerprintTrust ( String fingerprint , FingerprintStatus status ) {
2016-11-17 19:09:42 +00:00
axolotlStore . setFingerprintStatus ( fingerprint , status ) ;
2015-07-09 12:23:17 +00:00
}
2015-06-26 13:41:02 +00:00
2015-12-19 14:44:11 +00:00
private void verifySessionWithPEP ( final XmppAxolotlSession session ) {
Log . d ( Config . LOGTAG , " trying to verify fresh session ( " + session . getRemoteAddress ( ) . getName ( ) + " ) with pep " ) ;
2015-10-16 21:48:42 +00:00
final AxolotlAddress address = session . getRemoteAddress ( ) ;
2015-12-19 14:44:11 +00:00
final IdentityKey identityKey = session . getIdentityKey ( ) ;
2015-10-16 21:48:42 +00:00
try {
IqPacket packet = mXmppConnectionService . getIqGenerator ( ) . retrieveVerificationForDevice ( Jid . fromString ( address . getName ( ) ) , address . getDeviceId ( ) ) ;
mXmppConnectionService . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
Pair < X509Certificate [ ] , byte [ ] > verification = mXmppConnectionService . getIqParser ( ) . verification ( packet ) ;
if ( verification ! = null ) {
try {
Signature verifier = Signature . getInstance ( " sha256WithRSA " ) ;
verifier . initVerify ( verification . first [ 0 ] ) ;
verifier . update ( identityKey . serialize ( ) ) ;
if ( verifier . verify ( verification . second ) ) {
try {
mXmppConnectionService . getMemorizingTrustManager ( ) . getNonInteractive ( ) . checkClientTrusted ( verification . first , " RSA " ) ;
2015-12-23 16:41:26 +00:00
String fingerprint = session . getFingerprint ( ) ;
Log . d ( Config . LOGTAG , " verified session with x.509 signature. fingerprint was: " + fingerprint ) ;
2016-11-14 21:27:41 +00:00
setFingerprintTrust ( fingerprint , FingerprintStatus . createActiveVerified ( true ) ) ;
2015-12-23 16:41:26 +00:00
axolotlStore . setFingerprintCertificate ( fingerprint , verification . first [ 0 ] ) ;
2015-10-17 12:09:26 +00:00
fetchStatusMap . put ( address , FetchStatus . SUCCESS_VERIFIED ) ;
2016-02-02 12:43:20 +00:00
Bundle information = CryptoHelper . extractCertificateInformation ( verification . first [ 0 ] ) ;
try {
final String cn = information . getString ( " subject_cn " ) ;
final Jid jid = Jid . fromString ( address . getName ( ) ) ;
Log . d ( Config . LOGTAG , " setting common name for " + jid + " to " + cn ) ;
account . getRoster ( ) . getContact ( jid ) . setCommonName ( cn ) ;
} catch ( final InvalidJidException ignored ) {
//ignored
}
2015-10-17 12:09:26 +00:00
finishBuildingSessionsFromPEP ( address ) ;
return ;
2015-10-16 21:48:42 +00:00
} catch ( Exception e ) {
Log . d ( Config . LOGTAG , " could not verify certificate " ) ;
}
}
} catch ( Exception e ) {
Log . d ( Config . LOGTAG , " error during verification " + e . getMessage ( ) ) ;
}
2015-12-19 11:44:55 +00:00
} else {
Log . d ( Config . LOGTAG , " no verification found " ) ;
2015-10-16 21:48:42 +00:00
}
2015-10-17 12:09:26 +00:00
fetchStatusMap . put ( address , FetchStatus . SUCCESS ) ;
2015-10-16 21:48:42 +00:00
finishBuildingSessionsFromPEP ( address ) ;
}
} ) ;
} catch ( InvalidJidException e ) {
2015-10-17 12:09:26 +00:00
fetchStatusMap . put ( address , FetchStatus . SUCCESS ) ;
2015-10-16 21:48:42 +00:00
finishBuildingSessionsFromPEP ( address ) ;
}
}
private void finishBuildingSessionsFromPEP ( final AxolotlAddress address ) {
2016-10-20 15:31:46 +00:00
AxolotlAddress ownAddress = new AxolotlAddress ( account . getJid ( ) . toBareJid ( ) . toPreppedString ( ) , 0 ) ;
2016-11-17 19:09:42 +00:00
Map < Integer , FetchStatus > own = fetchStatusMap . getAll ( ownAddress ) ;
Map < Integer , FetchStatus > remote = fetchStatusMap . getAll ( address ) ;
if ( ! own . containsValue ( FetchStatus . PENDING ) & & ! remote . containsValue ( FetchStatus . PENDING ) ) {
2015-10-17 12:09:26 +00:00
FetchStatus report = null ;
2016-11-17 19:09:42 +00:00
if ( own . containsValue ( FetchStatus . SUCCESS ) | | remote . containsValue ( FetchStatus . SUCCESS ) ) {
report = FetchStatus . SUCCESS ;
} else if ( own . containsValue ( FetchStatus . SUCCESS_VERIFIED ) | | remote . containsValue ( FetchStatus . SUCCESS_VERIFIED ) ) {
2015-10-17 12:09:26 +00:00
report = FetchStatus . SUCCESS_VERIFIED ;
2016-11-17 19:09:42 +00:00
} else if ( own . containsValue ( FetchStatus . ERROR ) | | remote . containsValue ( FetchStatus . ERROR ) ) {
2015-10-17 12:09:26 +00:00
report = FetchStatus . ERROR ;
}
mXmppConnectionService . keyStatusUpdated ( report ) ;
2015-10-16 21:48:42 +00:00
}
}
2015-07-31 19:12:34 +00:00
private void buildSessionFromPEP ( final AxolotlAddress address ) {
2015-09-05 15:25:46 +00:00
Log . i ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Building new sesstion for " + address . toString ( ) ) ;
if ( address . getDeviceId ( ) = = getOwnDeviceId ( ) ) {
throw new AssertionError ( " We should NEVER build a session with ourselves. What happened here?! " ) ;
}
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
2015-06-29 12:18:11 +00:00
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
2015-10-17 13:51:21 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE . TIMEOUT ) {
fetchStatusMap . put ( address , FetchStatus . TIMEOUT ) ;
} else if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
2015-08-23 11:23:10 +00:00
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 ) ;
2015-10-16 21:48:42 +00:00
finishBuildingSessionsFromPEP ( address ) ;
2015-08-23 11:23:10 +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-10-16 21:48:42 +00:00
finishBuildingSessionsFromPEP ( address ) ;
2015-08-23 11:23:10 +00:00
return ;
}
final PreKeyBundle preKeyBundle = new PreKeyBundle ( 0 , address . getDeviceId ( ) ,
preKey . getPreKeyId ( ) , preKey . getPreKey ( ) ,
bundle . getSignedPreKeyId ( ) , bundle . getSignedPreKey ( ) ,
bundle . getSignedPreKeySignature ( ) , bundle . getIdentityKey ( ) ) ;
try {
SessionBuilder builder = new SessionBuilder ( axolotlStore , address ) ;
builder . process ( preKeyBundle ) ;
2015-12-19 14:44:11 +00:00
XmppAxolotlSession session = new XmppAxolotlSession ( account , axolotlStore , address , bundle . getIdentityKey ( ) ) ;
2015-08-23 11:23:10 +00:00
sessions . put ( address , session ) ;
2015-10-16 21:48:42 +00:00
if ( Config . X509_VERIFICATION ) {
2015-12-19 14:44:11 +00:00
verifySessionWithPEP ( session ) ;
2015-10-16 21:48:42 +00:00
} else {
2016-11-17 19:09:42 +00:00
FingerprintStatus status = getFingerprintTrust ( bundle . getIdentityKey ( ) . getFingerprint ( ) . replaceAll ( " \\ s " , " " ) ) ;
boolean verified = status ! = null & & status . isVerified ( ) ;
fetchStatusMap . put ( address , verified ? FetchStatus . SUCCESS_VERIFIED : FetchStatus . SUCCESS ) ;
2015-10-16 21:48:42 +00:00
finishBuildingSessionsFromPEP ( address ) ;
}
2015-08-23 11:23:10 +00:00
} 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-10-16 21:48:42 +00:00
finishBuildingSessionsFromPEP ( address ) ;
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-10-16 21:48:42 +00:00
finishBuildingSessionsFromPEP ( address ) ;
2015-06-29 12:18:11 +00:00
}
}
} ) ;
} 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 ) {
2015-06-29 12:22:26 +00:00
Set < AxolotlAddress > addresses = new HashSet < > ( ) ;
2016-02-29 12:18:07 +00:00
for ( Jid jid : getCryptoTargets ( conversation ) ) {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Finding devices without session for " + jid ) ;
if ( deviceIds . get ( jid ) ! = null ) {
for ( Integer foreignId : this . deviceIds . get ( jid ) ) {
AxolotlAddress address = new AxolotlAddress ( jid . 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 ) ;
sessions . put ( address , session ) ;
2015-10-17 13:51:21 +00:00
} else {
2016-02-29 12:18:07 +00:00
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Found device " + jid + " : " + foreignId ) ;
if ( fetchStatusMap . get ( address ) ! = FetchStatus . ERROR ) {
addresses . add ( address ) ;
} else {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " skipping over " + address + " because it's broken " ) ;
}
2015-10-17 13:51:21 +00:00
}
2015-07-19 16:36:28 +00:00
}
}
2016-02-29 12:18:07 +00:00
} else {
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 ( ) ) ) {
2016-10-20 15:31:46 +00:00
AxolotlAddress address = new AxolotlAddress ( account . getJid ( ) . toBareJid ( ) . toPreppedString ( ) , 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-12-19 14:44:11 +00:00
XmppAxolotlSession session = new XmppAxolotlSession ( account , axolotlStore , address , identityKey ) ;
2015-07-19 16:36:28 +00:00
sessions . put ( address , session ) ;
} else {
Log . d ( Config . LOGTAG , AxolotlService . getLogprefix ( account ) + " Found device " + account . getJid ( ) . toBareJid ( ) + " : " + ownId ) ;
2015-10-17 13:51:21 +00:00
if ( fetchStatusMap . get ( address ) ! = FetchStatus . ERROR ) {
addresses . add ( address ) ;
} else {
Log . d ( Config . LOGTAG , getLogprefix ( account ) + " skipping over " + address + " because it's broken " ) ;
}
2015-07-19 16:36:28 +00:00
}
}
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-10-17 13:51:21 +00:00
if ( status = = null | | status = = FetchStatus . TIMEOUT ) {
2015-07-31 16:05:32 +00:00
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-10-31 21:55:04 +00:00
public boolean trustedSessionVerified ( final Conversation conversation ) {
2016-02-29 12:18:07 +00:00
Set < XmppAxolotlSession > sessions = findSessionsForConversation ( conversation ) ;
2015-10-31 21:55:04 +00:00
sessions . addAll ( findOwnSessions ( ) ) ;
boolean verified = false ;
for ( XmppAxolotlSession session : sessions ) {
2016-11-14 21:27:41 +00:00
if ( session . getTrust ( ) . isTrustedAndActive ( ) ) {
if ( session . getTrust ( ) . getTrust ( ) = = FingerprintStatus . Trust . VERIFIED_X509 ) {
2015-10-31 21:55:04 +00:00
verified = true ;
} else {
return false ;
}
}
}
return verified ;
}
2016-02-29 12:18:07 +00:00
public boolean hasPendingKeyFetches ( Account account , List < Jid > jids ) {
2016-10-20 15:31:46 +00:00
AxolotlAddress ownAddress = new AxolotlAddress ( account . getJid ( ) . toBareJid ( ) . toPreppedString ( ) , 0 ) ;
2016-02-29 12:18:07 +00:00
if ( fetchStatusMap . getAll ( ownAddress ) . containsValue ( FetchStatus . PENDING ) ) {
return true ;
}
for ( Jid jid : jids ) {
2016-10-20 15:31:46 +00:00
AxolotlAddress foreignAddress = new AxolotlAddress ( jid . toBareJid ( ) . toPreppedString ( ) , 0 ) ;
2016-02-29 12:18:07 +00:00
if ( fetchStatusMap . getAll ( foreignAddress ) . containsValue ( FetchStatus . PENDING ) ) {
return true ;
}
}
return false ;
2015-07-19 16:36:28 +00:00
}
2015-06-29 12:22:26 +00:00
@Nullable
2016-02-29 12:18:07 +00:00
private XmppAxolotlMessage buildHeader ( Conversation conversation ) {
2015-07-31 19:12:34 +00:00
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage (
2016-02-29 12:18:07 +00:00
account . getJid ( ) . toBareJid ( ) , getOwnDeviceId ( ) ) ;
2015-06-26 13:41:02 +00:00
2016-02-29 12:18:07 +00:00
Set < XmppAxolotlSession > remoteSessions = findSessionsForConversation ( conversation ) ;
2016-11-19 12:34:54 +00:00
Collection < XmppAxolotlSession > ownSessions = findOwnSessions ( ) ;
2016-02-29 12:18:07 +00:00
if ( remoteSessions . isEmpty ( ) ) {
2015-06-29 12:22:26 +00:00
return null ;
}
2016-02-29 12:18:07 +00:00
for ( XmppAxolotlSession session : remoteSessions ) {
2015-07-31 19:12:34 +00:00
axolotlMessage . addDevice ( session ) ;
2015-06-26 13:41:02 +00:00
}
2015-07-31 19:12:34 +00:00
for ( XmppAxolotlSession session : ownSessions ) {
axolotlMessage . addDevice ( session ) ;
}
return axolotlMessage ;
}
@Nullable
public XmppAxolotlMessage encrypt ( Message message ) {
2016-02-29 12:18:07 +00:00
XmppAxolotlMessage axolotlMessage = buildHeader ( message . getConversation ( ) ) ;
2015-07-31 19:12:34 +00:00
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
}
}
} ) ;
}
2016-02-29 12:18:07 +00:00
public void prepareKeyTransportMessage ( final Conversation conversation , final OnMessageCreatedCallback onMessageCreatedCallback ) {
2015-07-31 19:12:34 +00:00
executor . execute ( new Runnable ( ) {
@Override
public void run ( ) {
2016-02-29 12:18:07 +00:00
XmppAxolotlMessage axolotlMessage = buildHeader ( conversation ) ;
2015-07-31 19:12:34 +00:00
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 )
2015-12-19 14:44:11 +00:00
? new XmppAxolotlSession ( account , axolotlStore , address , identityKey )
2015-07-31 21:28:09 +00:00
: 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 ) {
2015-10-11 13:48:58 +00:00
publishBundlesIfNeeded ( false , false ) ;
2015-07-31 19:12:34 +00:00
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 ) {
2015-12-08 16:15:08 +00:00
putFreshSession ( 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 ) {
2015-10-11 14:04:31 +00:00
XmppAxolotlMessage . XmppAxolotlKeyTransportMessage keyTransportMessage ;
2015-07-31 21:28:09 +00:00
XmppAxolotlSession session = getReceivingSession ( message ) ;
keyTransportMessage = message . getParameters ( session , getOwnDeviceId ( ) ) ;
if ( session . isFresh ( ) & & keyTransportMessage ! = null ) {
2015-12-08 16:15:08 +00:00
putFreshSession ( session ) ;
2015-07-31 21:28:09 +00:00
}
return keyTransportMessage ;
}
2015-12-08 16:15:08 +00:00
private void putFreshSession ( XmppAxolotlSession session ) {
2015-12-19 11:44:55 +00:00
Log . d ( Config . LOGTAG , " put fresh session " ) ;
2015-12-08 16:15:08 +00:00
sessions . put ( session ) ;
if ( Config . X509_VERIFICATION ) {
2015-12-19 14:44:11 +00:00
if ( session . getIdentityKey ( ) ! = null ) {
verifySessionWithPEP ( session ) ;
2015-12-08 16:15:08 +00:00
} else {
Log . e ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) + " : identity key was empty after reloading for x509 verification " ) ;
}
}
}
2015-05-29 09:17:26 +00:00
}