Support for sealed sender - Part 1

This commit is contained in:
Moxie Marlinspike 2018-05-22 02:13:10 -07:00 committed by Greyson Parrelli
parent b7b9554364
commit 5f31762220
67 changed files with 1679 additions and 801 deletions

View File

@ -77,7 +77,7 @@ dependencies {
compile 'com.google.android.exoplayer:exoplayer-core:2.8.4' compile 'com.google.android.exoplayer:exoplayer-core:2.8.4'
compile 'com.google.android.exoplayer:exoplayer-ui:2.8.4' compile 'com.google.android.exoplayer:exoplayer-ui:2.8.4'
compile 'org.whispersystems:signal-service-android:2.9.0' compile 'org.whispersystems:signal-service-android:2.10.0-RC1'
compile 'org.whispersystems:webrtc-android:M69' compile 'org.whispersystems:webrtc-android:M69'
compile "me.leolin:ShortcutBadger:1.1.16" compile "me.leolin:ShortcutBadger:1.1.16"
@ -147,124 +147,6 @@ dependencies {
} }
} }
dependencyVerification {
verify = [
'com.android.support:design:7874ad1904eedc74aa41cffffb7f759d8990056f3bbbc9264911651c67c42f5f',
'com.android.support:preference-v14:8133c6e19233fa51e036a341e6d3f4adeead3375cebf777efced0fe154c3267e',
'com.android.support:preference-v7:75eabe936d1fc3b178450a554c4d433466036f2be6d6dccdf971eac9590fdbf5',
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
'mobi.upod:time-duration-picker:db469ce0f48dd96b892eac424ed76870e54bf00fe0a28cdcddfbe5f2a226a0e1',
'com.codewaves.stickyheadergrid:stickyheadergrid:5b4aa6a52a957cfd55f60f4220c11c0c371385a3cb9786cae03c260dcdef5794',
'com.android.support:appcompat-v7:a3a8e5230359746ed91801579b5fbe4668e3b1c4e6a14c7d67c8f58cb0311752',
'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
'com.android.support:recyclerview-v7:eb296414c1f6d4c7b522f69fe50588ea85297855db0e7806c24eb4f75409587d',
'com.android.support:support-v13:491f940c5d6d2ec7678fa2f14bd4bbbe8bf776e2c776d04bf0e5c2175975be43',
'com.android.support:cardview-v7:bc9e6b0e06ce1205f1db34f0e6193019613d19cfeb54cdccea722340d1c60f26',
'com.android.support:gridlayout-v7:5029529f7db66f8773426bf7318645f0840fc50d74f66355cd60c5e58d2da087',
'com.android.support:exifinterface:bbf44e519edd6333a24a3285aa21fd00181b920b81ca8aa89a8899f03ab4d6b0',
'android.arch.work:work-runtime:eda29b2cad202dee05a2e5aafe0a37c93ba9cde8f7cc0d0c8926a9f1a9498a8f',
'android.arch.lifecycle:extensions:429426b2feec2245ffc5e75b3b5309bedb36159cf06dc71843ae43526ac289b6',
'android.arch.lifecycle:common-java8:7078b5c8ccb94203df9cc2a463c69cf0021596e6cf966d78fbfd697aaafe0630',
'com.google.android.gms:play-services-gcm:312e61253a236f2d9b750b9c04fc92fd190d23b0b2755c99de6ce4a28b259dae',
'com.google.android.gms:play-services-places:abf3a4a3b146ec7e6e753be62775e512868cf37d6f88ffe2d81167b33b57132b',
'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e',
'com.google.android.exoplayer:exoplayer-ui:027557b2d69b15e1852a2530b36971f0dcc177abae240ee35e05f63502cdb0a7',
'com.google.android.exoplayer:exoplayer-core:e69b409e11887c955deb373357c30eeabf183395db0092b4817e0f80bb467d5b',
'org.whispersystems:signal-service-android:bf469abcdcd2b2ba429024aca30713eaa3b83a77af34030a818b1c9fb4780044',
'org.whispersystems:webrtc-android:5493c92141ce884fc5ce8240d783232f4fe14bd17a8d0d7d1bd4944d0bd1682f',
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
'com.github.chrisbanes:PhotoView:ed06775308da260e1fd86d1d3288988fcd3d80db24ce0d7c9fcfedc39e622292',
'com.github.bumptech.glide:glide:997de7ac95be6c944d3b8cbe13de11307736ea45451c1b09a6cec7c328ead59f',
'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1',
'org.greenrobot:eventbus:180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c',
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
'com.davemorrissey.labs:subsampling-scale-image-view:550c5baa07e0bb4ff0a18b705e96d34436d22619248bd8c08c08c730b1f55cfe',
'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729',
'com.tomergoldst.android:tooltips:4c56697dd1ad64b8066535c61f961a6d901e7ae5d97ae27084ba40ad620349b6',
'com.klinkerapps:android-smsmms:e7c3328a0f3a8dd44daa8129de4e99996f3057a4546e47891b036b81e0ebf1d1',
'com.annimon:stream:5da6e2e3e0551d61a3ea7014f04312276549e3dd739cf637996e4cf43c5535b9',
'com.takisoft.fix:colorpicker:f5d0dbabe406a1800498ca9c1faf34db36e021d8488bf10360f29961fe3ab0d1',
'com.github.dmytrodanylyk.circular-progress-button:library:8dc6a29a5a8db7b2ad5a9a7fda1dc9ae0893f4c8f0545732b2c63854ea693e8e',
'org.signal:android-database-sqlcipher:33d4063336893af00b9d68b418e7b290cace74c20ce8aacffddc0911010d3d73',
'com.googlecode.ez-vcard:ez-vcard:7e24ad50b222d2f70ac91bdccfa3c0f6200b078d797cb784837f75e77bb4210f',
'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
'com.google.android.gms:play-services-base:0ca636a8fc9a5af45e607cdcd61783bf5d561cbbb0f862021ce69606eee5ad49',
'com.google.android.gms:play-services-tasks:69ec265168e601d0203d04cd42e34bb019b2f029aa1e16fabd38a5153eea2086',
'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d',
'com.android.support:support-v4:8b9031381c678d628c9e47b566ae1d161e1c9710f7855c759beeac7596cecf30',
'com.android.support:support-fragment:3772fc738ada86824ba1a4b3f197c3dbd67b7ddcfe2c9db1de95ef2e3487a915',
'com.android.support:animated-vector-drawable:271ecbc906cda8dcd9e655ba0473129c3408a4189c806f616c378e6fd18fb3b7',
'com.android.support:support-core-ui:bbc7f65fc95649464733af373361532ab5f9f3b749c3badaa2bbf27e574b6c6f',
'com.android.support:transition:45d09fc51284c17bbab300f5122512ac7d7348a6d23bda2051648bbe76cc9aa5',
'com.android.support:viewpager:013c4c53058758ec104dbae970be58159f75dfe342ba8b937d15ff5282e35ffc',
'com.android.support:coordinatorlayout:9dfacd80423dc979048fbaed83c0ee543c46259feb2417377e79a656888d3892',
'com.android.support:drawerlayout:8f6809afae4793550c37461c9810e954ae6a23dbb4d23e5333bf18148df1150a',
'com.android.support:slidingpanelayout:d1d234f66a1b36a9aee9b94fa6c66f97128c0828078c8e889e9037ec898cd600',
'com.android.support:customview:98db03845f994e08248bf701c1ff0ccaa12e70f94251ec9272900f0f694e072b',
'com.android.support:swiperefreshlayout:a3b41f7f6730866b49865e86e49f988d4858699765f534300fb2ff5f9325e712',
'com.android.support:asynclayoutinflater:115bde87721f7334579b0c735f60dd7c98af1bb7f34010c5b0553b95dc351aa2',
'android.arch.persistence.room:runtime:c21810eaafce370f1c9df1365393f55f962370a0d8b0b38b4771052c7021b737',
'com.android.support:support-core-utils:c81e1e98ca3cb2edae002c69cf35b22aec364b8cb2f1042c97e206eb5790ac41',
'com.android.support:loader:920b85efd72dc33e915b0f88a883fe73b88483c6df8751a741e17611f2460341',
'com.android.support:support-vector-drawable:f658986d968172bccfed28578471c96050780fe5e133861e4d331069cc373f4d',
'com.android.support:support-media-compat:266eff9605f515013eee1ebdbd8818a9270696dc807f34bbcc5fc11fb61a22c7',
'com.android.support:support-compat:e17e3b01dbea3f9ea1c86943292f903ca93d2231c6242e456e0b6a9c5817118a',
'com.android.support:versionedparcelable:60eb1cb08f71b65c3f6123135e03ebeb5930b5e126e1e5b2ac91b386908c9d02',
'com.android.support:collections:93c258c8a09f531a267653829742c0f8f6da0e348b11cb8655b0855628f2d4f0',
'android.arch.lifecycle:livedata:50ab0490c1ff1a7cfb4e554032998b080888946d0dd424f39900efc4a1bcd750',
'android.arch.lifecycle:livedata-core:d6fdd8b985d6178d7ea2f16986a24e83f1bee936b74d43167c69e08d3cc12c50',
'android.arch.lifecycle:runtime:c4e4be66c1b2f0abec593571454e1de14013f7e0f96bf2a9f212931a48cae550',
'android.arch.lifecycle:common:8d378e88ebd5189e09eef623414812c868fd90aa519d6160e2311fb8b81cff56',
'com.github.bumptech.glide:gifdecoder:59ccf3bb0cec11dab4b857382cbe0b171111b6fc62bf141adce4e1180889af15',
'com.android.support:interpolator:7bc7ee86a0db39a4b51956f3e89842d2bd962118d57d779eb6ed6b34ba0677ea',
'com.android.support:cursoradapter:87feffe742b8d62ca8a9833abe564838bf6a672e31c7ad1306ec4006adf90d21',
'android.arch.persistence.room:common:7cf36bcd5f59ddc4876f887e36511bfd7b111f1eb717c0e9b6e2bcc710305ae6',
'android.arch.persistence:db-framework:bd665448330acb90a6f551a87b0ba69169da2b8ec168b92f387997339cc14311',
'android.arch.persistence:db:504e8c4307bfd53084924776ba3d49fed11b6f76d82dd80d5121c2d907fdfef6',
'android.arch.core:runtime:c3215aa5873311b3f88a6f4e4a3c25ad89971bc127de8c3e1291c57f93a05c39',
'android.arch.core:common:3a616a32f433e9e23f556b38575c31b013613d3ae85206263b7625fe1f4c151a',
'android.arch.lifecycle:viewmodel:7de29cfaba77d6b5d5be234c57f6812d0150d087e63941af22ba1d1f8e2bc96a',
'com.android.support:documentfile:47cdcd3e9302b7b064923f05487a5c03babbd9bbda4726b71e97791fab5d4779',
'com.android.support:localbroadcastmanager:d287c823af5fdde72c099fcfc5f630efe9687af7a914343ae6fd92de32c8a806',
'com.android.support:print:4be8a812d73e4a80e35b91ceae127def3f0bb9726bf3bc439aa0cc81503f5728',
'com.android.support:support-annotations:5d5b9414f02d3fa0ee7526b8d5ddae0da67c8ecc8c4d63ffa6cf91488a93b927',
'androidx.concurrent:futures:1f63078c41efd29d20ee3444fba93c6cdfaeeb862c6d3b6166ff8debd37d471a',
'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1',
'org.whispersystems:signal-service-java:4db9adf763071756cfd93fe48a40850f684ca02f2dea59601841abba7715c5c1',
'com.github.bumptech.glide:disklrucache:c1b1b6f5bbd01e2fcdc9d7f60913c8d338bdb65ed4a93bfa02b56f19daaade4b',
'com.github.bumptech.glide:annotations:bede99ef9f71517a4274bac18fd3e483e9f2b6108d7d6fe8f4949be4aa4d9512',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541',
'com.google.android:flexbox:a9989fd13ae2ee42765dfc515fe362edf4f326e74925d02a10369df8092a4935',
'org.jsoup:jsoup:abeaf34795a4de70f72aed6de5966d2955ec7eb348eeb813324f23c999575473',
'com.google.guava:listenablefuture:e4ad7607e5c0477c6f890ef26a49cb8d1bb4dffb650bab4502afee64644e3069',
'androidx.annotation:annotation:04f22f257944ce223701d5aa1bdc36fb7f4594e87b539044045cd161d965468e',
'org.whispersystems:curve25519-android:82595394422b957d4a5b5f1b27b75ba25cf6dc4db4d312418ca38cd6fff279ca',
'org.whispersystems:signal-protocol-java:5152c2b01a25147967d6bf82e540f947901bdfa79260be3eb3e96b03f787d6b5',
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
'com.googlecode.libphonenumber:libphonenumber:183392c0565be16d3f6f86680b4106bbde6fe31a402ad21bf9823d938c0c8706',
'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d',
'com.squareup.okhttp3:okhttp:7265adbd6f028aade307f58569d814835cd02bc9beffb70c25f72c9de50d61c4',
'com.madgag.spongycastle:pkix:0d9cca6991f68eb373cfad309d5268c9fc38db5efb5fe00dcccf5c973af1eca1',
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
'org.threeten:threetenbp:f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7',
'org.whispersystems:curve25519-java:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
]
}
android { android {
flavorDimensions "none" flavorDimensions "none"
compileSdkVersion 28 compileSdkVersion 28
@ -287,7 +169,7 @@ android {
project.ext.set("archivesBaseName", "Signal"); project.ext.set("archivesBaseName", "Signal");
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\"" buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\"" buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\"" buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\"" buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
@ -296,6 +178,7 @@ android {
buildConfigField "String", "USER_AGENT", "\"OWA\"" buildConfigField "String", "USER_AGENT", "\"OWA\""
buildConfigField "boolean", "DEV_BUILD", "false" buildConfigField "boolean", "DEV_BUILD", "false"
buildConfigField "String", "MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\"" buildConfigField "String", "MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
ndk { ndk {
abiFilters "armeabi", "armeabi-v7a", "x86" abiFilters "armeabi", "armeabi-v7a", "x86"

View File

@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener; import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -207,6 +208,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
RotateSignedPreKeyListener.schedule(this); RotateSignedPreKeyListener.schedule(this);
DirectoryRefreshListener.schedule(this); DirectoryRefreshListener.schedule(this);
LocalBackupListener.schedule(this); LocalBackupListener.schedule(this);
RotateSenderCertificateListener.schedule(this);
if (BuildConfig.PLAY_STORE_DISABLED) { if (BuildConfig.PLAY_STORE_DISABLED) {
UpdateApkRefreshListener.schedule(this); UpdateApkRefreshListener.schedule(this);

View File

@ -167,10 +167,11 @@ public class ConfirmIdentityDialog extends AlertDialog {
SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE, SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE,
messageRecord.getIndividualRecipient().getAddress().toPhoneString(), messageRecord.getIndividualRecipient().getAddress().toPhoneString(),
messageRecord.getRecipientDeviceId(), "", messageRecord.getRecipientDeviceId(),
messageRecord.getDateSent(), messageRecord.getDateSent(),
legacy ? Base64.decode(messageRecord.getBody()) : null, legacy ? Base64.decode(messageRecord.getBody()) : null,
!legacy ? Base64.decode(messageRecord.getBody()) : null); !legacy ? Base64.decode(messageRecord.getBody()) : null,
0, null);
long pushId = pushDatabase.insert(envelope); long pushId = pushDatabase.insert(envelope);

View File

@ -51,10 +51,6 @@ import android.support.v7.app.AlertDialog;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import org.thoughtcrime.securesms.camera.CameraActivity;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair; import android.util.Pair;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
@ -79,6 +75,7 @@ import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.audio.AudioRecorder; import org.thoughtcrime.securesms.audio.AudioRecorder;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer; import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.camera.CameraActivity;
import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.components.AnimatingToggle; import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.AttachmentTypeSelector; import org.thoughtcrime.securesms.components.AttachmentTypeSelector;
@ -114,6 +111,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft; import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts; import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
@ -129,6 +127,7 @@ import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob; import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType; import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.AudioSlide;

View File

@ -303,13 +303,13 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
} }
@Override @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new MessageDetailsLoader(this, getIntent().getStringExtra(TYPE_EXTRA), return new MessageDetailsLoader(this, getIntent().getStringExtra(TYPE_EXTRA),
getIntent().getLongExtra(MESSAGE_ID_EXTRA, -1)); getIntent().getLongExtra(MESSAGE_ID_EXTRA, -1));
} }
@Override @Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA)); MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA));
if (messageRecord == null) { if (messageRecord == null) {
@ -320,7 +320,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
} }
@Override @Override
public void onLoaderReset(Loader<Cursor> loader) { public void onLoaderReset(@NonNull Loader<Cursor> loader) {
recipientsList.setAdapter(null); recipientsList.setAdapter(null);
} }

View File

