1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.adb;
18 
19 import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
20 
21 import android.annotation.TestApi;
22 import android.app.ActivityManager;
23 import android.content.ActivityNotFoundException;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.content.pm.UserInfo;
29 import android.content.res.Resources;
30 import android.database.ContentObserver;
31 import android.debug.AdbProtoEnums;
32 import android.net.LocalSocket;
33 import android.net.LocalSocketAddress;
34 import android.net.Uri;
35 import android.os.Environment;
36 import android.os.FileUtils;
37 import android.os.Handler;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.SystemClock;
41 import android.os.SystemProperties;
42 import android.os.UserHandle;
43 import android.os.UserManager;
44 import android.provider.Settings;
45 import android.service.adb.AdbDebuggingManagerProto;
46 import android.util.AtomicFile;
47 import android.util.Base64;
48 import android.util.Slog;
49 import android.util.StatsLog;
50 import android.util.Xml;
51 
52 import com.android.internal.R;
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.internal.util.FastXmlSerializer;
55 import com.android.internal.util.XmlUtils;
56 import com.android.internal.util.dump.DualDumpOutputStream;
57 import com.android.server.FgThread;
58 
59 import org.xmlpull.v1.XmlPullParser;
60 import org.xmlpull.v1.XmlPullParserException;
61 import org.xmlpull.v1.XmlSerializer;
62 
63 import java.io.BufferedReader;
64 import java.io.File;
65 import java.io.FileInputStream;
66 import java.io.FileOutputStream;
67 import java.io.FileReader;
68 import java.io.IOException;
69 import java.io.InputStream;
70 import java.io.OutputStream;
71 import java.nio.charset.StandardCharsets;
72 import java.security.MessageDigest;
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.HashMap;
76 import java.util.HashSet;
77 import java.util.Iterator;
78 import java.util.List;
79 import java.util.Map;
80 import java.util.Set;
81 
82 /**
83  * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
84  * that are authorized to connect to the ADB service itself.
85  */
86 public class AdbDebuggingManager {
87     private static final String TAG = "AdbDebuggingManager";
88     private static final boolean DEBUG = false;
89 
90     private static final String ADBD_SOCKET = "adbd";
91     private static final String ADB_DIRECTORY = "misc/adb";
92     // This file contains keys that will always be allowed to connect to the device via adb.
93     private static final String ADB_KEYS_FILE = "adb_keys";
94     // This file contains keys that will be allowed to connect without user interaction as long
95     // as a subsequent connection occurs within the allowed duration.
96     private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
97     private static final int BUFFER_SIZE = 65536;
98 
99     private final Context mContext;
100     private final Handler mHandler;
101     private AdbDebuggingThread mThread;
102     private boolean mAdbEnabled = false;
103     private String mFingerprints;
104     private final List<String> mConnectedKeys;
105     private String mConfirmComponent;
106     private final File mTestUserKeyFile;
107 
AdbDebuggingManager(Context context)108     public AdbDebuggingManager(Context context) {
109         mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
110         mContext = context;
111         mTestUserKeyFile = null;
112         mConnectedKeys = new ArrayList<>(1);
113     }
114 
115     /**
116      * Constructor that accepts the component to be invoked to confirm if the user wants to allow
117      * an adb connection from the key.
118      */
119     @TestApi
AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile)120     protected AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile) {
121         mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
122         mContext = context;
123         mConfirmComponent = confirmComponent;
124         mTestUserKeyFile = testUserKeyFile;
125         mConnectedKeys = new ArrayList<>();
126     }
127 
128     class AdbDebuggingThread extends Thread {
129         private boolean mStopped;
130         private LocalSocket mSocket;
131         private OutputStream mOutputStream;
132         private InputStream mInputStream;
133 
AdbDebuggingThread()134         AdbDebuggingThread() {
135             super(TAG);
136         }
137 
138         @Override
run()139         public void run() {
140             if (DEBUG) Slog.d(TAG, "Entering thread");
141             while (true) {
142                 synchronized (this) {
143                     if (mStopped) {
144                         if (DEBUG) Slog.d(TAG, "Exiting thread");
145                         return;
146                     }
147                     try {
148                         openSocketLocked();
149                     } catch (Exception e) {
150                         /* Don't loop too fast if adbd dies, before init restarts it */
151                         SystemClock.sleep(1000);
152                     }
153                 }
154                 try {
155                     listenToSocket();
156                 } catch (Exception e) {
157                     /* Don't loop too fast if adbd dies, before init restarts it */
158                     SystemClock.sleep(1000);
159                 }
160             }
161         }
162 
openSocketLocked()163         private void openSocketLocked() throws IOException {
164             try {
165                 LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET,
166                         LocalSocketAddress.Namespace.RESERVED);
167                 mInputStream = null;
168 
169                 if (DEBUG) Slog.d(TAG, "Creating socket");
170                 mSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
171                 mSocket.connect(address);
172 
173                 mOutputStream = mSocket.getOutputStream();
174                 mInputStream = mSocket.getInputStream();
175             } catch (IOException ioe) {
176                 Slog.e(TAG, "Caught an exception opening the socket: " + ioe);
177                 closeSocketLocked();
178                 throw ioe;
179             }
180         }
181 
listenToSocket()182         private void listenToSocket() throws IOException {
183             try {
184                 byte[] buffer = new byte[BUFFER_SIZE];
185                 while (true) {
186                     int count = mInputStream.read(buffer);
187                     // if less than 2 bytes are read the if statements below will throw an
188                     // IndexOutOfBoundsException.
189                     if (count < 2) {
190                         Slog.w(TAG, "Read failed with count " + count);
191                         break;
192                     }
193 
194                     if (buffer[0] == 'P' && buffer[1] == 'K') {
195                         String key = new String(Arrays.copyOfRange(buffer, 2, count));
196                         Slog.d(TAG, "Received public key: " + key);
197                         Message msg = mHandler.obtainMessage(
198                                 AdbDebuggingHandler.MESSAGE_ADB_CONFIRM);
199                         msg.obj = key;
200                         mHandler.sendMessage(msg);
201                     } else if (buffer[0] == 'D' && buffer[1] == 'C') {
202                         String key = new String(Arrays.copyOfRange(buffer, 2, count));
203                         Slog.d(TAG, "Received disconnected message: " + key);
204                         Message msg = mHandler.obtainMessage(
205                                 AdbDebuggingHandler.MESSAGE_ADB_DISCONNECT);
206                         msg.obj = key;
207                         mHandler.sendMessage(msg);
208                     } else if (buffer[0] == 'C' && buffer[1] == 'K') {
209                         String key = new String(Arrays.copyOfRange(buffer, 2, count));
210                         Slog.d(TAG, "Received connected key message: " + key);
211                         Message msg = mHandler.obtainMessage(
212                                 AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY);
213                         msg.obj = key;
214                         mHandler.sendMessage(msg);
215                     } else {
216                         Slog.e(TAG, "Wrong message: "
217                                 + (new String(Arrays.copyOfRange(buffer, 0, 2))));
218                         break;
219                     }
220                 }
221             } finally {
222                 synchronized (this) {
223                     closeSocketLocked();
224                 }
225             }
226         }
227 
closeSocketLocked()228         private void closeSocketLocked() {
229             if (DEBUG) Slog.d(TAG, "Closing socket");
230             try {
231                 if (mOutputStream != null) {
232                     mOutputStream.close();
233                     mOutputStream = null;
234                 }
235             } catch (IOException e) {
236                 Slog.e(TAG, "Failed closing output stream: " + e);
237             }
238 
239             try {
240                 if (mSocket != null) {
241                     mSocket.close();
242                     mSocket = null;
243                 }
244             } catch (IOException ex) {
245                 Slog.e(TAG, "Failed closing socket: " + ex);
246             }
247         }
248 
249         /** Call to stop listening on the socket and exit the thread. */
stopListening()250         void stopListening() {
251             synchronized (this) {
252                 mStopped = true;
253                 closeSocketLocked();
254             }
255         }
256 
sendResponse(String msg)257         void sendResponse(String msg) {
258             synchronized (this) {
259                 if (!mStopped && mOutputStream != null) {
260                     try {
261                         mOutputStream.write(msg.getBytes());
262                     } catch (IOException ex) {
263                         Slog.e(TAG, "Failed to write response:", ex);
264                     }
265                 }
266             }
267         }
268     }
269 
270     class AdbDebuggingHandler extends Handler {
271         // The default time to schedule the job to keep the keystore updated with a currently
272         // connected key as well as to removed expired keys.
273         static final long UPDATE_KEYSTORE_JOB_INTERVAL = 86400000;
274         // The minimum interval at which the job should run to update the keystore. This is intended
275         // to prevent the job from running too often if the allowed connection time for adb grants
276         // is set to an extremely small value.
277         static final long UPDATE_KEYSTORE_MIN_JOB_INTERVAL = 60000;
278 
279         static final int MESSAGE_ADB_ENABLED = 1;
280         static final int MESSAGE_ADB_DISABLED = 2;
281         static final int MESSAGE_ADB_ALLOW = 3;
282         static final int MESSAGE_ADB_DENY = 4;
283         static final int MESSAGE_ADB_CONFIRM = 5;
284         static final int MESSAGE_ADB_CLEAR = 6;
285         static final int MESSAGE_ADB_DISCONNECT = 7;
286         static final int MESSAGE_ADB_PERSIST_KEYSTORE = 8;
287         static final int MESSAGE_ADB_UPDATE_KEYSTORE = 9;
288         static final int MESSAGE_ADB_CONNECTED_KEY = 10;
289 
290         private AdbKeyStore mAdbKeyStore;
291 
292         private ContentObserver mAuthTimeObserver = new ContentObserver(this) {
293             @Override
294             public void onChange(boolean selfChange, Uri uri) {
295                 Slog.d(TAG, "Received notification that uri " + uri
296                         + " was modified; rescheduling keystore job");
297                 scheduleJobToUpdateAdbKeyStore();
298             }
299         };
300 
AdbDebuggingHandler(Looper looper)301         AdbDebuggingHandler(Looper looper) {
302             super(looper);
303         }
304 
305         /**
306          * Constructor that accepts the AdbDebuggingThread to which responses should be sent
307          * and the AdbKeyStore to be used to store the temporary grants.
308          */
309         @TestApi
AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore)310         AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
311             super(looper);
312             mThread = thread;
313             mAdbKeyStore = adbKeyStore;
314         }
315 
handleMessage(Message msg)316         public void handleMessage(Message msg) {
317             switch (msg.what) {
318                 case MESSAGE_ADB_ENABLED:
319                     if (mAdbEnabled) {
320                         break;
321                     }
322                     registerForAuthTimeChanges();
323                     mAdbEnabled = true;
324 
325                     mThread = new AdbDebuggingThread();
326                     mThread.start();
327 
328                     mAdbKeyStore = new AdbKeyStore();
329                     mAdbKeyStore.updateKeyStore();
330                     scheduleJobToUpdateAdbKeyStore();
331                     break;
332 
333                 case MESSAGE_ADB_DISABLED:
334                     if (!mAdbEnabled) {
335                         break;
336                     }
337 
338                     mAdbEnabled = false;
339 
340                     if (mThread != null) {
341                         mThread.stopListening();
342                         mThread = null;
343                     }
344 
345                     if (!mConnectedKeys.isEmpty()) {
346                         for (String connectedKey : mConnectedKeys) {
347                             mAdbKeyStore.setLastConnectionTime(connectedKey,
348                                     System.currentTimeMillis());
349                         }
350                         sendPersistKeyStoreMessage();
351                         mConnectedKeys.clear();
352                     }
353                     scheduleJobToUpdateAdbKeyStore();
354                     break;
355 
356                 case MESSAGE_ADB_ALLOW: {
357                     String key = (String) msg.obj;
358                     String fingerprints = getFingerprints(key);
359                     if (!fingerprints.equals(mFingerprints)) {
360                         Slog.e(TAG, "Fingerprints do not match. Got "
361                                 + fingerprints + ", expected " + mFingerprints);
362                         break;
363                     }
364 
365                     boolean alwaysAllow = msg.arg1 == 1;
366                     if (mThread != null) {
367                         mThread.sendResponse("OK");
368                         if (alwaysAllow) {
369                             if (!mConnectedKeys.contains(key)) {
370                                 mConnectedKeys.add(key);
371                             }
372                             mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
373                             sendPersistKeyStoreMessage();
374                             scheduleJobToUpdateAdbKeyStore();
375                         }
376                         logAdbConnectionChanged(key, AdbProtoEnums.USER_ALLOWED, alwaysAllow);
377                     }
378                     break;
379                 }
380 
381                 case MESSAGE_ADB_DENY:
382                     if (mThread != null) {
383                         mThread.sendResponse("NO");
384                         logAdbConnectionChanged(null, AdbProtoEnums.USER_DENIED, false);
385                     }
386                     break;
387 
388                 case MESSAGE_ADB_CONFIRM: {
389                     String key = (String) msg.obj;
390                     if ("trigger_restart_min_framework".equals(
391                             SystemProperties.get("vold.decrypt"))) {
392                         Slog.d(TAG, "Deferring adb confirmation until after vold decrypt");
393                         if (mThread != null) {
394                             mThread.sendResponse("NO");
395                             logAdbConnectionChanged(key, AdbProtoEnums.DENIED_VOLD_DECRYPT, false);
396                         }
397                         break;
398                     }
399                     String fingerprints = getFingerprints(key);
400                     if ("".equals(fingerprints)) {
401                         if (mThread != null) {
402                             mThread.sendResponse("NO");
403                             logAdbConnectionChanged(key, AdbProtoEnums.DENIED_INVALID_KEY, false);
404                         }
405                         break;
406                     }
407                     logAdbConnectionChanged(key, AdbProtoEnums.AWAITING_USER_APPROVAL, false);
408                     mFingerprints = fingerprints;
409                     startConfirmation(key, mFingerprints);
410                     break;
411                 }
412 
413                 case MESSAGE_ADB_CLEAR: {
414                     Slog.d(TAG, "Received a request to clear the adb authorizations");
415                     mConnectedKeys.clear();
416                     mAdbKeyStore.deleteKeyStore();
417                     cancelJobToUpdateAdbKeyStore();
418                     break;
419                 }
420 
421                 case MESSAGE_ADB_DISCONNECT: {
422                     String key = (String) msg.obj;
423                     boolean alwaysAllow = false;
424                     if (key != null && key.length() > 0) {
425                         if (mConnectedKeys.contains(key)) {
426                             alwaysAllow = true;
427                             mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
428                             sendPersistKeyStoreMessage();
429                             scheduleJobToUpdateAdbKeyStore();
430                             mConnectedKeys.remove(key);
431                         }
432                     } else {
433                         Slog.w(TAG, "Received a disconnected key message with an empty key");
434                     }
435                     logAdbConnectionChanged(key, AdbProtoEnums.DISCONNECTED, alwaysAllow);
436                     break;
437                 }
438 
439                 case MESSAGE_ADB_PERSIST_KEYSTORE: {
440                     if (mAdbKeyStore != null) {
441                         mAdbKeyStore.persistKeyStore();
442                     }
443                     break;
444                 }
445 
446                 case MESSAGE_ADB_UPDATE_KEYSTORE: {
447                     if (!mConnectedKeys.isEmpty()) {
448                         for (String connectedKey : mConnectedKeys) {
449                             mAdbKeyStore.setLastConnectionTime(connectedKey,
450                                     System.currentTimeMillis());
451                         }
452                         sendPersistKeyStoreMessage();
453                         scheduleJobToUpdateAdbKeyStore();
454                     } else if (!mAdbKeyStore.isEmpty()) {
455                         mAdbKeyStore.updateKeyStore();
456                         scheduleJobToUpdateAdbKeyStore();
457                     }
458                     break;
459                 }
460 
461                 case MESSAGE_ADB_CONNECTED_KEY: {
462                     String key = (String) msg.obj;
463                     if (key == null || key.length() == 0) {
464                         Slog.w(TAG, "Received a connected key message with an empty key");
465                     } else {
466                         if (!mConnectedKeys.contains(key)) {
467                             mConnectedKeys.add(key);
468                         }
469                         mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
470                         sendPersistKeyStoreMessage();
471                         scheduleJobToUpdateAdbKeyStore();
472                         logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
473                     }
474                     break;
475                 }
476             }
477         }
478 
registerForAuthTimeChanges()479         void registerForAuthTimeChanges() {
480             Uri uri = Settings.Global.getUriFor(Settings.Global.ADB_ALLOWED_CONNECTION_TIME);
481             mContext.getContentResolver().registerContentObserver(uri, false, mAuthTimeObserver);
482         }
483 
logAdbConnectionChanged(String key, int state, boolean alwaysAllow)484         private void logAdbConnectionChanged(String key, int state, boolean alwaysAllow) {
485             long lastConnectionTime = mAdbKeyStore.getLastConnectionTime(key);
486             long authWindow = mAdbKeyStore.getAllowedConnectionTime();
487             Slog.d(TAG,
488                     "Logging key " + key + ", state = " + state + ", alwaysAllow = " + alwaysAllow
489                             + ", lastConnectionTime = " + lastConnectionTime + ", authWindow = "
490                             + authWindow);
491             StatsLog.write(StatsLog.ADB_CONNECTION_CHANGED, lastConnectionTime, authWindow, state,
492                     alwaysAllow);
493         }
494 
495 
496         /**
497          * Schedules a job to update the connection time of the currently connected key and filter
498          * out any keys that are beyond their expiration time.
499          *
500          * @return the time in ms when the next job will run or -1 if the job should not be
501          * scheduled to run.
502          */
503         @VisibleForTesting
scheduleJobToUpdateAdbKeyStore()504         long scheduleJobToUpdateAdbKeyStore() {
505             cancelJobToUpdateAdbKeyStore();
506             long keyExpiration = mAdbKeyStore.getNextExpirationTime();
507             // if the keyExpiration time is -1 then either the keys are set to never expire or
508             // there are no keys in the keystore, just return for now as a new job will be
509             // scheduled on the next connection or when the auth time changes.
510             if (keyExpiration == -1) {
511                 return -1;
512             }
513             long delay;
514             // if the keyExpiration is 0 this indicates a key has already expired; schedule the job
515             // to run now to ensure the key is removed immediately from adb_keys.
516             if (keyExpiration == 0) {
517                 delay = 0;
518             } else {
519                 // else the next job should be run either daily or when the next key is set to
520                 // expire with a min job interval to ensure this job does not run too often if a
521                 // small value is set for the key expiration.
522                 delay = Math.max(Math.min(UPDATE_KEYSTORE_JOB_INTERVAL, keyExpiration),
523                         UPDATE_KEYSTORE_MIN_JOB_INTERVAL);
524             }
525             Message message = obtainMessage(MESSAGE_ADB_UPDATE_KEYSTORE);
526             sendMessageDelayed(message, delay);
527             return delay;
528         }
529 
530         /**
531          * Cancels the scheduled job to update the connection time of the currently connected key
532          * and to remove any expired keys.
533          */
cancelJobToUpdateAdbKeyStore()534         private void cancelJobToUpdateAdbKeyStore() {
535             removeMessages(AdbDebuggingHandler.MESSAGE_ADB_UPDATE_KEYSTORE);
536         }
537     }
538 
getFingerprints(String key)539     private String getFingerprints(String key) {
540         String hex = "0123456789ABCDEF";
541         StringBuilder sb = new StringBuilder();
542         MessageDigest digester;
543 
544         if (key == null) {
545             return "";
546         }
547 
548         try {
549             digester = MessageDigest.getInstance("MD5");
550         } catch (Exception ex) {
551             Slog.e(TAG, "Error getting digester", ex);
552             return "";
553         }
554 
555         byte[] base64_data = key.split("\\s+")[0].getBytes();
556         byte[] digest;
557         try {
558             digest = digester.digest(Base64.decode(base64_data, Base64.DEFAULT));
559         } catch (IllegalArgumentException e) {
560             Slog.e(TAG, "error doing base64 decoding", e);
561             return "";
562         }
563         for (int i = 0; i < digest.length; i++) {
564             sb.append(hex.charAt((digest[i] >> 4) & 0xf));
565             sb.append(hex.charAt(digest[i] & 0xf));
566             if (i < digest.length - 1) {
567                 sb.append(":");
568             }
569         }
570         return sb.toString();
571     }
572 
startConfirmation(String key, String fingerprints)573     private void startConfirmation(String key, String fingerprints) {
574         int currentUserId = ActivityManager.getCurrentUser();
575         UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
576         String componentString;
577         if (userInfo.isAdmin()) {
578             componentString = mConfirmComponent != null
579                     ? mConfirmComponent : Resources.getSystem().getString(
580                     com.android.internal.R.string.config_customAdbPublicKeyConfirmationComponent);
581         } else {
582             // If the current foreground user is not the admin user we send a different
583             // notification specific to secondary users.
584             componentString = Resources.getSystem().getString(
585                     R.string.config_customAdbPublicKeyConfirmationSecondaryUserComponent);
586         }
587         ComponentName componentName = ComponentName.unflattenFromString(componentString);
588         if (startConfirmationActivity(componentName, userInfo.getUserHandle(), key, fingerprints)
589                 || startConfirmationService(componentName, userInfo.getUserHandle(),
590                         key, fingerprints)) {
591             return;
592         }
593         Slog.e(TAG, "unable to start customAdbPublicKeyConfirmation[SecondaryUser]Component "
594                 + componentString + " as an Activity or a Service");
595     }
596 
597     /**
598      * @return true if the componentName led to an Activity that was started.
599      */
startConfirmationActivity(ComponentName componentName, UserHandle userHandle, String key, String fingerprints)600     private boolean startConfirmationActivity(ComponentName componentName, UserHandle userHandle,
601             String key, String fingerprints) {
602         PackageManager packageManager = mContext.getPackageManager();
603         Intent intent = createConfirmationIntent(componentName, key, fingerprints);
604         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
605         if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
606             try {
607                 mContext.startActivityAsUser(intent, userHandle);
608                 return true;
609             } catch (ActivityNotFoundException e) {
610                 Slog.e(TAG, "unable to start adb whitelist activity: " + componentName, e);
611             }
612         }
613         return false;
614     }
615 
616     /**
617      * @return true if the componentName led to a Service that was started.
618      */
startConfirmationService(ComponentName componentName, UserHandle userHandle, String key, String fingerprints)619     private boolean startConfirmationService(ComponentName componentName, UserHandle userHandle,
620             String key, String fingerprints) {
621         Intent intent = createConfirmationIntent(componentName, key, fingerprints);
622         try {
623             if (mContext.startServiceAsUser(intent, userHandle) != null) {
624                 return true;
625             }
626         } catch (SecurityException e) {
627             Slog.e(TAG, "unable to start adb whitelist service: " + componentName, e);
628         }
629         return false;
630     }
631 
createConfirmationIntent(ComponentName componentName, String key, String fingerprints)632     private Intent createConfirmationIntent(ComponentName componentName, String key,
633             String fingerprints) {
634         Intent intent = new Intent();
635         intent.setClassName(componentName.getPackageName(), componentName.getClassName());
636         intent.putExtra("key", key);
637         intent.putExtra("fingerprints", fingerprints);
638         return intent;
639     }
640 
641     /**
642      * Returns a new File with the specified name in the adb directory.
643      */
getAdbFile(String fileName)644     private File getAdbFile(String fileName) {
645         File dataDir = Environment.getDataDirectory();
646         File adbDir = new File(dataDir, ADB_DIRECTORY);
647 
648         if (!adbDir.exists()) {
649             Slog.e(TAG, "ADB data directory does not exist");
650             return null;
651         }
652 
653         return new File(adbDir, fileName);
654     }
655 
getAdbTempKeysFile()656     File getAdbTempKeysFile() {
657         return getAdbFile(ADB_TEMP_KEYS_FILE);
658     }
659 
getUserKeyFile()660     File getUserKeyFile() {
661         return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile;
662     }
663 
writeKey(String key)664     private void writeKey(String key) {
665         try {
666             File keyFile = getUserKeyFile();
667 
668             if (keyFile == null) {
669                 return;
670             }
671 
672             FileOutputStream fo = new FileOutputStream(keyFile, true);
673             fo.write(key.getBytes());
674             fo.write('\n');
675             fo.close();
676 
677             FileUtils.setPermissions(keyFile.toString(),
678                     FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
679         } catch (IOException ex) {
680             Slog.e(TAG, "Error writing key:" + ex);
681         }
682     }
683 
writeKeys(Iterable<String> keys)684     private void writeKeys(Iterable<String> keys) {
685         AtomicFile atomicKeyFile = null;
686         FileOutputStream fo = null;
687         try {
688             File keyFile = getUserKeyFile();
689 
690             if (keyFile == null) {
691                 return;
692             }
693 
694             atomicKeyFile = new AtomicFile(keyFile);
695             fo = atomicKeyFile.startWrite();
696             for (String key : keys) {
697                 fo.write(key.getBytes());
698                 fo.write('\n');
699             }
700             atomicKeyFile.finishWrite(fo);
701 
702             FileUtils.setPermissions(keyFile.toString(),
703                     FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
704         } catch (IOException ex) {
705             Slog.e(TAG, "Error writing keys: " + ex);
706             if (atomicKeyFile != null) {
707                 atomicKeyFile.failWrite(fo);
708             }
709         }
710     }
711 
deleteKeyFile()712     private void deleteKeyFile() {
713         File keyFile = getUserKeyFile();
714         if (keyFile != null) {
715             keyFile.delete();
716         }
717     }
718 
719     /**
720      * When {@code enabled} is {@code true}, this allows ADB debugging and starts the ADB hanler
721      * thread. When {@code enabled} is {@code false}, this disallows ADB debugging and shuts
722      * down the handler thread.
723      */
setAdbEnabled(boolean enabled)724     public void setAdbEnabled(boolean enabled) {
725         mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MESSAGE_ADB_ENABLED
726                                           : AdbDebuggingHandler.MESSAGE_ADB_DISABLED);
727     }
728 
729     /**
730      * Allows the debugging from the endpoint identified by {@code publicKey} either once or
731      * always if {@code alwaysAllow} is {@code true}.
732      */
allowDebugging(boolean alwaysAllow, String publicKey)733     public void allowDebugging(boolean alwaysAllow, String publicKey) {
734         Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MESSAGE_ADB_ALLOW);
735         msg.arg1 = alwaysAllow ? 1 : 0;
736         msg.obj = publicKey;
737         mHandler.sendMessage(msg);
738     }
739 
740     /**
741      * Denies debugging connection from the device that last requested to connect.
742      */
denyDebugging()743     public void denyDebugging() {
744         mHandler.sendEmptyMessage(AdbDebuggingHandler.MESSAGE_ADB_DENY);
745     }
746 
747     /**
748      * Clears all previously accepted ADB debugging public keys. Any subsequent request will need
749      * to pass through {@link #allowUsbDebugging(boolean, String)} again.
750      */
clearDebuggingKeys()751     public void clearDebuggingKeys() {
752         mHandler.sendEmptyMessage(AdbDebuggingHandler.MESSAGE_ADB_CLEAR);
753     }
754 
755     /**
756      * Sends a message to the handler to persist the keystore.
757      */
sendPersistKeyStoreMessage()758     private void sendPersistKeyStoreMessage() {
759         Message msg = mHandler.obtainMessage(AdbDebuggingHandler.MESSAGE_ADB_PERSIST_KEYSTORE);
760         mHandler.sendMessage(msg);
761     }
762 
763     /**
764      * Dump the USB debugging state.
765      */
dump(DualDumpOutputStream dump, String idName, long id)766     public void dump(DualDumpOutputStream dump, String idName, long id) {
767         long token = dump.start(idName, id);
768 
769         dump.write("connected_to_adb", AdbDebuggingManagerProto.CONNECTED_TO_ADB, mThread != null);
770         writeStringIfNotNull(dump, "last_key_received", AdbDebuggingManagerProto.LAST_KEY_RECEVIED,
771                 mFingerprints);
772 
773         try {
774             dump.write("user_keys", AdbDebuggingManagerProto.USER_KEYS,
775                     FileUtils.readTextFile(new File("/data/misc/adb/adb_keys"), 0, null));
776         } catch (IOException e) {
777             Slog.e(TAG, "Cannot read user keys", e);
778         }
779 
780         try {
781             dump.write("system_keys", AdbDebuggingManagerProto.SYSTEM_KEYS,
782                     FileUtils.readTextFile(new File("/adb_keys"), 0, null));
783         } catch (IOException e) {
784             Slog.e(TAG, "Cannot read system keys", e);
785         }
786 
787         try {
788             dump.write("keystore", AdbDebuggingManagerProto.KEYSTORE,
789                     FileUtils.readTextFile(getAdbTempKeysFile(), 0, null));
790         } catch (IOException e) {
791             Slog.e(TAG, "Cannot read keystore: ", e);
792         }
793 
794         dump.end(token);
795     }
796 
797     /**
798      * Handles adb keys for which the user has granted the 'always allow' option. This class ensures
799      * these grants are revoked after a period of inactivity as specified in the
800      * ADB_ALLOWED_CONNECTION_TIME setting.
801      */
802     class AdbKeyStore {
803         private Map<String, Long> mKeyMap;
804         private Set<String> mSystemKeys;
805         private File mKeyFile;
806         private AtomicFile mAtomicKeyFile;
807 
808         private static final String XML_TAG_ADB_KEY = "adbKey";
809         private static final String XML_ATTRIBUTE_KEY = "key";
810         private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection";
811         private static final String SYSTEM_KEY_FILE = "/adb_keys";
812 
813         /**
814          * Value returned by {@code getLastConnectionTime} when there is no previously saved
815          * connection time for the specified key.
816          */
817         public static final long NO_PREVIOUS_CONNECTION = 0;
818 
819         /**
820          * Constructor that uses the default location for the persistent adb keystore.
821          */
AdbKeyStore()822         AdbKeyStore() {
823             init();
824         }
825 
826         /**
827          * Constructor that uses the specified file as the location for the persistent adb keystore.
828          */
AdbKeyStore(File keyFile)829         AdbKeyStore(File keyFile) {
830             mKeyFile = keyFile;
831             init();
832         }
833 
init()834         private void init() {
835             initKeyFile();
836             mKeyMap = getKeyMap();
837             mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE);
838             addUserKeysToKeyStore();
839         }
840 
841         /**
842          * Initializes the key file that will be used to persist the adb grants.
843          */
initKeyFile()844         private void initKeyFile() {
845             if (mKeyFile == null) {
846                 mKeyFile = getAdbTempKeysFile();
847             }
848             // getAdbTempKeysFile can return null if the adb file cannot be obtained
849             if (mKeyFile != null) {
850                 mAtomicKeyFile = new AtomicFile(mKeyFile);
851             }
852         }
853 
getSystemKeysFromFile(String fileName)854         private Set<String> getSystemKeysFromFile(String fileName) {
855             Set<String> systemKeys = new HashSet<>();
856             File systemKeyFile = new File(fileName);
857             if (systemKeyFile.exists()) {
858                 try (BufferedReader in = new BufferedReader(new FileReader(systemKeyFile))) {
859                     String key;
860                     while ((key = in.readLine()) != null) {
861                         key = key.trim();
862                         if (key.length() > 0) {
863                             systemKeys.add(key);
864                         }
865                     }
866                 } catch (IOException e) {
867                     Slog.e(TAG, "Caught an exception reading " + fileName + ": " + e);
868                 }
869             }
870             return systemKeys;
871         }
872 
873         /**
874          * Returns whether there are any 'always allowed' keys in the keystore.
875          */
isEmpty()876         public boolean isEmpty() {
877             return mKeyMap.isEmpty();
878         }
879 
880         /**
881          * Iterates through the keys in the keystore and removes any that are beyond the window
882          * within which connections are automatically allowed without user interaction.
883          */
updateKeyStore()884         public void updateKeyStore() {
885             if (filterOutOldKeys()) {
886                 sendPersistKeyStoreMessage();
887             }
888         }
889 
890         /**
891          * Returns the key map with the keys and last connection times from the key file.
892          */
getKeyMap()893         private Map<String, Long> getKeyMap() {
894             Map<String, Long> keyMap = new HashMap<String, Long>();
895             // if the AtomicFile could not be instantiated before attempt again; if it still fails
896             // return an empty key map.
897             if (mAtomicKeyFile == null) {
898                 initKeyFile();
899                 if (mAtomicKeyFile == null) {
900                     Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
901                     return keyMap;
902                 }
903             }
904             if (!mAtomicKeyFile.exists()) {
905                 return keyMap;
906             }
907             try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
908                 XmlPullParser parser = Xml.newPullParser();
909                 parser.setInput(keyStream, StandardCharsets.UTF_8.name());
910                 XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
911                 while (parser.next() != XmlPullParser.END_DOCUMENT) {
912                     String tagName = parser.getName();
913                     if (tagName == null) {
914                         break;
915                     } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
916                         XmlUtils.skipCurrentTag(parser);
917                         continue;
918                     }
919                     String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
920                     long connectionTime;
921                     try {
922                         connectionTime = Long.valueOf(
923                                 parser.getAttributeValue(null, XML_ATTRIBUTE_LAST_CONNECTION));
924                     } catch (NumberFormatException e) {
925                         Slog.e(TAG,
926                                 "Caught a NumberFormatException parsing the last connection time: "
927                                         + e);
928                         XmlUtils.skipCurrentTag(parser);
929                         continue;
930                     }
931                     keyMap.put(key, connectionTime);
932                 }
933             } catch (IOException | XmlPullParserException e) {
934                 Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
935             }
936             return keyMap;
937         }
938 
939         /**
940          * Updates the keystore with keys that were previously set to be always allowed before the
941          * connection time of keys was tracked.
942          */
addUserKeysToKeyStore()943         private void addUserKeysToKeyStore() {
944             File userKeyFile = getUserKeyFile();
945             boolean mapUpdated = false;
946             if (userKeyFile != null && userKeyFile.exists()) {
947                 try (BufferedReader in = new BufferedReader(new FileReader(userKeyFile))) {
948                     long time = System.currentTimeMillis();
949                     String key;
950                     while ((key = in.readLine()) != null) {
951                         // if the keystore does not contain the key from the user key file then add
952                         // it to the Map with the current system time to prevent it from expiring
953                         // immediately if the user is actively using this key.
954                         if (!mKeyMap.containsKey(key)) {
955                             mKeyMap.put(key, time);
956                             mapUpdated = true;
957                         }
958                     }
959                 } catch (IOException e) {
960                     Slog.e(TAG, "Caught an exception reading " + userKeyFile + ": " + e);
961                 }
962             }
963             if (mapUpdated) {
964                 sendPersistKeyStoreMessage();
965             }
966         }
967 
968         /**
969          * Writes the key map to the key file.
970          */
persistKeyStore()971         public void persistKeyStore() {
972             // if there is nothing in the key map then ensure any keys left in the keystore files
973             // are deleted as well.
974             filterOutOldKeys();
975             if (mKeyMap.isEmpty()) {
976                 deleteKeyStore();
977                 return;
978             }
979             if (mAtomicKeyFile == null) {
980                 initKeyFile();
981                 if (mAtomicKeyFile == null) {
982                     Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing");
983                     return;
984                 }
985             }
986             FileOutputStream keyStream = null;
987             try {
988                 XmlSerializer serializer = new FastXmlSerializer();
989                 keyStream = mAtomicKeyFile.startWrite();
990                 serializer.setOutput(keyStream, StandardCharsets.UTF_8.name());
991                 serializer.startDocument(null, true);
992 
993                 for (Map.Entry<String, Long> keyEntry : mKeyMap.entrySet()) {
994                     serializer.startTag(null, XML_TAG_ADB_KEY);
995                     serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey());
996                     serializer.attribute(null, XML_ATTRIBUTE_LAST_CONNECTION,
997                             String.valueOf(keyEntry.getValue()));
998                     serializer.endTag(null, XML_TAG_ADB_KEY);
999                 }
1000 
1001                 serializer.endDocument();
1002                 mAtomicKeyFile.finishWrite(keyStream);
1003             } catch (IOException e) {
1004                 Slog.e(TAG, "Caught an exception writing the key map: ", e);
1005                 mAtomicKeyFile.failWrite(keyStream);
1006             }
1007         }
1008 
filterOutOldKeys()1009         private boolean filterOutOldKeys() {
1010             boolean keysDeleted = false;
1011             long allowedTime = getAllowedConnectionTime();
1012             long systemTime = System.currentTimeMillis();
1013             Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
1014             while (keyMapIterator.hasNext()) {
1015                 Map.Entry<String, Long> keyEntry = keyMapIterator.next();
1016                 long connectionTime = keyEntry.getValue();
1017                 if (allowedTime != 0 && systemTime > (connectionTime + allowedTime)) {
1018                     keyMapIterator.remove();
1019                     keysDeleted = true;
1020                 }
1021             }
1022             // if any keys were deleted then the key file should be rewritten with the active keys
1023             // to prevent authorizing a key that is now beyond the allowed window.
1024             if (keysDeleted) {
1025                 writeKeys(mKeyMap.keySet());
1026             }
1027             return keysDeleted;
1028         }
1029 
1030         /**
1031          * Returns the time in ms that the next key will expire or -1 if there are no keys or the
1032          * keys will not expire.
1033          */
getNextExpirationTime()1034         public long getNextExpirationTime() {
1035             long minExpiration = -1;
1036             long allowedTime = getAllowedConnectionTime();
1037             // if the allowedTime is 0 then keys never expire; return -1 to indicate this
1038             if (allowedTime == 0) {
1039                 return minExpiration;
1040             }
1041             long systemTime = System.currentTimeMillis();
1042             Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
1043             while (keyMapIterator.hasNext()) {
1044                 Map.Entry<String, Long> keyEntry = keyMapIterator.next();
1045                 long connectionTime = keyEntry.getValue();
1046                 // if the key has already expired then ensure that the result is set to 0 so that
1047                 // any scheduled jobs to clean up the keystore can run right away.
1048                 long keyExpiration = Math.max(0, (connectionTime + allowedTime) - systemTime);
1049                 if (minExpiration == -1 || keyExpiration < minExpiration) {
1050                     minExpiration = keyExpiration;
1051                 }
1052             }
1053             return minExpiration;
1054         }
1055 
1056         /**
1057          * Removes all of the entries in the key map and deletes the key file.
1058          */
deleteKeyStore()1059         public void deleteKeyStore() {
1060             mKeyMap.clear();
1061             deleteKeyFile();
1062             if (mAtomicKeyFile == null) {
1063                 return;
1064             }
1065             mAtomicKeyFile.delete();
1066         }
1067 
1068         /**
1069          * Returns the time of the last connection from the specified key, or {@code
1070          * NO_PREVIOUS_CONNECTION} if the specified key does not have an active adb grant.
1071          */
getLastConnectionTime(String key)1072         public long getLastConnectionTime(String key) {
1073             return mKeyMap.getOrDefault(key, NO_PREVIOUS_CONNECTION);
1074         }
1075 
1076         /**
1077          * Sets the time of the last connection for the specified key to the provided time.
1078          */
setLastConnectionTime(String key, long connectionTime)1079         public void setLastConnectionTime(String key, long connectionTime) {
1080             setLastConnectionTime(key, connectionTime, false);
1081         }
1082 
1083         /**
1084          * Sets the time of the last connection for the specified key to the provided time. If force
1085          * is set to true the time will be set even if it is older than the previously written
1086          * connection time.
1087          */
setLastConnectionTime(String key, long connectionTime, boolean force)1088         public void setLastConnectionTime(String key, long connectionTime, boolean force) {
1089             // Do not set the connection time to a value that is earlier than what was previously
1090             // stored as the last connection time unless force is set.
1091             if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime && !force) {
1092                 return;
1093             }
1094             // System keys are always allowed so there's no need to keep track of their connection
1095             // time.
1096             if (mSystemKeys.contains(key)) {
1097                 return;
1098             }
1099             // if this is the first time the key is being added then write it to the key file as
1100             // well.
1101             if (!mKeyMap.containsKey(key)) {
1102                 writeKey(key);
1103             }
1104             mKeyMap.put(key, connectionTime);
1105         }
1106 
1107         /**
1108          * Returns the connection time within which a connection from an allowed key is
1109          * automatically allowed without user interaction.
1110          */
getAllowedConnectionTime()1111         public long getAllowedConnectionTime() {
1112             return Settings.Global.getLong(mContext.getContentResolver(),
1113                     Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
1114                     Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
1115         }
1116 
1117         /**
1118          * Returns whether the specified key should be authroized to connect without user
1119          * interaction. This requires that the user previously connected this device and selected
1120          * the option to 'Always allow', and the time since the last connection is within the
1121          * allowed window.
1122          */
isKeyAuthorized(String key)1123         public boolean isKeyAuthorized(String key) {
1124             // A system key is always authorized to connect.
1125             if (mSystemKeys.contains(key)) {
1126                 return true;
1127             }
1128             long lastConnectionTime = getLastConnectionTime(key);
1129             if (lastConnectionTime == NO_PREVIOUS_CONNECTION) {
1130                 return false;
1131             }
1132             long allowedConnectionTime = getAllowedConnectionTime();
1133             // if the allowed connection time is 0 then revert to the previous behavior of always
1134             // allowing previously granted adb grants.
1135             if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime
1136                     + allowedConnectionTime))) {
1137                 return true;
1138             } else {
1139                 return false;
1140             }
1141         }
1142     }
1143 }
1144