That fuck shit the fascists are using
at master 325 lines 12 kB view raw
1/* 2 * Copyright (C) 2011 Whisper Systems 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17package org.tm.archive.service; 18 19import android.annotation.SuppressLint; 20import android.app.AlarmManager; 21import android.app.Notification; 22import android.app.PendingIntent; 23import android.app.Service; 24import android.content.Context; 25import android.content.Intent; 26import android.os.AsyncTask; 27import android.os.Binder; 28import android.os.Build; 29import android.os.IBinder; 30import android.os.SystemClock; 31 32import androidx.annotation.NonNull; 33import androidx.annotation.Nullable; 34import androidx.core.app.NotificationCompat; 35 36import org.signal.core.util.logging.Log; 37import org.tm.archive.BuildConfig; 38import org.tm.archive.DummyActivity; 39import org.tm.archive.MainActivity; 40import org.tm.archive.R; 41import org.tm.archive.crypto.InvalidPassphraseException; 42import org.tm.archive.crypto.MasterSecret; 43import org.tm.archive.crypto.MasterSecretUtil; 44import org.tm.archive.dependencies.ApplicationDependencies; 45import org.tm.archive.migrations.ApplicationMigrations; 46import org.tm.archive.notifications.NotificationChannels; 47import org.tm.archive.util.DynamicLanguage; 48import org.tm.archive.util.ServiceUtil; 49import org.tm.archive.util.TextSecurePreferences; 50 51import java.util.concurrent.TimeUnit; 52 53/** 54 * Small service that stays running to keep a key cached in memory. 55 * 56 * @author Moxie Marlinspike 57 */ 58 59public class KeyCachingService extends Service { 60 61 private static final String TAG = Log.tag(KeyCachingService.class); 62 63 public static final int SERVICE_RUNNING_ID = 4141; 64 65 public static final String KEY_PERMISSION = BuildConfig.APPLICATION_ID + ".ACCESS_SECRETS"; 66 public static final String NEW_KEY_EVENT = BuildConfig.APPLICATION_ID + ".service.action.NEW_KEY_EVENT"; 67 public static final String CLEAR_KEY_EVENT = BuildConfig.APPLICATION_ID + ".service.action.CLEAR_KEY_EVENT"; 68 public static final String LOCK_TOGGLED_EVENT = BuildConfig.APPLICATION_ID + ".service.action.LOCK_ENABLED_EVENT"; 69 private static final String PASSPHRASE_EXPIRED_EVENT = BuildConfig.APPLICATION_ID + ".service.action.PASSPHRASE_EXPIRED_EVENT"; 70 public static final String CLEAR_KEY_ACTION = BuildConfig.APPLICATION_ID + ".service.action.CLEAR_KEY"; 71 public static final String DISABLE_ACTION = BuildConfig.APPLICATION_ID + ".service.action.DISABLE"; 72 public static final String LOCALE_CHANGE_EVENT = BuildConfig.APPLICATION_ID + ".service.action.LOCALE_CHANGE_EVENT"; 73 74 private DynamicLanguage dynamicLanguage = new DynamicLanguage(); 75 76 private final IBinder binder = new KeySetBinder(); 77 78 private static MasterSecret masterSecret; 79 80 public KeyCachingService() {} 81 82 public static synchronized boolean isLocked(Context context) { 83 boolean locked = masterSecret == null && (!TextSecurePreferences.isPasswordDisabled(context) || TextSecurePreferences.isScreenLockEnabled(context)); 84 85 if (locked) { 86 Log.d(TAG, "Locked! PasswordDisabled: " + TextSecurePreferences.isPasswordDisabled(context) + ", ScreenLock: " + TextSecurePreferences.isScreenLockEnabled(context)); 87 } 88 89 return locked; 90 } 91 92 public static synchronized @Nullable MasterSecret getMasterSecret(Context context) { 93 if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) { 94 try { 95 return MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE); 96 } catch (InvalidPassphraseException e) { 97 Log.w(TAG, e); 98 } 99 } 100 101 return masterSecret; 102 } 103 104 public static void onAppForegrounded(@NonNull Context context) { 105 if (TextSecurePreferences.isScreenLockEnabled(context) || !TextSecurePreferences.isPasswordDisabled(context)) { 106 ServiceUtil.getAlarmManager(context).cancel(buildExpirationPendingIntent(context)); 107 } 108 } 109 110 public static void onAppBackgrounded(@NonNull Context context) { 111 startTimeoutIfAppropriate(context); 112 } 113 114 @SuppressLint("StaticFieldLeak") 115 public void setMasterSecret(final MasterSecret masterSecret) { 116 synchronized (KeyCachingService.class) { 117 KeyCachingService.masterSecret = masterSecret; 118 119 foregroundService(); 120 broadcastNewSecret(); 121 startTimeoutIfAppropriate(this); 122 123 new AsyncTask<Void, Void, Void>() { 124 @Override 125 protected Void doInBackground(Void... params) { 126 if (!ApplicationMigrations.isUpdate(KeyCachingService.this)) { 127 ApplicationDependencies.getMessageNotifier().updateNotification(KeyCachingService.this); 128 } 129 return null; 130 } 131 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 132 } 133 } 134 135 @Override 136 public int onStartCommand(Intent intent, int flags, int startId) { 137 if (intent == null) return START_NOT_STICKY; 138 Log.d(TAG, "onStartCommand, " + intent.getAction()); 139 140 if (intent.getAction() != null) { 141 switch (intent.getAction()) { 142 case CLEAR_KEY_ACTION: handleClearKey(); break; 143 case PASSPHRASE_EXPIRED_EVENT: handleClearKey(); break; 144 case DISABLE_ACTION: handleDisableService(); break; 145 case LOCALE_CHANGE_EVENT: handleLocaleChanged(); break; 146 case LOCK_TOGGLED_EVENT: handleLockToggled(); break; 147 } 148 } 149 150 return START_NOT_STICKY; 151 } 152 153 @Override 154 public void onCreate() { 155 Log.i(TAG, "onCreate()"); 156 super.onCreate(); 157 158 if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) { 159 try { 160 MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE); 161 setMasterSecret(masterSecret); 162 } catch (InvalidPassphraseException e) { 163 Log.w(TAG, e); 164 } 165 } 166 } 167 168 @Override 169 public void onDestroy() { 170 super.onDestroy(); 171 Log.w(TAG, "KCS Is Being Destroyed!"); 172 handleClearKey(); 173 } 174 175 /** 176 * Workaround for Android bug: 177 * https://code.google.com/p/android/issues/detail?id=53313 178 */ 179 @Override 180 public void onTaskRemoved(Intent rootIntent) { 181 Intent intent = new Intent(this, DummyActivity.class); 182 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 183 startActivity(intent); 184 } 185 186 @SuppressLint("StaticFieldLeak") 187 private void handleClearKey() { 188 Log.i(TAG, "handleClearKey()"); 189 KeyCachingService.masterSecret = null; 190 stopForeground(true); 191 192 Intent intent = new Intent(CLEAR_KEY_EVENT); 193 intent.setPackage(getApplicationContext().getPackageName()); 194 195 sendBroadcast(intent, KEY_PERMISSION); 196 197 new AsyncTask<Void, Void, Void>() { 198 @Override 199 protected Void doInBackground(Void... params) { 200 ApplicationDependencies.getMessageNotifier().updateNotification(KeyCachingService.this); 201 return null; 202 } 203 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 204 } 205 206 private void handleLockToggled() { 207 stopForeground(true); 208 209 try { 210 MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE); 211 setMasterSecret(masterSecret); 212 } catch (InvalidPassphraseException e) { 213 Log.w(TAG, e); 214 } 215 } 216 217 private void handleDisableService() { 218 if (TextSecurePreferences.isPasswordDisabled(this) && 219 !TextSecurePreferences.isScreenLockEnabled(this)) 220 { 221 stopForeground(true); 222 } 223 } 224 225 private void handleLocaleChanged() { 226 dynamicLanguage.updateServiceLocale(this); 227 foregroundService(); 228 } 229 230 private static void startTimeoutIfAppropriate(@NonNull Context context) { 231 boolean appVisible = ApplicationDependencies.getAppForegroundObserver().isForegrounded(); 232 boolean secretSet = KeyCachingService.masterSecret != null; 233 234 boolean timeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(context); 235 boolean passLockActive = timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(context); 236 237 long screenTimeout = TextSecurePreferences.getScreenLockTimeout(context); 238 boolean screenLockActive = screenTimeout >= 60 && TextSecurePreferences.isScreenLockEnabled(context); 239 240 if (!appVisible && secretSet && (passLockActive || screenLockActive)) { 241 long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(context); 242 long screenLockTimeoutSeconds = TextSecurePreferences.getScreenLockTimeout(context); 243 244 long timeoutMillis; 245 246 if (!TextSecurePreferences.isPasswordDisabled(context)) timeoutMillis = TimeUnit.MINUTES.toMillis(passphraseTimeoutMinutes); 247 else timeoutMillis = TimeUnit.SECONDS.toMillis(screenLockTimeoutSeconds); 248 249 Log.i(TAG, "Starting timeout: " + timeoutMillis); 250 251 AlarmManager alarmManager = ServiceUtil.getAlarmManager(context); 252 PendingIntent expirationIntent = buildExpirationPendingIntent(context); 253 254 alarmManager.cancel(expirationIntent); 255 alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeoutMillis, expirationIntent); 256 } 257 } 258 259 private void foregroundService() { 260 if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) { 261 stopForeground(true); 262 return; 263 } 264 265 Log.i(TAG, "foregrounding KCS"); 266 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationChannels.getInstance().LOCKED_STATUS); 267 268 builder.setContentTitle(getString(R.string.KeyCachingService_passphrase_cached)); 269 builder.setContentText(getString(R.string.KeyCachingService_signal_passphrase_cached)); 270 builder.setSmallIcon(R.drawable.icon_cached); 271 builder.setWhen(0); 272 builder.setPriority(Notification.PRIORITY_MIN); 273 builder.setOngoing(true); 274 275 builder.addAction(R.drawable.symbol_lock_24, getString(R.string.KeyCachingService_lock), buildLockIntent()); 276 builder.setContentIntent(buildLaunchIntent()); 277 278 stopForeground(true); 279 startForeground(SERVICE_RUNNING_ID, builder.build()); 280 } 281 282 private void broadcastNewSecret() { 283 Log.i(TAG, "Broadcasting new secret..."); 284 285 Intent intent = new Intent(NEW_KEY_EVENT); 286 intent.setPackage(getApplicationContext().getPackageName()); 287 288 sendBroadcast(intent, KEY_PERMISSION); 289 } 290 291 private PendingIntent buildLockIntent() { 292 Intent intent = new Intent(this, KeyCachingService.class); 293 intent.setAction(PASSPHRASE_EXPIRED_EVENT); 294 return PendingIntent.getService(getApplicationContext(), 0, intent, getPendingIntentFlags()); 295 } 296 297 private PendingIntent buildLaunchIntent() { 298 // TODO [greyson] Navigation 299 return PendingIntent.getActivity(getApplicationContext(), 0, MainActivity.clearTop(this), getPendingIntentFlags()); 300 } 301 302 private static PendingIntent buildExpirationPendingIntent(@NonNull Context context) { 303 Intent expirationIntent = new Intent(PASSPHRASE_EXPIRED_EVENT, null, context, KeyCachingService.class); 304 return PendingIntent.getService(context, 0, expirationIntent, getPendingIntentFlags()); 305 } 306 307 private static int getPendingIntentFlags() { 308 if (Build.VERSION.SDK_INT >= 23) { 309 return PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT; 310 } else { 311 return PendingIntent.FLAG_UPDATE_CURRENT; 312 } 313 } 314 315 @Override 316 public IBinder onBind(Intent arg0) { 317 return binder; 318 } 319 320 public class KeySetBinder extends Binder { 321 public KeyCachingService getService() { 322 return KeyCachingService.this; 323 } 324 } 325}