@ -25,7 +25,6 @@ import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan; import android.text.style.ClickableSpan;
import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair; import android.util.Pair;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -62,13 +61,16 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.PreKeyUtil; import org.thoughtcrime.securesms.crypto.PreKeyUtil;
import org.thoughtcrime.securesms.crypto.SessionUtil; import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.NoExternalStorageException; import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.GcmRefreshJob; import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.lock.RegistrationLockReminders; import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.push.AccountManagerFactory;
@ -689,12 +691,16 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
private void verifyAccount(@NonNull String code, @Nullable String pin) throws IOException { private void verifyAccount(@NonNull String code, @Nullable String pin) throws IOException {
int registrationId = KeyHelper.generateRegistrationId(false); int registrationId = KeyHelper.generateRegistrationId(false);
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(RegistrationActivity.this);
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(RegistrationActivity.this);
TextSecurePreferences.setLocalRegistrationId(RegistrationActivity.this, registrationId); TextSecurePreferences.setLocalRegistrationId(RegistrationActivity.this, registrationId);
SessionUtil.archiveAllSessions(RegistrationActivity.this); SessionUtil.archiveAllSessions(RegistrationActivity.this);
String signalingKey = Util.getSecret(52); String signalingKey = Util.getSecret(52);
accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent(), pin); accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent(), pin,
unidentifiedAccessKey, universalUnidentifiedAccess);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(RegistrationActivity.this); IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(RegistrationActivity.this);
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(RegistrationActivity.this); List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(RegistrationActivity.this);
@ -727,6 +733,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
private void handleSuccessfulRegistration() { private void handleSuccessfulRegistration() {
ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new DirectoryRefreshJob(RegistrationActivity.this, false)); ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new DirectoryRefreshJob(RegistrationActivity.this, false));
ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new RotateCertificateJob(RegistrationActivity.this));
DirectoryRefreshListener.schedule(RegistrationActivity.this); DirectoryRefreshListener.schedule(RegistrationActivity.this);
RotateSignedPreKeyListener.schedule(RegistrationActivity.this); RotateSignedPreKeyListener.schedule(RegistrationActivity.this);

View File

@ -83,7 +83,7 @@ public class PointerAttachment extends Attachment {
pointer.get().asPointer().getSize().or(0), pointer.get().asPointer().getSize().or(0),
pointer.get().asPointer().getFileName().orNull(), pointer.get().asPointer().getFileName().orNull(),
String.valueOf(pointer.get().asPointer().getId()), String.valueOf(pointer.get().asPointer().getId()),
encodedKey, pointer.get().asPointer().getRelay().orNull(), encodedKey, null,
pointer.get().asPointer().getDigest().orNull(), pointer.get().asPointer().getDigest().orNull(),
pointer.get().asPointer().getVoiceNote(), pointer.get().asPointer().getVoiceNote(),
pointer.get().asPointer().getWidth(), pointer.get().asPointer().getWidth(),

View File

@ -0,0 +1,118 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import java.io.IOException;
public class UnidentifiedAccessUtil {
private static final String TAG = UnidentifiedAccessUtil.class.getSimpleName();
public static CertificateValidator getCertificateValidator() {
try {
ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BuildConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
return new CertificateValidator(unidentifiedSenderTrustRoot);
} catch (InvalidKeyException | IOException e) {
throw new AssertionError(e);
}
}
@WorkerThread
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context,
@NonNull Recipient recipient)
{
try {
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
Log.w(TAG, "Their access key: " + (theirUnidentifiedAccessKey == null));
Log.w(TAG, "Our access key: " + (ourUnidentifiedAccessKey == null));
Log.w(TAG, "Our certificatE: " + (ourUnidentifiedAccessCertificate == null));
if (theirUnidentifiedAccessKey != null &&
ourUnidentifiedAccessKey != null &&
ourUnidentifiedAccessCertificate != null)
{
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(theirUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate),
new UnidentifiedAccess(ourUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate)));
}
return Optional.absent();
} catch (InvalidCertificateException e) {
Log.w(TAG, e);
return Optional.absent();
}
}
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
try {
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
if (ourUnidentifiedAccessKey != null && ourUnidentifiedAccessCertificate != null) {
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate),
new UnidentifiedAccess(ourUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate)));
}
return Optional.absent();
} catch (InvalidCertificateException e) {
Log.w(TAG, e);
return Optional.absent();
}
}
public static @NonNull byte[] getSelfUnidentifiedAccessKey(@NonNull Context context) {
return UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getProfileKey(context));
}
private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient) {
byte[] theirProfileKey = recipient.resolve().getProfileKey();
switch (recipient.resolve().getUnidentifiedAccessMode()) {
case UNKNOWN:
if (theirProfileKey == null) return Util.getSecretBytes(16);
else return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
case DISABLED:
return null;
case ENABLED:
if (theirProfileKey == null) return null;
else return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
case UNRESTRICTED:
return Util.getSecretBytes(16);
default:
throw new AssertionError("Unknown mode: " + recipient.getUnidentifiedAccessMode().getMode());
}
}
}

View File

@ -51,6 +51,11 @@ public class SignalProtocolStoreImpl implements SignalProtocolStore {
return identityKeyStore.isTrustedIdentity(address, identityKey, direction); return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
} }
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
return identityKeyStore.getIdentity(address);
}
@Override @Override
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
return preKeyStore.loadPreKey(preKeyId); return preKeyStore.loadPreKey(preKeyId);

View File

@ -108,6 +108,17 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
} }
} }
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
Optional<IdentityRecord> record = DatabaseFactory.getIdentityDatabase(context).getIdentity(Address.fromSerialized(address.getName()));
if (record.isPresent()) {
return record.get().getIdentityKey();
} else {
return null;
}
}
private boolean isTrustedForSending(IdentityKey identityKey, Optional<IdentityRecord> identityRecord) { private boolean isTrustedForSending(IdentityKey identityKey, Optional<IdentityRecord> identityRecord) {
if (!identityRecord.isPresent()) { if (!identityRecord.isPresent()) {
Log.w(TAG, "Nothing here, returning true..."); Log.w(TAG, "Nothing here, returning true...");

View File

@ -22,6 +22,7 @@ public class GroupReceiptDatabase extends Database {
private static final String ADDRESS = "address"; private static final String ADDRESS = "address";
private static final String STATUS = "status"; private static final String STATUS = "status";
private static final String TIMESTAMP = "timestamp"; private static final String TIMESTAMP = "timestamp";
private static final String UNIDENTIFIED = "unidentified";
public static final int STATUS_UNKNOWN = -1; public static final int STATUS_UNKNOWN = -1;
public static final int STATUS_UNDELIVERED = 0; public static final int STATUS_UNDELIVERED = 0;
@ -29,7 +30,7 @@ public class GroupReceiptDatabase extends Database {
public static final int STATUS_READ = 2; public static final int STATUS_READ = 2;
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + ADDRESS + " TEXT, " + STATUS + " INTEGER, " + TIMESTAMP + " INTEGER);"; MMS_ID + " INTEGER, " + ADDRESS + " TEXT, " + STATUS + " INTEGER, " + TIMESTAMP + " INTEGER, " + UNIDENTIFIED + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXES = { public static final String[] CREATE_INDEXES = {
"CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", "CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
@ -63,6 +64,16 @@ public class GroupReceiptDatabase extends Database {
new String[] {String.valueOf(mmsId), address.serialize(), String.valueOf(status)}); new String[] {String.valueOf(mmsId), address.serialize(), String.valueOf(status)});
} }
public void setUnidentified(Address address, long mmsId, boolean unidentified) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues(1);
values.put(UNIDENTIFIED, unidentified ? 1 : 0);
db.update(TABLE_NAME, values, MMS_ID + " = ? AND " + ADDRESS + " = ?",
new String[] {String.valueOf(mmsId), address.serialize()});
}
public @NonNull List<GroupReceiptInfo> getGroupReceiptInfo(long mmsId) { public @NonNull List<GroupReceiptInfo> getGroupReceiptInfo(long mmsId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteDatabase db = databaseHelper.getReadableDatabase();
List<GroupReceiptInfo> results = new LinkedList<>(); List<GroupReceiptInfo> results = new LinkedList<>();
@ -71,7 +82,8 @@ public class GroupReceiptDatabase extends Database {
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
results.add(new GroupReceiptInfo(Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))), results.add(new GroupReceiptInfo(Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))),
cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)), cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)),
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)))); cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)),
cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED)) == 1));
} }
} }
@ -92,11 +104,13 @@ public class GroupReceiptDatabase extends Database {
private final Address address; private final Address address;
private final int status; private final int status;
private final long timestamp; private final long timestamp;
private final boolean unidentified;
public GroupReceiptInfo(Address address, int status, long timestamp) { GroupReceiptInfo(Address address, int status, long timestamp, boolean unidentified) {
this.address = address; this.address = address;
this.status = status; this.status = status;
this.timestamp = timestamp; this.timestamp = timestamp;
this.unidentified = unidentified;
} }
public Address getAddress() { public Address getAddress() {
@ -110,5 +124,9 @@ public class GroupReceiptDatabase extends Database {
public long getTimestamp() { public long getTimestamp() {
return timestamp; return timestamp;
} }
public boolean isUnidentified() {
return unidentified;
}
} }
} }

View File

@ -23,7 +23,6 @@ import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair; import android.util.Pair;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
@ -52,6 +51,7 @@ import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote; import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
@ -125,7 +125,7 @@ public class MmsDatabase extends MessagingDatabase {
EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " INTEGER DEFAULT 0, " +
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + QUOTE_ID + " INTEGER DEFAULT 0, " + READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + QUOTE_ID + " INTEGER DEFAULT 0, " +
QUOTE_AUTHOR + " TEXT, " + QUOTE_BODY + " TEXT, " + QUOTE_ATTACHMENT + " INTEGER DEFAULT -1, " + QUOTE_AUTHOR + " TEXT, " + QUOTE_BODY + " TEXT, " + QUOTE_ATTACHMENT + " INTEGER DEFAULT -1, " +
QUOTE_MISSING + " INTEGER DEFAULT 0, " + SHARED_CONTACTS + " TEXT);"; QUOTE_MISSING + " INTEGER DEFAULT 0, " + SHARED_CONTACTS + " TEXT, " + UNIDENTIFIED + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = { public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", "CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -145,7 +145,7 @@ public class MmsDatabase extends MessagingDatabase {
MESSAGE_SIZE, STATUS, TRANSACTION_ID, MESSAGE_SIZE, STATUS, TRANSACTION_ID,
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID, DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING, SHARED_CONTACTS, EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING, SHARED_CONTACTS, UNIDENTIFIED,
"json_group_array(json_object(" + "json_group_array(json_object(" +
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " + "'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " + "'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
@ -403,6 +403,14 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
public void markUnidentified(long messageId, boolean unidentified) {
ContentValues contentValues = new ContentValues();
contentValues.put(UNIDENTIFIED, unidentified ? 1 : 0);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
}
@Override @Override
public void markExpireStarted(long messageId) { public void markExpireStarted(long messageId) {
markExpireStarted(messageId, System.currentTimeMillis()); markExpireStarted(messageId, System.currentTimeMillis());
@ -575,6 +583,8 @@ public class MmsDatabase extends MessagingDatabase {
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId); int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId);
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES));
String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE));
long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID)); long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID));
String quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR)); String quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR));
@ -586,19 +596,37 @@ public class MmsDatabase extends MessagingDatabase {
List<Attachment> attachments = Stream.of(associatedAttachments).filterNot(Attachment::isQuote).filterNot(contactAttachments::contains).map(a -> (Attachment)a).toList(); List<Attachment> attachments = Stream.of(associatedAttachments).filterNot(Attachment::isQuote).filterNot(contactAttachments::contains).map(a -> (Attachment)a).toList();
Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false); Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false);
List<NetworkFailure> networkFailures = new LinkedList<>();
List<IdentityKeyMismatch> mismatches = new LinkedList<>();
QuoteModel quote = null; QuoteModel quote = null;
if (quoteId > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) { if (quoteId > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) {
quote = new QuoteModel(quoteId, Address.fromSerialized(quoteAuthor), quoteText, quoteMissing, quoteAttachments); quote = new QuoteModel(quoteId, Address.fromSerialized(quoteAuthor), quoteText, quoteMissing, quoteAttachments);
} }
if (!TextUtils.isEmpty(mismatchDocument)) {
try {
mismatches = JsonUtils.fromJson(mismatchDocument, IdentityKeyMismatchList.class).getList();
} catch (IOException e) {
Log.w(TAG, e);
}
}
if (!TextUtils.isEmpty(networkDocument)) {
try {
networkFailures = JsonUtils.fromJson(networkDocument, NetworkFailureList.class).getList();
} catch (IOException e) {
Log.w(TAG, e);
}
}
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) { if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, quote, contacts); return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, quote, contacts);
} else if (Types.isExpirationTimerUpdate(outboxType)) { } else if (Types.isExpirationTimerUpdate(outboxType)) {
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn); return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
} }
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts); OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts, networkFailures, mismatches);
if (Types.isSecureType(outboxType)) { if (Types.isSecureType(outboxType)) {
return new OutgoingSecureMediaMessage(message); return new OutgoingSecureMediaMessage(message);
@ -730,6 +758,7 @@ public class MmsDatabase extends MessagingDatabase {
contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId()); contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId());
contentValues.put(EXPIRES_IN, retrieved.getExpiresIn()); contentValues.put(EXPIRES_IN, retrieved.getExpiresIn());
contentValues.put(READ, retrieved.isExpirationUpdate() ? 1 : 0); contentValues.put(READ, retrieved.isExpirationUpdate() ? 1 : 0);
contentValues.put(UNIDENTIFIED, retrieved.isUnidentified());
if (!contentValues.containsKey(DATE_SENT)) { if (!contentValues.containsKey(DATE_SENT)) {
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED)); contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
@ -1181,7 +1210,7 @@ public class MmsDatabase extends MessagingDatabase {
message.getOutgoingQuote().isOriginalMissing(), message.getOutgoingQuote().isOriginalMissing(),
new SlideDeck(context, message.getOutgoingQuote().getAttachments())) : new SlideDeck(context, message.getOutgoingQuote().getAttachments())) :
null, null,
message.getSharedContacts()); message.getSharedContacts(), false);
} }
} }
@ -1269,6 +1298,7 @@ public class MmsDatabase extends MessagingDatabase {
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID)); int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED)); long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED));
boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.UNIDENTIFIED)) == 1;
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0; readReceiptCount = 0;
@ -1287,7 +1317,7 @@ public class MmsDatabase extends MessagingDatabase {
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, addressDeviceId, dateSent, dateReceived, deliveryReceiptCount,
threadId, body, slideDeck, partCount, box, mismatches, threadId, body, slideDeck, partCount, box, mismatches,
networkFailures, subscriptionId, expiresIn, expireStarted, networkFailures, subscriptionId, expiresIn, expireStarted,
readReceiptCount, quote, contacts); readReceiptCount, quote, contacts, unidentified);
} }
private Recipient getRecipientFor(String serialized) { private Recipient getRecipientFor(String serialized) {

View File

@ -19,6 +19,7 @@ public interface MmsSmsColumns {
public static final String EXPIRES_IN = "expires_in"; public static final String EXPIRES_IN = "expires_in";
public static final String EXPIRE_STARTED = "expire_started"; public static final String EXPIRE_STARTED = "expire_started";
public static final String NOTIFIED = "notified"; public static final String NOTIFIED = "notified";
public static final String UNIDENTIFIED = "unidentified";
public static class Types { public static class Types {
protected static final long TOTAL_MASK = 0xFFFFFFFF; protected static final long TOTAL_MASK = 0xFFFFFFFF;

View File

@ -49,7 +49,9 @@ public class MmsSmsDatabase extends Database {
MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX,
SmsDatabase.STATUS, MmsDatabase.PART_COUNT, SmsDatabase.STATUS,
MmsSmsColumns.UNIDENTIFIED,
MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
MmsDatabase.STATUS, MmsDatabase.STATUS,
@ -232,6 +234,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
MmsDatabase.UNIDENTIFIED,
MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT, MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT,
MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
@ -256,6 +259,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
MmsDatabase.UNIDENTIFIED,
MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT, MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT,
MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
@ -266,7 +270,8 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.QUOTE_BODY, MmsDatabase.QUOTE_BODY,
MmsDatabase.QUOTE_MISSING, MmsDatabase.QUOTE_MISSING,
MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.QUOTE_ATTACHMENT,
MmsDatabase.SHARED_CONTACTS}; MmsDatabase.SHARED_CONTACTS,
MmsDatabase.UNIDENTIFIED};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
@ -304,6 +309,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsDatabase.EXPIRY); mmsColumnsPresent.add(MmsDatabase.EXPIRY);
mmsColumnsPresent.add(MmsDatabase.NOTIFIED); mmsColumnsPresent.add(MmsDatabase.NOTIFIED);
mmsColumnsPresent.add(MmsDatabase.STATUS); mmsColumnsPresent.add(MmsDatabase.STATUS);
mmsColumnsPresent.add(MmsDatabase.UNIDENTIFIED);
mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE); mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE);
mmsColumnsPresent.add(AttachmentDatabase.ROW_ID); mmsColumnsPresent.add(AttachmentDatabase.ROW_ID);
@ -351,6 +357,7 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add(SmsDatabase.DATE_SENT); smsColumnsPresent.add(SmsDatabase.DATE_SENT);
smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED); smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED);
smsColumnsPresent.add(SmsDatabase.STATUS); smsColumnsPresent.add(SmsDatabase.STATUS);
smsColumnsPresent.add(SmsDatabase.UNIDENTIFIED);
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 4, MMS_TRANSPORT, selection, null, MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID, null); String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 4, MMS_TRANSPORT, selection, null, MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID, null);

View File

