That fuck shit the fascists are using
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}