@ -28,9 +28,12 @@ public class PushDatabase extends Database {
public static final String LEGACY_MSG = "body"; public static final String LEGACY_MSG = "body";
public static final String CONTENT = "content"; public static final String CONTENT = "content";
public static final String TIMESTAMP = "timestamp"; public static final String TIMESTAMP = "timestamp";
public static final String SERVER_TIMESTAMP = "server_timestamp";
public static final String SERVER_GUID = "server_guid";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + LEGACY_MSG + " TEXT, " + CONTENT + " TEXT, " + TIMESTAMP + " INTEGER);"; TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + LEGACY_MSG + " TEXT, " + CONTENT + " TEXT, " + TIMESTAMP + " INTEGER, " +
SERVER_TIMESTAMP + " INTEGER DEFAULT 0, " + SERVER_GUID + " TEXT DEFAULT NULL);";
public PushDatabase(Context context, SQLCipherOpenHelper databaseHelper) { public PushDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper); super(context, databaseHelper);
@ -49,6 +52,8 @@ public class PushDatabase extends Database {
values.put(LEGACY_MSG, envelope.hasLegacyMessage() ? Base64.encodeBytes(envelope.getLegacyMessage()) : ""); values.put(LEGACY_MSG, envelope.hasLegacyMessage() ? Base64.encodeBytes(envelope.getLegacyMessage()) : "");
values.put(CONTENT, envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : ""); values.put(CONTENT, envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : "");
values.put(TIMESTAMP, envelope.getTimestamp()); values.put(TIMESTAMP, envelope.getTimestamp());
values.put(SERVER_TIMESTAMP, envelope.getServerTimestamp());
values.put(SERVER_GUID, envelope.getUuid());
return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values);
} }
@ -69,10 +74,11 @@ public class PushDatabase extends Database {
return new SignalServiceEnvelope(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)), return new SignalServiceEnvelope(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)),
cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)), cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)),
cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)), cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)),
"",
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)), cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)),
Util.isEmpty(legacyMessage) ? null : Base64.decode(legacyMessage), Util.isEmpty(legacyMessage) ? null : Base64.decode(legacyMessage),
Util.isEmpty(content) ? null : Base64.decode(content)); Util.isEmpty(content) ? null : Base64.decode(content),
cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_TIMESTAMP)),
cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID)));
} }
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
@ -141,10 +147,13 @@ public class PushDatabase extends Database {
String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG)); String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG));
String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT)); String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)); long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
long serverTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_TIMESTAMP));
String serverGuid = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID));
return new SignalServiceEnvelope(type, source, deviceId, "", timestamp, return new SignalServiceEnvelope(type, source, deviceId, timestamp,
legacyMessage != null ? Base64.decode(legacyMessage) : null, legacyMessage != null ? Base64.decode(legacyMessage) : null,
content != null ? Base64.decode(content) : null); content != null ? Base64.decode(content) : null,
serverTimestamp, serverGuid);
} catch (IOException e) { } catch (IOException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }

View File

@ -6,7 +6,6 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.logging.Log;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
@ -14,6 +13,7 @@ import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
@ -56,11 +56,13 @@ public class RecipientDatabase extends Database {
private static final String CALL_RINGTONE = "call_ringtone"; private static final String CALL_RINGTONE = "call_ringtone";
private static final String CALL_VIBRATE = "call_vibrate"; private static final String CALL_VIBRATE = "call_vibrate";
private static final String NOTIFICATION_CHANNEL = "notification_channel"; private static final String NOTIFICATION_CHANNEL = "notification_channel";
private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode";
private static final String[] RECIPIENT_PROJECTION = new String[] { private static final String[] RECIPIENT_PROJECTION = new String[] {
BLOCK, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED, BLOCK, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED,
PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI, PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI,
SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
UNIDENTIFIED_ACCESS_MODE
}; };
static final List<String> TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) static final List<String> TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
@ -103,6 +105,24 @@ public class RecipientDatabase extends Database {
} }
} }
public enum UnidentifiedAccessMode {
UNKNOWN(0), DISABLED(1), ENABLED(2), UNRESTRICTED(3);
private final int mode;
UnidentifiedAccessMode(int mode) {
this.mode = mode;
}
public int getMode() {
return mode;
}
public static UnidentifiedAccessMode fromMode(int mode) {
return values()[mode];
}
}
public static final String CREATE_TABLE = public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME + "CREATE TABLE " + TABLE_NAME +
" (" + ID + " INTEGER PRIMARY KEY, " + " (" + ID + " INTEGER PRIMARY KEY, " +
@ -126,7 +146,8 @@ public class RecipientDatabase extends Database {
PROFILE_SHARING + " INTEGER DEFAULT 0, " + PROFILE_SHARING + " INTEGER DEFAULT 0, " +
CALL_RINGTONE + " TEXT DEFAULT NULL, " + CALL_RINGTONE + " TEXT DEFAULT NULL, " +
CALL_VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " + CALL_VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " +
NOTIFICATION_CHANNEL + " TEXT DEFAULT NULL);"; NOTIFICATION_CHANNEL + " TEXT DEFAULT NULL, " +
UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0);";
public RecipientDatabase(Context context, SQLCipherOpenHelper databaseHelper) { public RecipientDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper); super(context, databaseHelper);
@ -189,6 +210,7 @@ public class RecipientDatabase extends Database {
String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR)); String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR));
boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1; boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1;
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL));
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
MaterialColor color; MaterialColor color;
byte[] profileKey = null; byte[] profileKey = null;
@ -219,7 +241,7 @@ public class RecipientDatabase extends Database {
profileKey, systemDisplayName, systemContactPhoto, profileKey, systemDisplayName, systemContactPhoto,
systemPhoneLabel, systemContactUri, systemPhoneLabel, systemContactUri,
signalProfileName, signalProfileAvatar, profileSharing, signalProfileName, signalProfileAvatar, profileSharing,
notificationChannel)); notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode)));
} }
public BulkOperationsHandle resetAllSystemContactInfo() { public BulkOperationsHandle resetAllSystemContactInfo() {
@ -309,6 +331,13 @@ public class RecipientDatabase extends Database {
recipient.resolve().setExpireMessages(expiration); recipient.resolve().setExpireMessages(expiration);
} }
public void setUnidentifiedAccessMode(@NonNull Recipient recipient, @NonNull UnidentifiedAccessMode unidentifiedAccessMode) {
ContentValues values = new ContentValues(1);
values.put(UNIDENTIFIED_ACCESS_MODE, unidentifiedAccessMode.getMode());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setUnidentifiedAccessMode(unidentifiedAccessMode);
}
public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) { public void setProfileKey(@NonNull Recipient recipient, @Nullable byte[] profileKey) {
ContentValues values = new ContentValues(1); ContentValues values = new ContentValues(1);
values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey)); values.put(PROFILE_KEY, profileKey == null ? null : Base64.encodeBytes(profileKey));
@ -526,6 +555,7 @@ public class RecipientDatabase extends Database {
private final String signalProfileAvatar; private final String signalProfileAvatar;
private final boolean profileSharing; private final boolean profileSharing;
private final String notificationChannel; private final String notificationChannel;
private final UnidentifiedAccessMode unidentifiedAccessMode;
RecipientSettings(boolean blocked, long muteUntil, RecipientSettings(boolean blocked, long muteUntil,
@NonNull VibrateState messageVibrateState, @NonNull VibrateState messageVibrateState,
@ -545,7 +575,8 @@ public class RecipientDatabase extends Database {
@Nullable String signalProfileName, @Nullable String signalProfileName,
@Nullable String signalProfileAvatar, @Nullable String signalProfileAvatar,
boolean profileSharing, boolean profileSharing,
@Nullable String notificationChannel) @Nullable String notificationChannel,
@NonNull UnidentifiedAccessMode unidentifiedAccessMode)
{ {
this.blocked = blocked; this.blocked = blocked;
this.muteUntil = muteUntil; this.muteUntil = muteUntil;
@ -567,6 +598,7 @@ public class RecipientDatabase extends Database {
this.signalProfileAvatar = signalProfileAvatar; this.signalProfileAvatar = signalProfileAvatar;
this.profileSharing = profileSharing; this.profileSharing = profileSharing;
this.notificationChannel = notificationChannel; this.notificationChannel = notificationChannel;
this.unidentifiedAccessMode = unidentifiedAccessMode;
} }
public @Nullable MaterialColor getColor() { public @Nullable MaterialColor getColor() {
@ -613,7 +645,7 @@ public class RecipientDatabase extends Database {
return registered; return registered;
} }
public byte[] getProfileKey() { public @Nullable byte[] getProfileKey() {
return profileKey; return profileKey;
} }
@ -648,6 +680,10 @@ public class RecipientDatabase extends Database {
public @Nullable String getNotificationChannel() { public @Nullable String getNotificationChannel() {
return notificationChannel; return notificationChannel;
} }
public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() {
return unidentifiedAccessMode;
}
} }
public static class RecipientReader implements Closeable { public static class RecipientReader implements Closeable {

View File

@ -22,7 +22,6 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair; import android.util.Pair;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
@ -38,6 +37,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
@ -81,7 +81,7 @@ public class SmsDatabase extends MessagingDatabase {
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " + DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " +
EXPIRES_IN + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " DEFAULT 0, " +
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0);"; READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + UNIDENTIFIED + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = { public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", "CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -99,7 +99,7 @@ public class SmsDatabase extends MessagingDatabase {
PROTOCOL, READ, STATUS, TYPE, PROTOCOL, READ, STATUS, TYPE,
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, DELIVERY_RECEIPT_COUNT, REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, DELIVERY_RECEIPT_COUNT,
MISMATCHED_IDENTITIES, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED, MISMATCHED_IDENTITIES, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED,
NOTIFIED, READ_RECEIPT_COUNT NOTIFIED, READ_RECEIPT_COUNT, UNIDENTIFIED
}; };
private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache();
@ -243,6 +243,14 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.TOTAL_MASK, Types.MISSED_CALL_TYPE); updateTypeBitmask(id, Types.TOTAL_MASK, Types.MISSED_CALL_TYPE);
} }
public void markUnidentified(long id, boolean unidentified) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(UNIDENTIFIED, unidentified ? 1 : 0);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)});
}
@Override @Override
public void markExpireStarted(long id) { public void markExpireStarted(long id) {
markExpireStarted(id, System.currentTimeMillis()); markExpireStarted(id, System.currentTimeMillis());
@ -559,6 +567,7 @@ public class SmsDatabase extends MessagingDatabase {
values.put(READ, unread ? 0 : 1); values.put(READ, unread ? 0 : 1);
values.put(SUBSCRIPTION_ID, message.getSubscriptionId()); values.put(SUBSCRIPTION_ID, message.getSubscriptionId());
values.put(EXPIRES_IN, message.getExpiresIn()); values.put(EXPIRES_IN, message.getExpiresIn());
values.put(UNIDENTIFIED, message.isUnidentified());
if (!TextUtils.isEmpty(message.getPseudoSubject())) if (!TextUtils.isEmpty(message.getPseudoSubject()))
values.put(SUBJECT, message.getPseudoSubject()); values.put(SUBJECT, message.getPseudoSubject());
@ -818,7 +827,7 @@ public class SmsDatabase extends MessagingDatabase {
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId, 0, new LinkedList<IdentityKeyMismatch>(), threadId, 0, new LinkedList<IdentityKeyMismatch>(),
message.getSubscriptionId(), message.getExpiresIn(), message.getSubscriptionId(), message.getExpiresIn(),
System.currentTimeMillis(), 0); System.currentTimeMillis(), 0, false);
} }
} }
@ -858,6 +867,7 @@ public class SmsDatabase extends MessagingDatabase {
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED)); long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED));
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY)); String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.UNIDENTIFIED)) == 1;
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0; readReceiptCount = 0;
@ -871,7 +881,7 @@ public class SmsDatabase extends MessagingDatabase {
addressDeviceId, addressDeviceId,
dateSent, dateReceived, deliveryReceiptCount, type, dateSent, dateReceived, deliveryReceiptCount, type,
threadId, status, mismatches, subscriptionId, threadId, status, mismatches, subscriptionId,
expiresIn, expireStarted, readReceiptCount); expiresIn, expireStarted, readReceiptCount, unidentified);
} }
private List<IdentityKeyMismatch> getMismatches(String document) { private List<IdentityKeyMismatch> getMismatches(String document) {

View File

@ -56,8 +56,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int BAD_IMPORT_CLEANUP = 10; private static final int BAD_IMPORT_CLEANUP = 10;
private static final int QUOTE_MISSING = 11; private static final int QUOTE_MISSING = 11;
private static final int NOTIFICATION_CHANNELS = 12; private static final int NOTIFICATION_CHANNELS = 12;
private static final int SECRET_SENDER = 13;
private static final int DATABASE_VERSION = 12; private static final int DATABASE_VERSION = 13;
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@ -284,6 +285,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
} }
} }
if (oldVersion < SECRET_SENDER) {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN unidentified_access_mode INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE push ADD COLUMN server_timestamp INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE push ADD COLUMN server_guid TEXT DEFAULT NULL");
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@ -55,11 +55,12 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
List<IdentityKeyMismatch> mismatches, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> failures, int subscriptionId, List<NetworkFailure> failures, int subscriptionId,
long expiresIn, long expireStarted, int readReceiptCount, long expiresIn, long expireStarted, int readReceiptCount,
@Nullable Quote quote, @Nullable List<Contact> contacts) @Nullable Quote quote, @Nullable List<Contact> contacts,
boolean unidentified)
{ {
super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts); subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts, unidentified);
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.partCount = partCount; this.partCount = partCount;

View File

@ -53,6 +53,7 @@ public abstract class MessageRecord extends DisplayRecord {
private final int subscriptionId; private final int subscriptionId;
private final long expiresIn; private final long expiresIn;
private final long expireStarted; private final long expireStarted;
private final boolean unidentified;
MessageRecord(Context context, long id, String body, Recipient conversationRecipient, MessageRecord(Context context, long id, String body, Recipient conversationRecipient,
Recipient individualRecipient, int recipientDeviceId, Recipient individualRecipient, int recipientDeviceId,
@ -61,7 +62,7 @@ public abstract class MessageRecord extends DisplayRecord {
List<IdentityKeyMismatch> mismatches, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> networkFailures, List<NetworkFailure> networkFailures,
int subscriptionId, long expiresIn, long expireStarted, int subscriptionId, long expiresIn, long expireStarted,
int readReceiptCount) int readReceiptCount, boolean unidentified)
{ {
super(context, body, conversationRecipient, dateSent, dateReceived, super(context, body, conversationRecipient, dateSent, dateReceived,
threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount); threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount);
@ -73,6 +74,7 @@ public abstract class MessageRecord extends DisplayRecord {
this.subscriptionId = subscriptionId; this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn; this.expiresIn = expiresIn;
this.expireStarted = expireStarted; this.expireStarted = expireStarted;
this.unidentified = unidentified;
} }
public abstract boolean isMms(); public abstract boolean isMms();
@ -242,4 +244,8 @@ public abstract class MessageRecord extends DisplayRecord {
public long getExpireStarted() { public long getExpireStarted() {
return expireStarted; return expireStarted;
} }
public boolean isUnidentified() {
return unidentified;
}
} }

View File

@ -27,9 +27,9 @@ public abstract class MmsMessageRecord extends MessageRecord {
long type, List<IdentityKeyMismatch> mismatches, long type, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> networkFailures, int subscriptionId, long expiresIn, List<NetworkFailure> networkFailures, int subscriptionId, long expiresIn,
long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount,
@Nullable Quote quote, @NonNull List<Contact> contacts) @Nullable Quote quote, @NonNull List<Contact> contacts, boolean unidentified)
{ {
super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount); super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount, unidentified);
this.slideDeck = slideDeck; this.slideDeck = slideDeck;
this.quote = quote; this.quote = quote;

View File

@ -56,7 +56,7 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
super(context, id, "", conversationRecipient, individualRecipient, recipientDeviceId, super(context, id, "", conversationRecipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId, new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId,
0, 0, slideDeck, readReceiptCount, null, Collections.emptyList()); 0, 0, slideDeck, readReceiptCount, null, Collections.emptyList(), false);
this.contentLocation = contentLocation; this.contentLocation = contentLocation;
this.messageSize = messageSize; this.messageSize = messageSize;

View File

@ -47,12 +47,12 @@ public class SmsMessageRecord extends MessageRecord {
long type, long threadId, long type, long threadId,
int status, List<IdentityKeyMismatch> mismatches, int status, List<IdentityKeyMismatch> mismatches,
int subscriptionId, long expiresIn, long expireStarted, int subscriptionId, long expiresIn, long expireStarted,
int readReceiptCount) int readReceiptCount, boolean unidentified)
{ {
super(context, id, body, recipient, individualRecipient, recipientDeviceId, super(context, id, body, recipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, dateSent, dateReceived, threadId, status, deliveryReceiptCount, type,
mismatches, new LinkedList<>(), subscriptionId, mismatches, new LinkedList<>(), subscriptionId,
expiresIn, expireStarted, readReceiptCount); expiresIn, expireStarted, readReceiptCount, unidentified);
} }
public long getType() { public long getType() {

View File

@ -33,7 +33,9 @@ import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob; import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.SecurityEventListener; import org.thoughtcrime.securesms.push.SecurityEventListener;
@ -83,7 +85,9 @@ import dagger.Provides;
SendReadReceiptJob.class, SendReadReceiptJob.class,
MultiDeviceReadReceiptUpdateJob.class, MultiDeviceReadReceiptUpdateJob.class,
AppProtectionPreferenceFragment.class, AppProtectionPreferenceFragment.class,
GcmBroadcastReceiver.class}) GcmBroadcastReceiver.class,
RotateCertificateJob.class,
SendDeliveryReceiptJob.class})
public class SignalCommunicationModule { public class SignalCommunicationModule {
private static final String TAG = SignalCommunicationModule.class.getSimpleName(); private static final String TAG = SignalCommunicationModule.class.getSimpleName();
@ -118,10 +122,13 @@ public class SignalCommunicationModule {
new DynamicCredentialsProvider(context), new DynamicCredentialsProvider(context),
new SignalProtocolStoreImpl(context), new SignalProtocolStoreImpl(context),
BuildConfig.USER_AGENT, BuildConfig.USER_AGENT,
TextSecurePreferences.isMultiDevice(context),
Optional.fromNullable(IncomingMessageObserver.getPipe()), Optional.fromNullable(IncomingMessageObserver.getPipe()),
Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()),
Optional.of(new SecurityEventListener(context))); Optional.of(new SecurityEventListener(context)));
} else { } else {
this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe()); this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe(), IncomingMessageObserver.getUnidentifiedPipe());
this.messageSender.setIsMultiDevice(TextSecurePreferences.isMultiDevice(context));
} }
return this.messageSender; return this.messageSender;

View File

@ -4,7 +4,6 @@ package org.thoughtcrime.securesms.groups;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.logging.Log;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
@ -17,6 +16,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob; import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
@ -27,8 +27,8 @@ import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type; import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
@ -47,7 +47,7 @@ public class GroupMessageProcessor {
private static final String TAG = GroupMessageProcessor.class.getSimpleName(); private static final String TAG = GroupMessageProcessor.class.getSimpleName();
public static @Nullable Long process(@NonNull Context context, public static @Nullable Long process(@NonNull Context context,
@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message, @NonNull SignalServiceDataMessage message,
boolean outgoing) boolean outgoing)
{ {
@ -62,13 +62,13 @@ public class GroupMessageProcessor {
Optional<GroupRecord> record = database.getGroup(id); Optional<GroupRecord> record = database.getGroup(id);
if (record.isPresent() && group.getType() == Type.UPDATE) { if (record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupUpdate(context, envelope, group, record.get(), outgoing); return handleGroupUpdate(context, content, group, record.get(), outgoing);
} else if (!record.isPresent() && group.getType() == Type.UPDATE) { } else if (!record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupCreate(context, envelope, group, outgoing); return handleGroupCreate(context, content, group, outgoing);
} else if (record.isPresent() && group.getType() == Type.QUIT) { } else if (record.isPresent() && group.getType() == Type.QUIT) {
return handleGroupLeave(context, envelope, group, record.get(), outgoing); return handleGroupLeave(context, content, group, record.get(), outgoing);
} else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) { } else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) {
return handleGroupInfoRequest(context, envelope, group, record.get()); return handleGroupInfoRequest(context, content, group, record.get());
} else { } else {
Log.w(TAG, "Received unknown type, ignoring..."); Log.w(TAG, "Received unknown type, ignoring...");
return null; return null;
@ -76,7 +76,7 @@ public class GroupMessageProcessor {
} }
private static @Nullable Long handleGroupCreate(@NonNull Context context, private static @Nullable Long handleGroupCreate(@NonNull Context context,
@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group, @NonNull SignalServiceGroup group,
boolean outgoing) boolean outgoing)
{ {
@ -95,14 +95,13 @@ public class GroupMessageProcessor {
} }
database.create(id, group.getName().orNull(), members, database.create(id, group.getName().orNull(), members,
avatar != null && avatar.isPointer() ? avatar.asPointer() : null, avatar != null && avatar.isPointer() ? avatar.asPointer() : null, null);
envelope.getRelay());
return storeMessage(context, envelope, group, builder.build(), outgoing); return storeMessage(context, content, group, builder.build(), outgoing);
} }
private static @Nullable Long handleGroupUpdate(@NonNull Context context, private static @Nullable Long handleGroupUpdate(@NonNull Context context,
@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group, @NonNull SignalServiceGroup group,
@NonNull GroupRecord groupRecord, @NonNull GroupRecord groupRecord,
boolean outgoing) boolean outgoing)
@ -156,25 +155,25 @@ public class GroupMessageProcessor {
if (!groupRecord.isActive()) database.setActive(id, true); if (!groupRecord.isActive()) database.setActive(id, true);
return storeMessage(context, envelope, group, builder.build(), outgoing); return storeMessage(context, content, group, builder.build(), outgoing);
} }
private static Long handleGroupInfoRequest(@NonNull Context context, private static Long handleGroupInfoRequest(@NonNull Context context,
@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group, @NonNull SignalServiceGroup group,
@NonNull GroupRecord record) @NonNull GroupRecord record)
{ {
if (record.getMembers().contains(Address.fromExternal(context, envelope.getSource()))) { if (record.getMembers().contains(Address.fromExternal(context, content.getSender()))) {
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)
.getJobManager() .getJobManager()
.add(new PushGroupUpdateJob(context, envelope.getSource(), group.getGroupId())); .add(new PushGroupUpdateJob(context, content.getSender(), group.getGroupId()));
} }
return null; return null;
} }
private static Long handleGroupLeave(@NonNull Context context, private static Long handleGroupLeave(@NonNull Context context,
@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group, @NonNull SignalServiceGroup group,
@NonNull GroupRecord record, @NonNull GroupRecord record,
boolean outgoing) boolean outgoing)
@ -186,11 +185,11 @@ public class GroupMessageProcessor {
GroupContext.Builder builder = createGroupContext(group); GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.QUIT); builder.setType(GroupContext.Type.QUIT);
if (members.contains(Address.fromExternal(context, envelope.getSource()))) { if (members.contains(Address.fromExternal(context, content.getSender()))) {
database.remove(id, Address.fromExternal(context, envelope.getSource())); database.remove(id, Address.fromExternal(context, content.getSender()));
if (outgoing) database.setActive(id, false); if (outgoing) database.setActive(id, false);
return storeMessage(context, envelope, group, builder.build(), outgoing); return storeMessage(context, content, group, builder.build(), outgoing);
} }
return null; return null;
@ -198,7 +197,7 @@ public class GroupMessageProcessor {
private static @Nullable Long storeMessage(@NonNull Context context, private static @Nullable Long storeMessage(@NonNull Context context,
@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group, @NonNull SignalServiceGroup group,
@NonNull GroupContext storage, @NonNull GroupContext storage,
boolean outgoing) boolean outgoing)
@ -213,7 +212,7 @@ public class GroupMessageProcessor {
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false)); Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false));
Recipient recipient = Recipient.from(context, addres, false); Recipient recipient = Recipient.from(context, addres, false);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, envelope.getTimestamp(), 0, null, Collections.emptyList()); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, null, Collections.emptyList());
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null); long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
@ -223,7 +222,7 @@ public class GroupMessageProcessor {
} else { } else {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
String body = Base64.encodeBytes(storage.toByteArray()); String body = Base64.encodeBytes(storage.toByteArray());
IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group), 0); IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt());
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage); Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage);

View File

@ -193,7 +193,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
Log.i(TAG, "Downloading attachment with no digest..."); Log.i(TAG, "Downloading attachment with no digest...");
} }
return new SignalServiceAttachmentPointer(id, null, key, relay, return new SignalServiceAttachmentPointer(id, null, key,
Optional.of(Util.toIntExact(attachment.getSize())), Optional.of(Util.toIntExact(attachment.getSize())),
Optional.absent(), Optional.absent(),
0, 0, 0, 0,

View File

@ -98,7 +98,7 @@ public class AvatarDownloadJob extends MasterSecretJob implements InjectableType
attachment = File.createTempFile("avatar", "tmp", context.getCacheDir()); attachment = File.createTempFile("avatar", "tmp", context.getCacheDir());
attachment.deleteOnExit(); attachment.deleteOnExit();
SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, relay, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false); SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false);
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE); InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500); Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);

View File

@ -252,7 +252,7 @@ public class MmsDownloadJob extends MasterSecretJob {
group = Optional.of(Address.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), true))); group = Optional.of(Address.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), true)));
} }
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false); IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false, false);
Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId); Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
if (insertResult.isPresent()) { if (insertResult.isPresent()) {

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientReader; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientReader;
@ -30,6 +31,7 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@SuppressWarnings("unused")
private static final String TAG = MultiDeviceBlockedUpdateJob.class.getSimpleName(); private static final String TAG = MultiDeviceBlockedUpdateJob.class.getSimpleName();
@Inject transient SignalServiceMessageSender messageSender; @Inject transient SignalServiceMessageSender messageSender;
@ -75,7 +77,8 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje
} }
} }
messageSender.sendMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups))); messageSender.sendMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups)),
UnidentifiedAccessUtil.getAccessForSync(context));
} }
} }

View File

@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
@ -238,7 +239,8 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
.build(); .build();
try { try {
messageSender.sendMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, complete))); messageSender.sendMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, complete)),
UnidentifiedAccessUtil.getAccessForSync(context));
} catch (IOException ioe) { } catch (IOException ioe) {
throw new NetworkException(ioe); throw new NetworkException(ioe);
} }

View File

@ -5,6 +5,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
@ -131,7 +132,8 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
.withLength(contactsFile.length()) .withLength(contactsFile.length())
.build(); .build();
messageSender.sendMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); messageSender.sendMessage(SignalServiceSyncMessage.forGroups(attachmentStream),
UnidentifiedAccessUtil.getAccessForSync(context));
} }

View File

@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -86,7 +87,7 @@ public class MultiDeviceProfileKeyUpdateJob extends MasterSecretJob implements I
SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false)); SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false));
messageSender.sendMessage(syncMessage); messageSender.sendMessage(syncMessage, UnidentifiedAccessUtil.getAccessForSync(context));
} }
@Override @Override

View File

@ -4,6 +4,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.jobmanager.SafeData;
@ -58,7 +59,8 @@ public class MultiDeviceReadReceiptUpdateJob extends ContextJob implements Injec
@Override @Override
public void onRun() throws IOException, UntrustedIdentityException { public void onRun() throws IOException, UntrustedIdentityException {
messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(enabled)))); messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(enabled))),
UnidentifiedAccessUtil.getAccessForSync(context));
} }
@Override @Override

View File

@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
@ -100,7 +101,8 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta
readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp)); readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp));
} }
messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages)); messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages),
UnidentifiedAccessUtil.getAccessForSync(context));
} }
@Override @Override

View File

@ -7,11 +7,14 @@ import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyException;
@ -101,7 +104,8 @@ public class MultiDeviceVerifiedUpdateJob extends ContextJob implements Injectab
VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus); VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus);
VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination.toPhoneString(), new IdentityKey(identityKey, 0), verifiedState, timestamp); VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination.toPhoneString(), new IdentityKey(identityKey, 0), verifiedState, timestamp);
messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(destination), false)));
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
throw new IOException(e); throw new IOException(e);
} }

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.jobs; package org.thoughtcrime.securesms.jobs;
import android.annotation.SuppressLint;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -7,11 +8,18 @@ import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.NotificationManagerCompat;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair; import android.util.Pair;
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
import org.signal.libsignal.metadata.ProtocolInvalidVersionException;
import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
import org.signal.libsignal.metadata.ProtocolNoSessionException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.ConversationListActivity; import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
@ -22,6 +30,7 @@ import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.contactshare.ContactModelMapper; import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
@ -40,6 +49,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.groups.GroupMessageProcessor; import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
@ -53,25 +64,13 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage; import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage; import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.libsignal.LegacyMessageException;
import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
import org.whispersystems.libsignal.state.SessionStore; import org.whispersystems.libsignal.state.SessionStore;
import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -212,11 +211,11 @@ public class PushDecryptJob extends ContextJob {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context); SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context)); SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore); SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, UnidentifiedAccessUtil.getCertificateValidator());
SignalServiceContent content = cipher.decrypt(envelope); SignalServiceContent content = cipher.decrypt(envelope);
if (shouldIgnore(envelope, content)) { if (shouldIgnore(content)) {
Log.i(TAG, "Ignoring message."); Log.i(TAG, "Ignoring message.");
return; return;
} }
@ -225,41 +224,45 @@ public class PushDecryptJob extends ContextJob {
SignalServiceDataMessage message = content.getDataMessage().get(); SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent();
if (message.isEndSession()) handleEndSessionMessage(envelope, message, smsMessageId); if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
else if (message.isGroupUpdate()) handleGroupMessage(envelope, message, smsMessageId); else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
else if (message.isExpirationUpdate()) handleExpirationUpdate(envelope, message, smsMessageId); else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId);
else if (isMediaMessage) handleMediaMessage(envelope, message, smsMessageId); else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId);
else if (message.getBody().isPresent()) handleTextMessage(envelope, message, smsMessageId); else if (message.getBody().isPresent()) handleTextMessage(content, message, smsMessageId);
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) { if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) {
handleUnknownGroupMessage(envelope, message.getGroupInfo().get()); handleUnknownGroupMessage(content, message.getGroupInfo().get());
} }
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
handleProfileKey(envelope, message); handleProfileKey(content, message);
}
if (content.isNeedsReceipt()) {
handleNeedsDeliveryReceipt(content, message);
} }
} else if (content.getSyncMessage().isPresent()) { } else if (content.getSyncMessage().isPresent()) {
SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(envelope, syncMessage.getSent().get()); if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get());
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get()); else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get());
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), envelope.getTimestamp()); else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
else Log.w(TAG, "Contains no known sync types..."); else Log.w(TAG, "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) { } else if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message..."); Log.i(TAG, "Got call message...");
SignalServiceCallMessage message = content.getCallMessage().get(); SignalServiceCallMessage message = content.getCallMessage().get();
if (message.getOfferMessage().isPresent()) handleCallOfferMessage(envelope, message.getOfferMessage().get(), smsMessageId); if (message.getOfferMessage().isPresent()) handleCallOfferMessage(content, message.getOfferMessage().get(), smsMessageId);
else if (message.getAnswerMessage().isPresent()) handleCallAnswerMessage(envelope, message.getAnswerMessage().get()); else if (message.getAnswerMessage().isPresent()) handleCallAnswerMessage(content, message.getAnswerMessage().get());
else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(envelope, message.getIceUpdateMessages().get()); else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(content, message.getIceUpdateMessages().get());
else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(envelope, message.getHangupMessage().get(), smsMessageId); else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(content, message.getHangupMessage().get(), smsMessageId);
else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(envelope, message.getBusyMessage().get()); else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(content, message.getBusyMessage().get());
} else if (content.getReceiptMessage().isPresent()) { } else if (content.getReceiptMessage().isPresent()) {
SignalServiceReceiptMessage message = content.getReceiptMessage().get(); SignalServiceReceiptMessage message = content.getReceiptMessage().get();
if (message.isReadReceipt()) handleReadReceipt(envelope, message); if (message.isReadReceipt()) handleReadReceipt(content, message);
else if (message.isDeliveryReceipt()) handleDeliveryReceipt(envelope, message); else if (message.isDeliveryReceipt()) handleDeliveryReceipt(content, message);
} else { } else {
Log.w(TAG, "Got unrecognized message..."); Log.w(TAG, "Got unrecognized message...");
} }
@ -267,28 +270,30 @@ public class PushDecryptJob extends ContextJob {
if (envelope.isPreKeySignalMessage()) { if (envelope.isPreKeySignalMessage()) {
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context)); ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
} }
} catch (InvalidVersionException e) { } catch (ProtocolInvalidVersionException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleInvalidVersionMessage(envelope, smsMessageId); handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException e) { } catch (ProtocolInvalidMessageException | ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleCorruptMessage(envelope, smsMessageId); handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (NoSessionException e) { } catch (StorageFailedException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleNoSessionMessage(envelope, smsMessageId); handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (LegacyMessageException e) { } catch (ProtocolNoSessionException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleLegacyMessage(envelope, smsMessageId); handleNoSessionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (DuplicateMessageException e) { } catch (ProtocolLegacyMessageException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleDuplicateMessage(envelope, smsMessageId); handleLegacyMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (UntrustedIdentityException e) { } catch (ProtocolDuplicateMessageException e) {
Log.w(TAG, e);
handleDuplicateMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (InvalidMetadataVersionException | InvalidMetadataMessageException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleUntrustedIdentityMessage(envelope, smsMessageId);
} }
} }
private void handleCallOfferMessage(@NonNull SignalServiceEnvelope envelope, private void handleCallOfferMessage(@NonNull SignalServiceContent content,
@NonNull OfferMessage message, @NonNull OfferMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
@ -301,29 +306,29 @@ public class PushDecryptJob extends ContextJob {
Intent intent = new Intent(context, WebRtcCallService.class); Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_INCOMING_CALL); intent.setAction(WebRtcCallService.ACTION_INCOMING_CALL);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender()));
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription()); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription());
intent.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, envelope.getTimestamp()); intent.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, content.getTimestamp());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(intent); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(intent);
else context.startService(intent); else context.startService(intent);
} }
} }
private void handleCallAnswerMessage(@NonNull SignalServiceEnvelope envelope, private void handleCallAnswerMessage(@NonNull SignalServiceContent content,
@NonNull AnswerMessage message) @NonNull AnswerMessage message)
{ {
Log.i(TAG, "handleCallAnswerMessage..."); Log.i(TAG, "handleCallAnswerMessage...");
Intent intent = new Intent(context, WebRtcCallService.class); Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_RESPONSE_MESSAGE); intent.setAction(WebRtcCallService.ACTION_RESPONSE_MESSAGE);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender()));
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription()); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription());
context.startService(intent); context.startService(intent);
} }
private void handleCallIceUpdateMessage(@NonNull SignalServiceEnvelope envelope, private void handleCallIceUpdateMessage(@NonNull SignalServiceContent content,
@NonNull List<IceUpdateMessage> messages) @NonNull List<IceUpdateMessage> messages)
{ {
Log.w(TAG, "handleCallIceUpdateMessage... " + messages.size()); Log.w(TAG, "handleCallIceUpdateMessage... " + messages.size());
@ -331,7 +336,7 @@ public class PushDecryptJob extends ContextJob {
Intent intent = new Intent(context, WebRtcCallService.class); Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_ICE_MESSAGE); intent.setAction(WebRtcCallService.ACTION_ICE_MESSAGE);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender()));
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP, message.getSdp()); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP, message.getSdp());
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_MID, message.getSdpMid()); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_MID, message.getSdpMid());
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_LINE_INDEX, message.getSdpMLineIndex()); intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_LINE_INDEX, message.getSdpMLineIndex());
@ -340,7 +345,7 @@ public class PushDecryptJob extends ContextJob {
} }
} }
private void handleCallHangupMessage(@NonNull SignalServiceEnvelope envelope, private void handleCallHangupMessage(@NonNull SignalServiceContent content,
@NonNull HangupMessage message, @NonNull HangupMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
@ -351,32 +356,32 @@ public class PushDecryptJob extends ContextJob {
Intent intent = new Intent(context, WebRtcCallService.class); Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_REMOTE_HANGUP); intent.setAction(WebRtcCallService.ACTION_REMOTE_HANGUP);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender()));
context.startService(intent); context.startService(intent);
} }
} }
private void handleCallBusyMessage(@NonNull SignalServiceEnvelope envelope, private void handleCallBusyMessage(@NonNull SignalServiceContent content,
@NonNull BusyMessage message) @NonNull BusyMessage message)
{ {
Intent intent = new Intent(context, WebRtcCallService.class); Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_REMOTE_BUSY); intent.setAction(WebRtcCallService.ACTION_REMOTE_BUSY);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()); intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, envelope.getSource())); intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromExternal(context, content.getSender()));
context.startService(intent); context.startService(intent);
} }
private void handleEndSessionMessage(@NonNull SignalServiceEnvelope envelope, private void handleEndSessionMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()),
envelope.getSourceDevice(), content.getSenderDevice(),
message.getTimestamp(), content.getTimestamp(),
"", Optional.absent(), 0); "", Optional.absent(), 0,
content.isNeedsReceipt());
Long threadId; Long threadId;
@ -393,7 +398,7 @@ public class PushDecryptJob extends ContextJob {
if (threadId != null) { if (threadId != null) {
SessionStore sessionStore = new TextSecureSessionStore(context); SessionStore sessionStore = new TextSecureSessionStore(context);
sessionStore.deleteAllSessions(envelope.getSource()); sessionStore.deleteAllSessions(content.getSender());
SecurityEvent.broadcastSecurityUpdateEvent(context); SecurityEvent.broadcastSecurityUpdateEvent(context);
MessageNotifier.updateNotification(context, threadId); MessageNotifier.updateNotification(context, threadId);
@ -424,15 +429,15 @@ public class PushDecryptJob extends ContextJob {
return threadId; return threadId;
} }
private void handleGroupMessage(@NonNull SignalServiceEnvelope envelope, private void handleGroupMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message, @NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
throws MmsException throws StorageFailedException
{ {
GroupMessageProcessor.process(context, envelope, message, false); GroupMessageProcessor.process(context, content, message, false);
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(envelope, message).getExpireMessages()) { if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) {
handleExpirationUpdate(envelope, message, Optional.absent()); handleExpirationUpdate(content, message, Optional.absent());
} }
if (smsMessageId.isPresent()) { if (smsMessageId.isPresent()) {
@ -440,29 +445,31 @@ public class PushDecryptJob extends ContextJob {
} }
} }
private void handleUnknownGroupMessage(@NonNull SignalServiceEnvelope envelope, private void handleUnknownGroupMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group) @NonNull SignalServiceGroup group)
{ {
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)
.getJobManager() .getJobManager()
.add(new RequestGroupInfoJob(context, envelope.getSource(), group.getGroupId())); .add(new RequestGroupInfoJob(context, content.getSender(), group.getGroupId()));
} }
private void handleExpirationUpdate(@NonNull SignalServiceEnvelope envelope, private void handleExpirationUpdate(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message, @NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
throws MmsException throws StorageFailedException
{ {
try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getMessageDestination(envelope, message); Recipient recipient = getMessageDestination(content, message);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()), IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()),
message.getTimestamp(), -1, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, true, message.getExpiresInSeconds() * 1000L, true,
Optional.fromNullable(envelope.getRelay()), content.isNeedsReceipt(),
Optional.absent(), message.getGroupInfo(), Optional.absent(),
Optional.absent(), Optional.absent(), Optional.absent()); message.getGroupInfo(),
Optional.absent(),
Optional.absent(),
Optional.absent());
database.insertSecureDecryptedMessageInbox(mediaMessage, -1); database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
@ -471,16 +478,21 @@ public class PushDecryptJob extends ContextJob {
if (smsMessageId.isPresent()) { if (smsMessageId.isPresent()) {
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get()); DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
} }
} catch (MmsException e) {
throw new StorageFailedException(e, content.getSender(), content.getSenderDevice());
}
} }
private void handleSynchronizeVerifiedMessage(@NonNull VerifiedMessage verifiedMessage) { private void handleSynchronizeVerifiedMessage(@NonNull VerifiedMessage verifiedMessage) {
IdentityUtil.processVerifiedMessage(context, verifiedMessage); IdentityUtil.processVerifiedMessage(context, verifiedMessage);
} }
private void handleSynchronizeSentMessage(@NonNull SignalServiceEnvelope envelope, private void handleSynchronizeSentMessage(@NonNull SignalServiceContent content,
@NonNull SentTranscriptMessage message) @NonNull SentTranscriptMessage message)
throws MmsException throws StorageFailedException
{ {
try {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
Long threadId; Long threadId;
@ -488,7 +500,7 @@ public class PushDecryptJob extends ContextJob {
if (message.getMessage().isEndSession()) { if (message.getMessage().isEndSession()) {
threadId = handleSynchronizeSentEndSessionMessage(message); threadId = handleSynchronizeSentEndSessionMessage(message);
} else if (message.getMessage().isGroupUpdate()) { } else if (message.getMessage().isGroupUpdate()) {
threadId = GroupMessageProcessor.process(context, envelope, message.getMessage(), true); threadId = GroupMessageProcessor.process(context, content, message.getMessage(), true);
} else if (message.getMessage().isExpirationUpdate()) { } else if (message.getMessage().isExpirationUpdate()) {
threadId = handleSynchronizeSentExpirationUpdate(message); threadId = handleSynchronizeSentExpirationUpdate(message);
} else if (message.getMessage().getAttachments().isPresent() || message.getMessage().getQuote().isPresent()) { } else if (message.getMessage().getAttachments().isPresent() || message.getMessage().getQuote().isPresent()) {
@ -498,7 +510,7 @@ public class PushDecryptJob extends ContextJob {
} }
if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false))) { if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false))) {
handleUnknownGroupMessage(envelope, message.getMessage().getGroupInfo().get()); handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get());
} }
if (message.getMessage().getProfileKey().isPresent()) { if (message.getMessage().getProfileKey().isPresent()) {
@ -519,6 +531,9 @@ public class PushDecryptJob extends ContextJob {
} }
MessageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp()); MessageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp());
} catch (MmsException e) {
throw new StorageFailedException(e, content.getSender(), content.getSenderDevice());
}
} }
private void handleSynchronizeRequestMessage(@NonNull RequestMessage message) private void handleSynchronizeRequestMessage(@NonNull RequestMessage message)
@ -572,29 +587,25 @@ public class PushDecryptJob extends ContextJob {
MessageNotifier.updateNotification(context); MessageNotifier.updateNotification(context);
} }
private void handleMediaMessage(@NonNull SignalServiceEnvelope envelope, private void handleMediaMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message, @NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
throws MmsException throws StorageFailedException
{ {
try {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getMessageDestination(envelope, message);
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote()); Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts()); Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()), IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()),
message.getTimestamp(), -1, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, false, message.getExpiresInSeconds() * 1000L, false,
Optional.fromNullable(envelope.getRelay()), content.isNeedsReceipt(),
message.getBody(), message.getBody(),
message.getGroupInfo(), message.getGroupInfo(),
message.getAttachments(), message.getAttachments(),
quote, quote,
sharedContacts); sharedContacts);
if (message.getExpiresInSeconds() != recipient.getExpireMessages()) {
handleExpirationUpdate(envelope, message, Optional.absent());
}
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1); Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
@ -612,11 +623,12 @@ public class PushDecryptJob extends ContextJob {
MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
} }
} catch (MmsException e) {
throw new StorageFailedException(e, content.getSender(), content.getSenderDevice());
}
} }
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException {
throws MmsException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getSyncMessageDestination(message); Recipient recipient = getSyncMessageDestination(message);
@ -646,7 +658,8 @@ public class PushDecryptJob extends ContextJob {
message.getTimestamp(), -1, message.getTimestamp(), -1,
message.getMessage().getExpiresInSeconds() * 1000, message.getMessage().getExpiresInSeconds() * 1000,
ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(), ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(),
sharedContacts.or(Collections.emptyList())); sharedContacts.or(Collections.emptyList()),
Collections.emptyList(), Collections.emptyList());
mediaMessage = new OutgoingSecureMediaMessage(mediaMessage); mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);
@ -677,17 +690,17 @@ public class PushDecryptJob extends ContextJob {
return threadId; return threadId;
} }
private void handleTextMessage(@NonNull SignalServiceEnvelope envelope, private void handleTextMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message, @NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
throws MmsException throws StorageFailedException
{ {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context); SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
String body = message.getBody().isPresent() ? message.getBody().get() : ""; String body = message.getBody().isPresent() ? message.getBody().get() : "";
Recipient recipient = getMessageDestination(envelope, message); Recipient recipient = getMessageDestination(content, message);
if (message.getExpiresInSeconds() != recipient.getExpireMessages()) { if (message.getExpiresInSeconds() != recipient.getExpireMessages()) {
handleExpirationUpdate(envelope, message, Optional.absent()); handleExpirationUpdate(content, message, Optional.absent());
} }
Long threadId; Long threadId;
@ -695,11 +708,12 @@ public class PushDecryptJob extends ContextJob {
if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) { if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) {
threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second; threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second;
} else { } else {
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()),
envelope.getSourceDevice(), content.getSenderDevice(),
message.getTimestamp(), body, message.getTimestamp(), body,
message.getGroupInfo(), message.getGroupInfo(),
message.getExpiresInSeconds() * 1000L); message.getExpiresInSeconds() * 1000L,
content.isNeedsReceipt());
textMessage = new IncomingEncryptedMessage(textMessage, body); textMessage = new IncomingEncryptedMessage(textMessage, body);
Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage); Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
@ -758,13 +772,13 @@ public class PushDecryptJob extends ContextJob {
return threadId; return threadId;
} }
private void handleInvalidVersionMessage(@NonNull SignalServiceEnvelope envelope, private void handleInvalidVersionMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) { if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(envelope); Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId()); smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId());
@ -775,13 +789,13 @@ public class PushDecryptJob extends ContextJob {
} }
} }
private void handleCorruptMessage(@NonNull SignalServiceEnvelope envelope, private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) { if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(envelope); Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId()); smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId());
@ -792,13 +806,13 @@ public class PushDecryptJob extends ContextJob {
} }
} }
private void handleNoSessionMessage(@NonNull SignalServiceEnvelope envelope, private void handleNoSessionMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) { if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(envelope); Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
smsDatabase.markAsNoSession(insertResult.get().getMessageId()); smsDatabase.markAsNoSession(insertResult.get().getMessageId());
@ -809,13 +823,13 @@ public class PushDecryptJob extends ContextJob {
} }
} }
private void handleLegacyMessage(@NonNull SignalServiceEnvelope envelope, private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) { if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(envelope); Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId()); smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId());
@ -827,7 +841,7 @@ public class PushDecryptJob extends ContextJob {
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private void handleDuplicateMessage(@NonNull SignalServiceEnvelope envelope, private void handleDuplicateMessage(@NonNull String sender, int senderDeviceId, long timestamp,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
// Let's start ignoring these now // Let's start ignoring these now
@ -842,45 +856,11 @@ public class PushDecryptJob extends ContextJob {
// } // }
} }
private void handleUntrustedIdentityMessage(@NonNull SignalServiceEnvelope envelope, private void handleProfileKey(@NonNull SignalServiceContent content,
@NonNull Optional<Long> smsMessageId)
{
try {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
Address sourceAddress = Address.fromExternal(context, envelope.getSource());
byte[] serialized = envelope.hasLegacyMessage() ? envelope.getLegacyMessage() : envelope.getContent();
PreKeySignalMessage whisperMessage = new PreKeySignalMessage(serialized);
IdentityKey identityKey = whisperMessage.getIdentityKey();
String encoded = Base64.encodeBytes(serialized);
IncomingTextMessage textMessage = new IncomingTextMessage(sourceAddress,
envelope.getSourceDevice(),
envelope.getTimestamp(), encoded,
Optional.absent(), 0);
if (!smsMessageId.isPresent()) {
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded, envelope.hasLegacyMessage());
Optional<InsertResult> insertResult = database.insertMessageInbox(bundleMessage);
if (insertResult.isPresent()) {
database.setMismatchedIdentity(insertResult.get().getMessageId(), sourceAddress, identityKey);
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
database.updateMessageBody(smsMessageId.get(), encoded);
database.markAsPreKeyBundle(smsMessageId.get());
database.setMismatchedIdentity(smsMessageId.get(), sourceAddress, identityKey);
}
} catch (InvalidMessageException | InvalidVersionException e) {
throw new AssertionError(e);
}
}
private void handleProfileKey(@NonNull SignalServiceEnvelope envelope,
@NonNull SignalServiceDataMessage message) @NonNull SignalServiceDataMessage message)
{ {
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
Address sourceAddress = Address.fromExternal(context, envelope.getSource()); Address sourceAddress = Address.fromExternal(context, content.getSender());
Recipient recipient = Recipient.from(context, sourceAddress, false); Recipient recipient = Recipient.from(context, sourceAddress, false);
if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) { if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) {
@ -889,17 +869,27 @@ public class PushDecryptJob extends ContextJob {
} }
} }
private void handleDeliveryReceipt(@NonNull SignalServiceEnvelope envelope, private void handleNeedsDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message)
{
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SendDeliveryReceiptJob(context, Address.fromExternal(context, content.getSender()), message.getTimestamp()));
}
@SuppressLint("DefaultLocale")
private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message) @NonNull SignalServiceReceiptMessage message)
{ {
for (long timestamp : message.getTimestamps()) { for (long timestamp : message.getTimestamps()) {
Log.i(TAG, String.format("Received encrypted delivery receipt: (XXXXX, %d)", timestamp)); Log.i(TAG, String.format("Received encrypted delivery receipt: (XXXXX, %d)", timestamp));
DatabaseFactory.getMmsSmsDatabase(context) DatabaseFactory.getMmsSmsDatabase(context)
.incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp), System.currentTimeMillis()); .incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, content.getSender()), timestamp), System.currentTimeMillis());
} }
} }
private void handleReadReceipt(@NonNull SignalServiceEnvelope envelope, @SuppressLint("DefaultLocale")
private void handleReadReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message) @NonNull SignalServiceReceiptMessage message)
{ {
if (TextSecurePreferences.isReadReceiptsEnabled(context)) { if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
@ -907,7 +897,7 @@ public class PushDecryptJob extends ContextJob {
Log.i(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp)); Log.i(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp));
DatabaseFactory.getMmsSmsDatabase(context) DatabaseFactory.getMmsSmsDatabase(context)
.incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp), envelope.getTimestamp()); .incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, content.getSender()), timestamp), content.getTimestamp());
} }
} }
} }
@ -960,12 +950,11 @@ public class PushDecryptJob extends ContextJob {
return Optional.of(contacts); return Optional.of(contacts);
} }
private Optional<InsertResult> insertPlaceholder(@NonNull SignalServiceEnvelope envelope) { private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp) {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context); SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, sender),
envelope.getSourceDevice(), senderDevice, timestamp, "",
envelope.getTimestamp(), "", Optional.absent(), 0, false);
Optional.absent(), 0);
textMessage = new IncomingEncryptedMessage(textMessage, ""); textMessage = new IncomingEncryptedMessage(textMessage, "");
return database.insertMessageInbox(textMessage); return database.insertMessageInbox(textMessage);
@ -979,21 +968,20 @@ public class PushDecryptJob extends ContextJob {
} }
} }
private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) {
private Recipient getMessageDestination(SignalServiceEnvelope envelope, SignalServiceDataMessage message) {
if (message.getGroupInfo().isPresent()) { if (message.getGroupInfo().isPresent()) {
return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false); return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)), false);
} else { } else {
return Recipient.from(context, Address.fromExternal(context, envelope.getSource()), false); return Recipient.from(context, Address.fromExternal(context, content.getSender()), false);
} }
} }
private boolean shouldIgnore(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) { private boolean shouldIgnore(@NonNull SignalServiceContent content) {
Recipient sender = Recipient.from(context, Address.fromExternal(context, envelope.getSource()), false); Recipient sender = Recipient.from(context, Address.fromExternal(context, content.getSender()), false);
if (content.getDataMessage().isPresent()) { if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get(); SignalServiceDataMessage message = content.getDataMessage().get();
Recipient conversation = getMessageDestination(envelope, message); Recipient conversation = getMessageDestination(content, message);
if (conversation.isGroupRecipient() && conversation.isBlocked()) { if (conversation.isGroupRecipient() && conversation.isBlocked()) {
return true; return true;
@ -1023,4 +1011,25 @@ public class PushDecryptJob extends ContextJob {
return false; return false;
} }
@SuppressWarnings("WeakerAccess")
private static class StorageFailedException extends Exception {
private final String sender;
private final int senderDevice;
private StorageFailedException(Exception e, String sender, int senderDevice) {
super(e);
this.sender = sender;
this.senderDevice = senderDevice;
}
public String getSender() {
return sender;
}
public int getSenderDevice() {
return senderDevice;
}
}
} }

View File

@ -4,15 +4,18 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo; import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
@ -24,25 +27,27 @@ import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Quote; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Quote;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
@ -96,17 +101,56 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
@Override @Override
public void onPushSend() public void onPushSend()
throws MmsException, IOException, NoSuchMessageException throws IOException, MmsException, NoSuchMessageException, RetryLaterException
{ {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(messageId); OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
List<NetworkFailure> existingNetworkFailures = message.getNetworkFailures();
List<IdentityKeyMismatch> existingIdentityMismatches = message.getIdentityKeyMismatches();
try { try {
Log.i(TAG, "Sending message: " + messageId); Log.i(TAG, "Sending message: " + messageId);
List<Address> target;
deliver(message, filterAddress == null ? null : Address.fromSerialized(filterAddress)); if (filterAddress != null) target = Collections.singletonList(Address.fromSerialized(filterAddress));
else if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(NetworkFailure::getAddress).toList();
else target = getGroupMessageRecipients(message.getRecipient().getAddress().toGroupString(), messageId);
List<SendMessageResult> results = deliver(message, target);
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Address.fromSerialized(result.getAddress().getNumber()))).toList();
List<IdentityKeyMismatch> identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Address.fromSerialized(result.getAddress().getNumber()), result.getIdentityFailure().getIdentityKey())).toList();
Set<Address> successAddresses = Stream.of(results).filter(result -> result.getSuccess() != null).map(result -> Address.fromSerialized(result.getAddress().getNumber())).collect(Collectors.toSet());
List<NetworkFailure> resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successAddresses.contains(failure.getAddress())).toList();
List<IdentityKeyMismatch> resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successAddresses.contains(failure.getAddress())).toList();
List<SendMessageResult> successes = Stream.of(results).filter(result -> result.getSuccess() != null).toList();
for (NetworkFailure resolvedFailure : resolvedNetworkFailures) {
database.removeFailure(messageId, resolvedFailure);
existingNetworkFailures.remove(resolvedFailure);
}
for (IdentityKeyMismatch resolvedIdentity : resolvedIdentityFailures) {
database.removeMismatchedIdentity(messageId, resolvedIdentity.getAddress(), resolvedIdentity.getIdentityKey());
existingIdentityMismatches.remove(resolvedIdentity);
}
if (!networkFailures.isEmpty()) {
database.addFailures(messageId, networkFailures);
}
for (IdentityKeyMismatch mismatch : identityMismatches) {
database.addMismatchedIdentity(messageId, mismatch.getAddress(), mismatch.getIdentityKey());
}
for (SendMessageResult success : successes) {
DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Address.fromSerialized(success.getAddress().getNumber()),
messageId,
success.getSuccess().isUnidentified());
}
if (existingNetworkFailures.isEmpty() && networkFailures.isEmpty() && identityMismatches.isEmpty() && existingIdentityMismatches.isEmpty()) {
database.markAsSent(messageId, true); database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments()); markAttachmentsUploaded(messageId, message.getAttachments());
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
@ -115,46 +159,28 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.getExpiringMessageManager() .getExpiringMessageManager()
.scheduleDeletion(messageId, true, message.getExpiresIn()); .scheduleDeletion(messageId, true, message.getExpiresIn());
} }
} else if (!networkFailures.isEmpty()) {
throw new RetryLaterException();
} else if (!identityMismatches.isEmpty()) {
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
Log.i(TAG, "Sent message: " + messageId);
} catch (InvalidNumberException | RecipientFormattingException | UndeliverableMessageException e) { } catch (InvalidNumberException | RecipientFormattingException | UndeliverableMessageException e) {
Log.w(TAG, e); Log.w(TAG, e);
database.markAsSentFailed(messageId); database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId); notifyMediaMessageDeliveryFailed(context, messageId);
} catch (EncapsulatedExceptions e) { } catch (UntrustedIdentityException e) {
Log.w(TAG, e); Log.w(TAG, e);
List<NetworkFailure> failures = new LinkedList<>();
for (NetworkFailureException nfe : e.getNetworkExceptions()) {
failures.add(new NetworkFailure(Address.fromSerialized(nfe.getE164number())));
}
for (UntrustedIdentityException uie : e.getUntrustedIdentityExceptions()) {
database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey());
}
database.addFailures(messageId, failures);
if (e.getNetworkExceptions().isEmpty() && e.getUntrustedIdentityExceptions().isEmpty()) {
database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments());
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
database.markExpireStarted(messageId);
ApplicationContext.getInstance(context)
.getExpiringMessageManager()
.scheduleDeletion(messageId, true, message.getExpiresIn());
}
} else {
database.markAsSentFailed(messageId); database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId); notifyMediaMessageDeliveryFailed(context, messageId);
} }
} }
}
@Override @Override
public boolean onShouldRetryThrowable(Exception exception) { public boolean onShouldRetryThrowable(Exception exception) {
if (exception instanceof IOException) return true; if (exception instanceof IOException) return true;
if (exception instanceof RetryLaterException) return true;
return false; return false;
} }
@ -163,23 +189,24 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId); DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
} }
private void deliver(OutgoingMediaMessage message, @Nullable Address filterAddress) private List<SendMessageResult> deliver(OutgoingMediaMessage message, @NonNull List<Address> destinations)
throws IOException, RecipientFormattingException, InvalidNumberException, throws IOException, RecipientFormattingException, InvalidNumberException,
EncapsulatedExceptions, UndeliverableMessageException UndeliverableMessageException, UntrustedIdentityException
{ {
String groupId = message.getRecipient().getAddress().toGroupString(); String groupId = message.getRecipient().getAddress().toGroupString();
Optional<byte[]> profileKey = getProfileKey(message.getRecipient()); Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
List<Address> recipients = getGroupMessageRecipients(groupId, messageId);
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
List<Attachment> scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments()); List<Attachment> scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments());
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(scaledAttachments); List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(scaledAttachments);
Optional<Quote> quote = getQuoteFor(message); Optional<Quote> quote = getQuoteFor(message);
List<SharedContact> sharedContacts = getSharedContactsFor(message); List<SharedContact> sharedContacts = getSharedContactsFor(message);
List<SignalServiceAddress> addresses = Stream.of(destinations).map(this::getPushAddress).toList();
List<SignalServiceAddress> addresses; List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = Stream.of(addresses)
.map(address -> Address.fromSerialized(address.getNumber()))
if (filterAddress != null) addresses = getPushAddresses(filterAddress); .map(address -> Recipient.from(context, address, false))
else addresses = getPushAddresses(recipients); .map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient))
.toList();
if (message.isGroup()) { if (message.isGroup()) {
OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message; OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message;
@ -193,7 +220,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.asGroupMessage(group) .asGroupMessage(group)
.build(); .build();
messageSender.sendMessage(addresses, groupDataMessage); return messageSender.sendMessage(addresses, unidentifiedAccess, groupDataMessage);
} else { } else {
SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedId(groupId)); SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedId(groupId));
SignalServiceDataMessage groupMessage = SignalServiceDataMessage.newBuilder() SignalServiceDataMessage groupMessage = SignalServiceDataMessage.newBuilder()
@ -208,20 +235,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.withSharedContacts(sharedContacts) .withSharedContacts(sharedContacts)
.build(); .build();
messageSender.sendMessage(addresses, groupMessage); return messageSender.sendMessage(addresses, unidentifiedAccess, groupMessage);
} }
} }
private List<SignalServiceAddress> getPushAddresses(Address address) {
List<SignalServiceAddress> addresses = new LinkedList<>();
addresses.add(getPushAddress(address));
return addresses;
}
private List<SignalServiceAddress> getPushAddresses(List<Address> addresses) {
return Stream.of(addresses).map(this::getPushAddress).toList();
}
private @NonNull List<Address> getGroupMessageRecipients(String groupId, long messageId) { private @NonNull List<Address> getGroupMessageRecipients(String groupId, long messageId) {
List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId); List<GroupReceiptInfo> destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId);
if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getAddress).toList(); if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getAddress).toList();

View File

@ -4,15 +4,15 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -122,7 +122,9 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType {
.withExpiration(groupRecipient.getExpireMessages()) .withExpiration(groupRecipient.getExpireMessages())
.build(); .build();
messageSender.sendMessage(new SignalServiceAddress(source), message); messageSender.sendMessage(new SignalServiceAddress(source),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(source), false)),
message);
} }
@Override @Override

View File

@ -3,16 +3,16 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
@ -80,9 +80,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
try { try {
Log.i(TAG, "Sending message: " + messageId); Log.i(TAG, "Sending message: " + messageId);
deliver(message); boolean unidentified = deliver(message);
database.markAsSent(messageId, true); database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message.getAttachments()); markAttachmentsUploaded(messageId, message.getAttachments());
database.markUnidentified(messageId, unidentified);
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
database.markExpireStarted(messageId); database.markExpireStarted(messageId);
@ -117,7 +118,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
notifyMediaMessageDeliveryFailed(context, messageId); notifyMediaMessageDeliveryFailed(context, messageId);
} }
private void deliver(OutgoingMediaMessage message) private boolean deliver(OutgoingMediaMessage message)
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException, throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
UndeliverableMessageException UndeliverableMessageException
{ {
@ -144,7 +145,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
.asExpirationUpdate(message.isExpirationUpdate()) .asExpirationUpdate(message.isExpirationUpdate())
.build(); .build();
messageSender.sendMessage(address, mediaMessage); return messageSender.sendMessage(address, UnidentifiedAccessUtil.getAccessFor(context, message.getRecipient()), mediaMessage).getSuccess().isUnidentified();
} catch (UnregisteredUserException e) { } catch (UnregisteredUserException e) {
Log.w(TAG, e); Log.w(TAG, e);
throw new InsecureFallbackApprovalException(e); throw new InsecureFallbackApprovalException(e);

View File

@ -4,15 +4,12 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException; import java.io.IOException;

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.jobs; package org.thoughtcrime.securesms.jobs;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -25,6 +26,7 @@ public abstract class PushReceivedJob extends ContextJob {
public void processEnvelope(@NonNull SignalServiceEnvelope envelope) { public void processEnvelope(@NonNull SignalServiceEnvelope envelope) {
synchronized (RECEIVE_LOCK) { synchronized (RECEIVE_LOCK) {
if (envelope.hasSource()) {
Address source = Address.fromExternal(context, envelope.getSource()); Address source = Address.fromExternal(context, envelope.getSource());
Recipient recipient = Recipient.from(context, source, false); Recipient recipient = Recipient.from(context, source, false);
@ -32,10 +34,11 @@ public abstract class PushReceivedJob extends ContextJob {
DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED); DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED);
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, recipient, false)); ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, recipient, false));
} }
}
if (envelope.isReceipt()) { if (envelope.isReceipt()) {
handleReceipt(envelope); handleReceipt(envelope);
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage()) { } else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender()) {
handleMessage(envelope); handleMessage(envelope);
} else { } else {
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType()); Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
@ -47,6 +50,7 @@ public abstract class PushReceivedJob extends ContextJob {
new PushDecryptJob(context).processMessage(envelope); new PushDecryptJob(context).processMessage(envelope);
} }
@SuppressLint("DefaultLocale")
private void handleReceipt(SignalServiceEnvelope envelope) { private void handleReceipt(SignalServiceEnvelope envelope) {
Log.i(TAG, String.format("Received receipt: (XXXXX, %d)", envelope.getTimestamp())); Log.i(TAG, String.format("Received receipt: (XXXXX, %d)", envelope.getTimestamp()));
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()),

View File

@ -92,7 +92,6 @@ public abstract class PushSendJob extends SendJob {
} }
protected SignalServiceAddress getPushAddress(Address address) { protected SignalServiceAddress getPushAddress(Address address) {
// String relay = TextSecureDirectory.getInstance(context).getRelay(address.toPhoneString());
String relay = null; String relay = null;
return new SignalServiceAddress(address.toPhoneString(), Optional.fromNullable(relay)); return new SignalServiceAddress(address.toPhoneString(), Optional.fromNullable(relay));
} }

View File

@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.NoSuchMessageException;
@ -20,7 +21,9 @@ import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
@ -76,8 +79,9 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
try { try {
Log.i(TAG, "Sending message: " + messageId); Log.i(TAG, "Sending message: " + messageId);
deliver(record); boolean unidentified = deliver(record);
database.markAsSent(messageId, true); database.markAsSent(messageId, true);
database.markUnidentified(messageId, unidentified);
if (record.getExpiresIn() > 0) { if (record.getExpiresIn() > 0) {
database.markExpireStarted(messageId); database.markExpireStarted(messageId);
@ -118,12 +122,16 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
} }
} }
private void deliver(SmsMessageRecord message) private boolean deliver(SmsMessageRecord message)
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException
{ {
try { try {
SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress()); SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress());
Optional<byte[]> profileKey = getProfileKey(message.getIndividualRecipient()); Optional<byte[]> profileKey = getProfileKey(message.getIndividualRecipient());
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, message.getIndividualRecipient());
Log.w(TAG, "Have access key to use: " + unidentifiedAccess.isPresent());
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder() SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getDateSent()) .withTimestamp(message.getDateSent())
.withBody(message.getBody()) .withBody(message.getBody())
@ -132,8 +140,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
.asEndSessionMessage(message.isEndSession()) .asEndSessionMessage(message.isEndSession())
.build(); .build();
return messageSender.sendMessage(address, unidentifiedAccess, textSecureMessage).getSuccess().isUnidentified();
messageSender.sendMessage(address, textSecureMessage);
} catch (UnregisteredUserException e) { } catch (UnregisteredUserException e) {
Log.w(TAG, e); Log.w(TAG, e);
throw new InsecureFallbackApprovalException(e); throw new InsecureFallbackApprovalException(e);

View File

@ -8,6 +8,12 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
@ -52,8 +58,11 @@ public class RefreshAttributesJob extends ContextJob implements InjectableType {
int registrationId = TextSecurePreferences.getLocalRegistrationId(context); int registrationId = TextSecurePreferences.getLocalRegistrationId(context);
boolean fetchesMessages = TextSecurePreferences.isGcmDisabled(context); boolean fetchesMessages = TextSecurePreferences.isGcmDisabled(context);
String pin = TextSecurePreferences.getRegistrationLockPin(context); String pin = TextSecurePreferences.getRegistrationLockPin(context);
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context);
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
signalAccountManager.setAccountAttributes(signalingKey, registrationId, fetchesMessages, pin); signalAccountManager.setAccountAttributes(signalingKey, registrationId, fetchesMessages, pin,
unidentifiedAccessKey, universalUnidentifiedAccess);
} }
@Override @Override

View File

@ -3,10 +3,14 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
@ -23,6 +27,7 @@ import androidx.work.Data;
public class RequestGroupInfoJob extends ContextJob implements InjectableType { public class RequestGroupInfoJob extends ContextJob implements InjectableType {
@SuppressWarnings("unused")
private static final String TAG = RequestGroupInfoJob.class.getSimpleName(); private static final String TAG = RequestGroupInfoJob.class.getSimpleName();
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
@ -77,7 +82,9 @@ public class RequestGroupInfoJob extends ContextJob implements InjectableType {
.withTimestamp(System.currentTimeMillis()) .withTimestamp(System.currentTimeMillis())
.build(); .build();
messageSender.sendMessage(new SignalServiceAddress(source), message); messageSender.sendMessage(new SignalServiceAddress(source),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromExternal(context, source), false)),
message);
} }
@Override @Override

View File

@ -5,14 +5,16 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
@ -20,11 +22,15 @@ import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe; import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.crypto.ProfileCipher; import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException; import java.io.IOException;
@ -89,11 +95,25 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
throws IOException, InvalidKeyException, InvalidNumberException throws IOException, InvalidKeyException, InvalidNumberException
{ {
String number = recipient.getAddress().toPhoneString(); String number = recipient.getAddress().toPhoneString();
SignalServiceProfile profile = retrieveProfile(number); Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(recipient);
SignalServiceProfile profile;
try {
profile = retrieveProfile(number, unidentifiedAccess);
} catch (AuthorizationFailedException e) {
if (unidentifiedAccess.isPresent()) {
// XXX Update UI
profile = retrieveProfile(number, Optional.absent());
} else {
throw e;
}
}
setIdentityKey(recipient, profile.getIdentityKey()); setIdentityKey(recipient, profile.getIdentityKey());
setProfileName(recipient, profile.getName()); setProfileName(recipient, profile.getName());
setProfileAvatar(recipient, profile.getAvatar()); setProfileAvatar(recipient, profile.getAvatar());
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
} }
private void handleGroupRecipient(Recipient group) private void handleGroupRecipient(Recipient group)
@ -106,18 +126,20 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
} }
} }
private SignalServiceProfile retrieveProfile(@NonNull String number) throws IOException { private SignalServiceProfile retrieveProfile(@NonNull String number, Optional<UnidentifiedAccess> unidentifiedAccess)
throws IOException
{
SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe(); SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe();
if (pipe != null) { if (pipe != null) {
try { try {
return pipe.getProfile(new SignalServiceAddress(number)); return pipe.getProfile(new SignalServiceAddress(number), unidentifiedAccess);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }
} }
return receiver.retrieveProfile(new SignalServiceAddress(number)); return receiver.retrieveProfile(new SignalServiceAddress(number), unidentifiedAccess);
} }
private void setIdentityKey(Recipient recipient, String identityKeyValue) { private void setIdentityKey(Recipient recipient, String identityKeyValue) {
@ -143,6 +165,30 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
} }
} }
private void setUnidentifiedAccessMode(Recipient recipient, String unidentifiedAccessVerifier, boolean unrestrictedUnidentifiedAccess) {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
byte[] profileKey = recipient.getProfileKey();
// XXX Update UI
if (unrestrictedUnidentifiedAccess) {
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
} else {
ProfileCipher profileCipher = new ProfileCipher(profileKey);
boolean verifiedUnidentifiedAccess;
try {
verifiedUnidentifiedAccess = profileCipher.verifyUnidentifiedAccess(Base64.decode(unidentifiedAccessVerifier));
} catch (IOException e) {
Log.w(TAG, e);
verifiedUnidentifiedAccess = false;
}
recipientDatabase.setUnidentifiedAccessMode(recipient, verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED);
}
}
private void setProfileName(Recipient recipient, String profileName) { private void setProfileName(Recipient recipient, String profileName) {
try { try {
byte[] profileKey = recipient.getProfileKey(); byte[] profileKey = recipient.getProfileKey();
@ -172,4 +218,14 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
.add(new RetrieveProfileAvatarJob(context, recipient, profileAvatar)); .add(new RetrieveProfileAvatarJob(context, recipient, profileAvatar));
} }
} }
private Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Recipient recipient) {
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
if (unidentifiedAccess.isPresent()) {
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
}
return Optional.absent();
}
} }

View File

@ -0,0 +1,71 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import javax.inject.Inject;
import androidx.work.Data;
@SuppressWarnings("WeakerAccess")
public class RotateCertificateJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 1L;
private static final String TAG = RotateCertificateJob.class.getName();
@Inject transient SignalServiceAccountManager accountManager;
public RotateCertificateJob() {
super(null, null);
}
public RotateCertificateJob(Context context) {
super(context, JobParameters.newBuilder()
.withGroupId("__ROTATE_SENDER_CERTIFICATE__")
.withNetworkRequirement()
.create());
}
@NonNull
@Override
protected Data serialize(@NonNull Data.Builder dataBuilder) {
return dataBuilder.build();
}
@Override
protected void initialize(@NonNull SafeData data) {
}
@Override
public void onAdded() {}
@Override
public void onRun() throws IOException {
byte[] certificate = accountManager.getSenderCertificate();
TextSecurePreferences.setUnidentifiedAccessCertificate(context, certificate);
}
@Override
public boolean onShouldRetry(Exception e) {
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() {
Log.w(TAG, "Failed to rotate sender certificate!");
}
}

View File

@ -0,0 +1,100 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.Collections;
import javax.inject.Inject;
import androidx.work.Data;
public class SendDeliveryReceiptJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 1L;
private static final String KEY_ADDRESS = "address";
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_TIMESTAMP = "timestamp";
private static final String TAG = SendReadReceiptJob.class.getSimpleName();
@Inject
transient SignalServiceMessageSender messageSender;
private String address;
private long messageId;
private long timestamp;
public SendDeliveryReceiptJob() {
super(null, null);
}
public SendDeliveryReceiptJob(Context context, Address address, long messageId) {
super(context, JobParameters.newBuilder()
.withNetworkRequirement()
.create());
this.address = address.serialize();
this.messageId = messageId;
this.timestamp = System.currentTimeMillis();
}
@Override
public void onAdded() {}
@NonNull
@Override
protected Data serialize(@NonNull Data.Builder dataBuilder) {
return dataBuilder.putString(KEY_ADDRESS, address)
.putLong(KEY_MESSAGE_ID, messageId)
.putLong(KEY_TIMESTAMP, timestamp)
.build();
}
@Override
protected void initialize(@NonNull SafeData data) {
this.address = data.getString(KEY_ADDRESS);
this.messageId = data.getLong(KEY_MESSAGE_ID);
this.timestamp = data.getLong(KEY_TIMESTAMP);
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
SignalServiceAddress remoteAddress = new SignalServiceAddress(address);
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
Collections.singletonList(messageId),
timestamp);
messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)),
receiptMessage);
}
@Override
public boolean onShouldRetry(Exception e) {
if (e instanceof PushNetworkException) return true;
return false;
}
@Override
public void onCanceled() {
Log.w(TAG, "Failed to send delivery receipt to: " + address);
}
}

View File

@ -4,11 +4,13 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters; import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData; import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@ -86,7 +88,9 @@ public class SendReadReceiptJob extends ContextJob implements InjectableType {
SignalServiceAddress remoteAddress = new SignalServiceAddress(address); SignalServiceAddress remoteAddress = new SignalServiceAddress(address);
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp); SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp);
messageSender.sendReceipt(remoteAddress, receiptMessage); messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)),
receiptMessage);
} }
@Override @Override

View File

@ -24,6 +24,7 @@ public class IncomingMediaMessage {
private final long expiresIn; private final long expiresIn;
private final boolean expirationUpdate; private final boolean expirationUpdate;
private final QuoteModel quote; private final QuoteModel quote;
private final boolean unidentified;
private final List<Attachment> attachments = new LinkedList<>(); private final List<Attachment> attachments = new LinkedList<>();
private final List<Contact> sharedContacts = new LinkedList<>(); private final List<Contact> sharedContacts = new LinkedList<>();
@ -35,7 +36,8 @@ public class IncomingMediaMessage {
List<Attachment> attachments, List<Attachment> attachments,
int subscriptionId, int subscriptionId,
long expiresIn, long expiresIn,
boolean expirationUpdate) boolean expirationUpdate,
boolean unidentified)
{ {
this.from = from; this.from = from;
this.groupId = groupId.orNull(); this.groupId = groupId.orNull();
@ -46,6 +48,7 @@ public class IncomingMediaMessage {
this.expiresIn = expiresIn; this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate; this.expirationUpdate = expirationUpdate;
this.quote = null; this.quote = null;
this.unidentified = unidentified;
this.attachments.addAll(attachments); this.attachments.addAll(attachments);
} }
@ -55,7 +58,7 @@ public class IncomingMediaMessage {
int subscriptionId, int subscriptionId,
long expiresIn, long expiresIn,
boolean expirationUpdate, boolean expirationUpdate,
Optional<String> relay, boolean unidentified,
Optional<String> body, Optional<String> body,
Optional<SignalServiceGroup> group, Optional<SignalServiceGroup> group,
Optional<List<SignalServiceAttachment>> attachments, Optional<List<SignalServiceAttachment>> attachments,
@ -70,6 +73,7 @@ public class IncomingMediaMessage {
this.expiresIn = expiresIn; this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate; this.expirationUpdate = expirationUpdate;
this.quote = quote.orNull(); this.quote = quote.orNull();
this.unidentified = unidentified;
if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false)); if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false));
else this.groupId = null; else this.groupId = null;
@ -125,4 +129,8 @@ public class IncomingMediaMessage {
public List<Contact> getSharedContacts() { public List<Contact> getSharedContacts() {
return sharedContacts; return sharedContacts;
} }
public boolean isUnidentified() {
return unidentified;
}
} }

View File

@ -6,6 +6,8 @@ import android.text.TextUtils;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.LinkedList; import java.util.LinkedList;
@ -21,13 +23,19 @@ public class OutgoingMediaMessage {
private final int subscriptionId; private final int subscriptionId;
private final long expiresIn; private final long expiresIn;
private final QuoteModel outgoingQuote; private final QuoteModel outgoingQuote;
private final List<NetworkFailure> networkFailures = new LinkedList<>();
private final List<IdentityKeyMismatch> identityKeyMismatches = new LinkedList<>();
private final List<Contact> contacts = new LinkedList<>(); private final List<Contact> contacts = new LinkedList<>();
public OutgoingMediaMessage(Recipient recipient, String message, public OutgoingMediaMessage(Recipient recipient, String message,
List<Attachment> attachments, long sentTimeMillis, List<Attachment> attachments, long sentTimeMillis,
int subscriptionId, long expiresIn, int subscriptionId, long expiresIn,
int distributionType, @Nullable QuoteModel outgoingQuote, int distributionType,
@NonNull List<Contact> contacts) @Nullable QuoteModel outgoingQuote,
@NonNull List<Contact> contacts,
@NonNull List<NetworkFailure> networkFailures,
@NonNull List<IdentityKeyMismatch> identityKeyMismatches)
{ {
this.recipient = recipient; this.recipient = recipient;
this.body = message; this.body = message;
@ -39,6 +47,8 @@ public class OutgoingMediaMessage {
this.outgoingQuote = outgoingQuote; this.outgoingQuote = outgoingQuote;
this.contacts.addAll(contacts); this.contacts.addAll(contacts);
this.networkFailures.addAll(networkFailures);
this.identityKeyMismatches.addAll(identityKeyMismatches);
} }
public OutgoingMediaMessage(Recipient recipient, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, long expiresIn, int distributionType, @Nullable QuoteModel outgoingQuote, @NonNull List<Contact> contacts) public OutgoingMediaMessage(Recipient recipient, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, long expiresIn, int distributionType, @Nullable QuoteModel outgoingQuote, @NonNull List<Contact> contacts)
@ -47,7 +57,8 @@ public class OutgoingMediaMessage {
buildMessage(slideDeck, message), buildMessage(slideDeck, message),
slideDeck.asAttachments(), slideDeck.asAttachments(),
sentTimeMillis, subscriptionId, sentTimeMillis, subscriptionId,
expiresIn, distributionType, outgoingQuote, contacts); expiresIn, distributionType, outgoingQuote,
contacts, new LinkedList<>(), new LinkedList<>());
} }
public OutgoingMediaMessage(OutgoingMediaMessage that) { public OutgoingMediaMessage(OutgoingMediaMessage that) {
@ -60,6 +71,8 @@ public class OutgoingMediaMessage {
this.expiresIn = that.expiresIn; this.expiresIn = that.expiresIn;
this.outgoingQuote = that.outgoingQuote; this.outgoingQuote = that.outgoingQuote;
this.identityKeyMismatches.addAll(that.identityKeyMismatches);
this.networkFailures.addAll(that.networkFailures);
this.contacts.addAll(that.contacts); this.contacts.addAll(that.contacts);
} }
@ -111,6 +124,14 @@ public class OutgoingMediaMessage {
return contacts; return contacts;
} }
public @NonNull List<NetworkFailure> getNetworkFailures() {
return networkFailures;
}
public @NonNull List<IdentityKeyMismatch> getIdentityKeyMismatches() {
return identityKeyMismatches;
}
private static String buildMessage(SlideDeck slideDeck, String message) { private static String buildMessage(SlideDeck slideDeck, String message) {
if (!TextUtils.isEmpty(message) && !TextUtils.isEmpty(slideDeck.getBody())) { if (!TextUtils.isEmpty(message) && !TextUtils.isEmpty(slideDeck.getBody())) {
return slideDeck.getBody() + "\n\n" + message; return slideDeck.getBody() + "\n\n" + message;

View File

@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Collections;
import java.util.List; import java.util.List;
public class OutgoingSecureMediaMessage extends OutgoingMediaMessage { public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
@ -19,7 +20,7 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
@Nullable QuoteModel quote, @Nullable QuoteModel quote,
@NonNull List<Contact> contacts) @NonNull List<Contact> contacts)
{ {
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts); super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts, Collections.emptyList(), Collections.emptyList());
} }
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {

View File

@ -76,7 +76,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver {
if (recipient.isGroupRecipient()) { if (recipient.isGroupRecipient()) {
Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message");
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList()); OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
replyThreadId = MessageSender.send(context, reply, threadId, false, null); replyThreadId = MessageSender.send(context, reply, threadId, false, null);
} else { } else {
Log.w("AndroidAutoReplyReceiver", "Sending regular message "); Log.w("AndroidAutoReplyReceiver", "Sending regular message ");

View File

@ -72,7 +72,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
long expiresIn = recipient.getExpireMessages() * 1000L; long expiresIn = recipient.getExpireMessages() * 1000L;
if (recipient.isGroupRecipient()) { if (recipient.isGroupRecipient()) {
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList()); OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
threadId = MessageSender.send(context, reply, -1, false, null); threadId = MessageSender.send(context, reply, -1, false, null);
} else if (TextSecurePreferences.isPushRegistered(context) && recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) { } else if (TextSecurePreferences.isPushRegistered(context) && recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
OutgoingEncryptedMessage reply = new OutgoingEncryptedMessage(recipient, responseText.toString(), expiresIn); OutgoingEncryptedMessage reply = new OutgoingEncryptedMessage(recipient, responseText.toString(), expiresIn);

View File

@ -3,6 +3,10 @@ package org.thoughtcrime.securesms.push;
import android.content.Context; import android.content.Context;
import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
@ -20,4 +24,12 @@ public class SecurityEventListener implements SignalServiceMessageSender.EventLi
public void onSecurityEvent(SignalServiceAddress textSecureAddress) { public void onSecurityEvent(SignalServiceAddress textSecureAddress) {
SecurityEvent.broadcastSecurityUpdateEvent(context); SecurityEvent.broadcastSecurityUpdateEvent(context);
} }
@Override
public void onUnidentifiedAccessFailed(SignalServiceAddress address) {
// XXX Update UI
DatabaseFactory.getRecipientDatabase(context)
.setUnidentifiedAccessMode(Recipient.from(context, Address.fromSerialized(address.getNumber()), false),
RecipientDatabase.UnidentifiedAccessMode.DISABLED);
}
} }

View File

@ -21,6 +21,7 @@ import okhttp3.TlsVersion;
public class SignalServiceNetworkAccess { public class SignalServiceNetworkAccess {
@SuppressWarnings("unused")
private static final String TAG = SignalServiceNetworkAccess.class.getName(); private static final String TAG = SignalServiceNetworkAccess.class.getName();
private static final String COUNTRY_CODE_EGYPT = "+20"; private static final String COUNTRY_CODE_EGYPT = "+20";

View File

@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
@ -93,6 +94,7 @@ public class Recipient implements RecipientModifiedListener {
private boolean profileSharing; private boolean profileSharing;
private String notificationChannel; private String notificationChannel;
private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) { public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) {
@ -142,6 +144,8 @@ public class Recipient implements RecipientModifiedListener {
this.profileName = stale.profileName; this.profileName = stale.profileName;
this.profileAvatar = stale.profileAvatar; this.profileAvatar = stale.profileAvatar;
this.profileSharing = stale.profileSharing; this.profileSharing = stale.profileSharing;
this.unidentifiedAccessMode = stale.unidentifiedAccessMode;
this.participants.clear(); this.participants.clear();
this.participants.addAll(stale.participants); this.participants.addAll(stale.participants);
} }
@ -166,6 +170,8 @@ public class Recipient implements RecipientModifiedListener {
this.profileName = details.get().profileName; this.profileName = details.get().profileName;
this.profileAvatar = details.get().profileAvatar; this.profileAvatar = details.get().profileAvatar;
this.profileSharing = details.get().profileSharing; this.profileSharing = details.get().profileSharing;
this.unidentifiedAccessMode = details.get().unidentifiedAccessMode;
this.participants.clear(); this.participants.clear();
this.participants.addAll(details.get().participants); this.participants.addAll(details.get().participants);
} }
@ -197,6 +203,7 @@ public class Recipient implements RecipientModifiedListener {
Recipient.this.profileAvatar = result.profileAvatar; Recipient.this.profileAvatar = result.profileAvatar;
Recipient.this.profileSharing = result.profileSharing; Recipient.this.profileSharing = result.profileSharing;
Recipient.this.profileName = result.profileName; Recipient.this.profileName = result.profileName;
Recipient.this.unidentifiedAccessMode = result.unidentifiedAccessMode;
Recipient.this.participants.clear(); Recipient.this.participants.clear();
Recipient.this.participants.addAll(result.participants); Recipient.this.participants.addAll(result.participants);
@ -243,6 +250,8 @@ public class Recipient implements RecipientModifiedListener {
this.profileName = details.profileName; this.profileName = details.profileName;
this.profileAvatar = details.profileAvatar; this.profileAvatar = details.profileAvatar;
this.profileSharing = details.profileSharing; this.profileSharing = details.profileSharing;
this.unidentifiedAccessMode = details.unidentifiedAccessMode;
this.participants.addAll(details.participants); this.participants.addAll(details.participants);
this.resolving = false; this.resolving = false;
} }
@ -616,6 +625,18 @@ public class Recipient implements RecipientModifiedListener {
notifyListeners(); notifyListeners();
} }
public synchronized UnidentifiedAccessMode getUnidentifiedAccessMode() {
return unidentifiedAccessMode;
}
public void setUnidentifiedAccessMode(@NonNull UnidentifiedAccessMode unidentifiedAccessMode) {
synchronized (this) {
this.unidentifiedAccessMode = unidentifiedAccessMode;
}
notifyListeners();
}
public synchronized boolean isSystemContact() { public synchronized boolean isSystemContact() {
return contactUri != null; return contactUri != null;
} }

View File

@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.SoftHashMap; import org.thoughtcrime.securesms.util.SoftHashMap;
@ -175,6 +176,7 @@ class RecipientProvider {
final boolean profileSharing; final boolean profileSharing;
final boolean systemContact; final boolean systemContact;
@Nullable final String notificationChannel; @Nullable final String notificationChannel;
@NonNull final UnidentifiedAccessMode unidentifiedAccessMode;
RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId, RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId,
boolean systemContact, @Nullable RecipientSettings settings, boolean systemContact, @Nullable RecipientSettings settings,
@ -202,6 +204,7 @@ class RecipientProvider {
this.profileSharing = settings != null && settings.isProfileSharing(); this.profileSharing = settings != null && settings.isProfileSharing();
this.systemContact = systemContact; this.systemContact = systemContact;
this.notificationChannel = settings != null ? settings.getNotificationChannel() : null; this.notificationChannel = settings != null ? settings.getNotificationChannel() : null;
this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED;
if (name == null && settings != null) this.name = settings.getSystemDisplayName(); if (name == null && settings != null) this.name = settings.getSystemDisplayName();
else this.name = name; else this.name = name;

View File

@ -41,6 +41,7 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
private static final long REQUEST_TIMEOUT_MINUTES = 1; private static final long REQUEST_TIMEOUT_MINUTES = 1;
private static SignalServiceMessagePipe pipe = null; private static SignalServiceMessagePipe pipe = null;
public static SignalServiceMessagePipe unidentifiedPipe = null;
private final Context context; private final Context context;
private final NetworkRequirement networkRequirement; private final NetworkRequirement networkRequirement;
@ -114,9 +115,10 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
} }
} }
private void shutdown(SignalServiceMessagePipe pipe) { private void shutdown(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
try { try {
pipe.shutdown(); pipe.shutdown();
unidentifiedPipe.shutdown();
} catch (Throwable t) { } catch (Throwable t) {
Log.w(TAG, t); Log.w(TAG, t);
} }
@ -126,6 +128,10 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
return pipe; return pipe;
} }
public static @Nullable SignalServiceMessagePipe getUnidentifiedPipe() {
return unidentifiedPipe;
}
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler { private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
MessageRetrievalThread() { MessageRetrievalThread() {
@ -141,8 +147,10 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
Log.i(TAG, "Making websocket connection...."); Log.i(TAG, "Making websocket connection....");
pipe = receiver.createMessagePipe(); pipe = receiver.createMessagePipe();
unidentifiedPipe = receiver.createUnidentifiedMessagePipe();
SignalServiceMessagePipe localPipe = pipe; SignalServiceMessagePipe localPipe = pipe;
SignalServiceMessagePipe unidentifiedLocalPipe = unidentifiedPipe;
try { try {
while (isConnectionNecessary()) { while (isConnectionNecessary()) {
@ -163,7 +171,7 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
Log.w(TAG, e); Log.w(TAG, e);
} finally { } finally {
Log.w(TAG, "Shutting down pipe..."); Log.w(TAG, "Shutting down pipe...");
shutdown(localPipe); shutdown(localPipe, unidentifiedLocalPipe);
} }
Log.i(TAG, "Looping..."); Log.i(TAG, "Looping...");

View File

@ -0,0 +1,254 @@
package org.thoughtcrime.securesms.service;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.gcm.GcmBroadcastReceiver;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirementProvider;
import org.thoughtcrime.securesms.jobmanager.requirements.RequirementListener;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
public class MessageRetrievalService extends Service implements InjectableType, RequirementListener {
private static final String TAG = MessageRetrievalService.class.getSimpleName();
public static final String ACTION_ACTIVITY_STARTED = "ACTIVITY_STARTED";
public static final String ACTION_ACTIVITY_FINISHED = "ACTIVITY_FINISHED";
public static final String ACTION_PUSH_RECEIVED = "PUSH_RECEIVED";
public static final String ACTION_INITIALIZE = "INITIALIZE";
public static final int FOREGROUND_ID = 313399;
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private NetworkRequirement networkRequirement;
private NetworkRequirementProvider networkRequirementProvider;
@Inject
public SignalServiceMessageReceiver receiver;
private int activeActivities = 0;
private List<Intent> pushPending = new LinkedList<>();
private MessageRetrievalThread retrievalThread = null;
public static SignalServiceMessagePipe pipe = null;
public static SignalServiceMessagePipe unidentifiedPipe = null;
@Override
public void onCreate() {
super.onCreate();
ApplicationContext.getInstance(this).injectDependencies(this);
networkRequirement = new NetworkRequirement(this);
networkRequirementProvider = new NetworkRequirementProvider(this);
networkRequirementProvider.setListener(this);
retrievalThread = new MessageRetrievalThread();
retrievalThread.start();
setForegroundIfNecessary();
}
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) return START_STICKY;
if (ACTION_ACTIVITY_STARTED.equals(intent.getAction())) incrementActive();
else if (ACTION_ACTIVITY_FINISHED.equals(intent.getAction())) decrementActive();
else if (ACTION_PUSH_RECEIVED.equals(intent.getAction())) incrementPushReceived(intent);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (retrievalThread != null) {
retrievalThread.stopThread();
}
sendBroadcast(new Intent("org.thoughtcrime.securesms.RESTART"));
}
@Override
public void onRequirementStatusChanged() {
synchronized (this) {
notifyAll();
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void setForegroundIfNecessary() {
if (TextSecurePreferences.isGcmDisabled(this)) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationChannels.OTHER);
builder.setContentTitle(getString(R.string.MessageRetrievalService_signal));
builder.setContentText(getString(R.string.MessageRetrievalService_background_connection_enabled));
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setWhen(0);
builder.setSmallIcon(R.drawable.ic_signal_grey_24dp);
startForeground(FOREGROUND_ID, builder.build());
}
}
private synchronized void incrementActive() {
activeActivities++;
Log.d(TAG, "Active Count: " + activeActivities);
notifyAll();
}
private synchronized void decrementActive() {
activeActivities--;
Log.d(TAG, "Active Count: " + activeActivities);
notifyAll();
}
private synchronized void incrementPushReceived(Intent intent) {
pushPending.add(intent);
notifyAll();
}
private synchronized void decrementPushReceived() {
if (!pushPending.isEmpty()) {
Intent intent = pushPending.remove(0);
GcmBroadcastReceiver.completeWakefulIntent(intent);
notifyAll();
}
}
private synchronized boolean isConnectionNecessary() {
boolean isGcmDisabled = TextSecurePreferences.isGcmDisabled(this);
Log.d(TAG, String.format("Network requirement: %s, active activities: %s, push pending: %s, gcm disabled: %b",
networkRequirement.isPresent(), activeActivities, pushPending.size(), isGcmDisabled));
return TextSecurePreferences.isPushRegistered(this) &&
TextSecurePreferences.isWebsocketRegistered(this) &&
(activeActivities > 0 || !pushPending.isEmpty() || isGcmDisabled) &&
networkRequirement.isPresent();
}
private synchronized void waitForConnectionNecessary() {
try {
while (!isConnectionNecessary()) wait();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
private void shutdown(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
try {
pipe.shutdown();
unidentifiedPipe.shutdown();
} catch (Throwable t) {
Log.w(TAG, t);
}
}
public static void registerActivityStarted(Context activity) {
Intent intent = new Intent(activity, MessageRetrievalService.class);
intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_STARTED);
activity.startService(intent);
}
public static void registerActivityStopped(Context activity) {
Intent intent = new Intent(activity, MessageRetrievalService.class);
intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_FINISHED);
activity.startService(intent);
}
public static @Nullable SignalServiceMessagePipe getPipe() {
return pipe;
}
public static @Nullable SignalServiceMessagePipe getUnidentifiedPipe() {
return unidentifiedPipe;
}
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
private AtomicBoolean stopThread = new AtomicBoolean(false);
MessageRetrievalThread() {
super("MessageRetrievalService");
setUncaughtExceptionHandler(this);
}
@Override
public void run() {
while (!stopThread.get()) {
Log.i(TAG, "Waiting for websocket state change....");
waitForConnectionNecessary();
Log.i(TAG, "Making websocket connection....");
pipe = receiver.createMessagePipe();
unidentifiedPipe = receiver.createUnidentifiedMessagePipe();
SignalServiceMessagePipe localPipe = pipe;
SignalServiceMessagePipe unidentifiedLocalPipe = unidentifiedPipe;
try {
while (isConnectionNecessary() && !stopThread.get()) {
try {
Log.i(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
envelope -> {
Log.i(TAG, "Retrieved envelope! " + envelope.getSource());
new PushContentReceiveJob(getApplicationContext()).processEnvelope(envelope);
decrementPushReceived();
});
} catch (TimeoutException e) {
Log.w(TAG, "Application level read timeout...");
} catch (InvalidVersionException e) {
Log.w(TAG, e);
}
}
} catch (Throwable e) {
Log.w(TAG, e);
} finally {
Log.w(TAG, "Shutting down pipe...");
shutdown(localPipe, unidentifiedLocalPipe);
}
Log.i(TAG, "Looping...");
}
Log.i(TAG, "Exiting...");
}
private void stopThread() {
stopThread.set(true);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Log.w(TAG, "*** Uncaught exception!");
Log.w(TAG, e);
}
}
}

View File

@ -0,0 +1,38 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class RotateSenderCertificateListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.DAYS.toMillis(1);
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getUnidentifiedAccessCertificateRotationTime(context);
}
@Override
protected long onAlarm(Context context, long scheduledTime) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RotateCertificateJob(context));
long nextTime = System.currentTimeMillis() + INTERVAL;
TextSecurePreferences.setUnidentifiedAccessCertificateRotationTime(context, nextTime);
return nextTime;
}
public static void schedule(Context context) {
new RotateSenderCertificateListener().onReceive(context, new Intent());
}
}

View File

@ -28,6 +28,7 @@ import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.WebRtcCallActivity; import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
@ -992,7 +993,9 @@ public class WebRtcCallService extends Service implements InjectableType,
Callable<Boolean> callable = new Callable<Boolean>() { Callable<Boolean> callable = new Callable<Boolean>() {
@Override @Override
public Boolean call() throws Exception { public Boolean call() throws Exception {
messageSender.sendCallMessage(new SignalServiceAddress(recipient.getAddress().toPhoneString()), callMessage); messageSender.sendCallMessage(new SignalServiceAddress(recipient.getAddress().toPhoneString()),
UnidentifiedAccessUtil.getAccessFor(WebRtcCallService.this, recipient),
callMessage);
return true; return true;
} }
}; };

View File

@ -7,7 +7,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
public class IncomingJoinedMessage extends IncomingTextMessage { public class IncomingJoinedMessage extends IncomingTextMessage {
public IncomingJoinedMessage(Address sender) { public IncomingJoinedMessage(Address sender) {
super(sender, 1, System.currentTimeMillis(), null, Optional.<SignalServiceGroup>absent(), 0); super(sender, 1, System.currentTimeMillis(), null, Optional.<SignalServiceGroup>absent(), 0, false);
} }
@Override @Override

View File

@ -42,6 +42,7 @@ public class IncomingTextMessage implements Parcelable {
private final boolean push; private final boolean push;
private final int subscriptionId; private final int subscriptionId;
private final long expiresInMillis; private final long expiresInMillis;
private final boolean unidentified;
public IncomingTextMessage(@NonNull Context context, @NonNull SmsMessage message, int subscriptionId) { public IncomingTextMessage(@NonNull Context context, @NonNull SmsMessage message, int subscriptionId) {
this.message = message.getDisplayMessageBody(); this.message = message.getDisplayMessageBody();
@ -56,11 +57,12 @@ public class IncomingTextMessage implements Parcelable {
this.expiresInMillis = 0; this.expiresInMillis = 0;
this.groupId = null; this.groupId = null;
this.push = false; this.push = false;
this.unidentified = false;
} }
public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis,
String encodedBody, Optional<SignalServiceGroup> group, String encodedBody, Optional<SignalServiceGroup> group,
long expiresInMillis) long expiresInMillis, boolean unidentified)
{ {
this.message = encodedBody; this.message = encodedBody;
this.sender = sender; this.sender = sender;
@ -73,6 +75,7 @@ public class IncomingTextMessage implements Parcelable {
this.push = true; this.push = true;
this.subscriptionId = -1; this.subscriptionId = -1;
this.expiresInMillis = expiresInMillis; this.expiresInMillis = expiresInMillis;
this.unidentified = unidentified;
if (group.isPresent()) { if (group.isPresent()) {
this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false)); this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get().getGroupId(), false));
@ -94,6 +97,7 @@ public class IncomingTextMessage implements Parcelable {
this.push = (in.readInt() == 1); this.push = (in.readInt() == 1);
this.subscriptionId = in.readInt(); this.subscriptionId = in.readInt();
this.expiresInMillis = in.readLong(); this.expiresInMillis = in.readLong();
this.unidentified = in.readInt() == 1;
} }
public IncomingTextMessage(IncomingTextMessage base, String newBody) { public IncomingTextMessage(IncomingTextMessage base, String newBody) {
@ -109,6 +113,7 @@ public class IncomingTextMessage implements Parcelable {
this.push = base.isPush(); this.push = base.isPush();
this.subscriptionId = base.getSubscriptionId(); this.subscriptionId = base.getSubscriptionId();
this.expiresInMillis = base.getExpiresIn(); this.expiresInMillis = base.getExpiresIn();
this.unidentified = base.isUnidentified();
} }
public IncomingTextMessage(List<IncomingTextMessage> fragments) { public IncomingTextMessage(List<IncomingTextMessage> fragments) {
@ -130,6 +135,7 @@ public class IncomingTextMessage implements Parcelable {
this.push = fragments.get(0).isPush(); this.push = fragments.get(0).isPush();
this.subscriptionId = fragments.get(0).getSubscriptionId(); this.subscriptionId = fragments.get(0).getSubscriptionId();
this.expiresInMillis = fragments.get(0).getExpiresIn(); this.expiresInMillis = fragments.get(0).getExpiresIn();
this.unidentified = fragments.get(0).isUnidentified();
} }
protected IncomingTextMessage(@NonNull Address sender, @Nullable Address groupId) protected IncomingTextMessage(@NonNull Address sender, @Nullable Address groupId)
@ -146,6 +152,7 @@ public class IncomingTextMessage implements Parcelable {
this.push = true; this.push = true;
this.subscriptionId = -1; this.subscriptionId = -1;
this.expiresInMillis = 0; this.expiresInMillis = 0;
this.unidentified = false;
} }
public int getSubscriptionId() { public int getSubscriptionId() {
@ -240,6 +247,10 @@ public class IncomingTextMessage implements Parcelable {
return false; return false;
} }
public boolean isUnidentified() {
return unidentified;
}
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;
@ -258,5 +269,6 @@ public class IncomingTextMessage implements Parcelable {
out.writeParcelable(groupId, flags); out.writeParcelable(groupId, flags);
out.writeInt(push ? 1 : 0); out.writeInt(push ? 1 : 0);
out.writeInt(subscriptionId); out.writeInt(subscriptionId);
out.writeInt(unidentified ? 1 : 0);
} }
} }

View File

@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.transport;
import java.io.IOException; import java.io.IOException;
public class RetryLaterException extends Exception { public class RetryLaterException extends Exception {
public RetryLaterException() {}
public RetryLaterException(Exception e) { public RetryLaterException(Exception e) {
super(e); super(e);
} }

View File

@ -7,7 +7,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
@ -19,6 +18,7 @@ import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage; import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage;
@ -82,7 +82,7 @@ public class IdentityUtil {
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId()); SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
if (remote) { if (remote) {
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0); IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
else incoming = new IncomingIdentityDefaultMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming);
@ -102,7 +102,7 @@ public class IdentityUtil {
} }
if (remote) { if (remote) {
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0); IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false);
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
else incoming = new IncomingIdentityDefaultMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming);
@ -132,14 +132,14 @@ public class IdentityUtil {
while ((groupRecord = reader.getNext()) != null) { while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) { if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) {
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId()); SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0); IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming); IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
smsDatabase.insertMessageInbox(groupUpdate); smsDatabase.insertMessageInbox(groupUpdate);
} }
} }
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0); IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false);
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming); IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate); Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);

View File

@ -84,6 +84,7 @@ public class TextSecurePreferences {
private static final String UPDATE_APK_DOWNLOAD_ID = "pref_update_apk_download_id"; private static final String UPDATE_APK_DOWNLOAD_ID = "pref_update_apk_download_id";
private static final String UPDATE_APK_DIGEST = "pref_update_apk_digest"; private static final String UPDATE_APK_DIGEST = "pref_update_apk_digest";
private static final String SIGNED_PREKEY_ROTATION_TIME_PREF = "pref_signed_pre_key_rotation_time"; private static final String SIGNED_PREKEY_ROTATION_TIME_PREF = "pref_signed_pre_key_rotation_time";
private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications"; private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
private static final String SHOW_INVITE_REMINDER_PREF = "pref_show_invite_reminder"; private static final String SHOW_INVITE_REMINDER_PREF = "pref_show_invite_reminder";
public static final String MESSAGE_BODY_TEXT_SIZE_PREF = "pref_message_body_text_size"; public static final String MESSAGE_BODY_TEXT_SIZE_PREF = "pref_message_body_text_size";
@ -164,6 +165,10 @@ public class TextSecurePreferences {
private static final String NEEDS_MESSAGE_PULL = "pref_needs_message_pull"; private static final String NEEDS_MESSAGE_PULL = "pref_needs_message_pull";
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF = "pref_unidentified_access_certificate_rotation_time";
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE = "pref_unidentified_access_certificate";
private static final String UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access";
public static boolean isScreenLockEnabled(@NonNull Context context) { public static boolean isScreenLockEnabled(@NonNull Context context) {
return getBooleanPreference(context, SCREEN_LOCK, false); return getBooleanPreference(context, SCREEN_LOCK, false);
} }
@ -506,6 +511,39 @@ public class TextSecurePreferences {
return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true); return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true);
} }
public static long getUnidentifiedAccessCertificateRotationTime(Context context) {
return getLongPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF, 0L);
}
public static void setUnidentifiedAccessCertificateRotationTime(Context context, long value) {
setLongPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF, value);
}
public static void setUnidentifiedAccessCertificate(Context context, byte[] value) {
setStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE, Base64.encodeBytes(value));
}
public static byte[] getUnidentifiedAccessCertificate(Context context) {
try {
String result = getStringPreference(context, UNIDENTIFIED_ACCESS_CERTIFICATE, null);
if (result != null) {
return Base64.decode(result);
}
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
public static boolean isUniversalUnidentifiedAccess(Context context) {
return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false);
}
public static void setUniversalUnidentifiedAccess(Context context, boolean value) {
setBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, value);
}
public static long getSignedPreKeyRotationTime(Context context) { public static long getSignedPreKeyRotationTime(Context context) {
return getLongPreference(context, SIGNED_PREKEY_ROTATION_TIME_PREF, 0L); return getLongPreference(context, SIGNED_PREKEY_ROTATION_TIME_PREF, 0L);
} }