Fix ADB key file reading

The "adb_temp_keys.xml" file is used to keep track of each ADB key and
when it was previously connected. It also keeps track of authorized WiFi
networks. It is written correctly, however, the parser currently is
unable to read the file that it wrote, meaning that:
1. ADB keys never properly expire
2. WiFi APs are never properly restored

Furthermore, because of complex interactions with restoring the adb_keys
file in Test Harness Mode, duplicate keys get appended repteatedly to
the adb_keys file if they're not in the key map.

This cleans up the file, making it more testable, adds tests for the
previously broken functionality, and just performs some simple
restructuring of bits of the code that were error-prone.

Now, the adb_keys file is nothing but a view of the temp keys file. When
keys are added to the temp keys file, we completely rewrite the adb_keys
file with the set of keys present in the temp keys file, meaning that
duplicates cannot be added.

This also introduces a method for other system services (primarily for
TestHarnessModeService) to notify the AdbDebuggingManager that the key
files were modified, and it should reload the state.

Bug: 236299256
Test: atest AdbDebuggingManagerTest

Change-Id: Iae955aca17e873288d58b16f184bd4d325d50d86
This commit is contained in:
William Hester
2022-06-15 17:24:50 -07:00
parent dfca3dbdb6
commit efb8194ec4
5 changed files with 405 additions and 358 deletions

View File

@@ -54,6 +54,12 @@ public abstract class AdbManagerInternal {
*/ */
public abstract File getAdbTempKeysFile(); public abstract File getAdbTempKeysFile();
/**
* Notify the AdbManager that the key files have changed and any in-memory state should be
* reloaded.
*/
public abstract void notifyKeyFilesUpdated();
/** /**
* Starts adbd for a transport. * Starts adbd for a transport.
*/ */

View File

@@ -19,7 +19,7 @@ package com.android.server.adb;
import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.TestApi; import android.annotation.Nullable;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
@@ -102,11 +102,26 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keys
* that are authorized to connect to the ADB service itself. * that are authorized to connect to the ADB service itself.
*
* <p>The AdbDebuggingManager controls two files:
* <ol>
* <li>adb_keys
* <li>adb_temp_keys.xml
* </ol>
*
* <p>The ADB Daemon (adbd) reads <em>only</em> the adb_keys file for authorization. Public keys
* from registered hosts are stored in adb_keys, one entry per line.
*
* <p>AdbDebuggingManager also keeps adb_temp_keys.xml, which is used for two things
* <ol>
* <li>Removing unused keys from the adb_keys file
* <li>Managing authorized WiFi access points for ADB over WiFi
* </ol>
*/ */
public class AdbDebuggingManager { public class AdbDebuggingManager {
private static final String TAG = "AdbDebuggingManager"; private static final String TAG = AdbDebuggingManager.class.getSimpleName();
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private static final boolean MDNS_DEBUG = false; private static final boolean MDNS_DEBUG = false;
@@ -118,18 +133,20 @@ public class AdbDebuggingManager {
// as a subsequent connection occurs within the allowed duration. // as a subsequent connection occurs within the allowed duration.
private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml"; private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
private static final int BUFFER_SIZE = 65536; private static final int BUFFER_SIZE = 65536;
private static final Ticker SYSTEM_TICKER = () -> System.currentTimeMillis();
private final Context mContext; private final Context mContext;
private final ContentResolver mContentResolver; private final ContentResolver mContentResolver;
private final Handler mHandler; @VisibleForTesting final AdbDebuggingHandler mHandler;
private AdbDebuggingThread mThread; @Nullable private AdbDebuggingThread mThread;
private boolean mAdbUsbEnabled = false; private boolean mAdbUsbEnabled = false;
private boolean mAdbWifiEnabled = false; private boolean mAdbWifiEnabled = false;
private String mFingerprints; private String mFingerprints;
// A key can be used more than once (e.g. USB, wifi), so need to keep a refcount // A key can be used more than once (e.g. USB, wifi), so need to keep a refcount
private final Map<String, Integer> mConnectedKeys; private final Map<String, Integer> mConnectedKeys = new HashMap<>();
private String mConfirmComponent; private final String mConfirmComponent;
private final File mTestUserKeyFile; @Nullable private final File mUserKeyFile;
@Nullable private final File mTempKeysFile;
private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = private static final String WIFI_PERSISTENT_CONFIG_PROPERTY =
"persist.adb.tls_server.enable"; "persist.adb.tls_server.enable";
@@ -138,37 +155,44 @@ public class AdbDebuggingManager {
private static final int PAIRING_CODE_LENGTH = 6; private static final int PAIRING_CODE_LENGTH = 6;
private PairingThread mPairingThread = null; private PairingThread mPairingThread = null;
// A list of keys connected via wifi // A list of keys connected via wifi
private final Set<String> mWifiConnectedKeys; private final Set<String> mWifiConnectedKeys = new HashSet<>();
// The current info of the adbwifi connection. // The current info of the adbwifi connection.
private AdbConnectionInfo mAdbConnectionInfo; private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo();
// Polls for a tls port property when adb wifi is enabled // Polls for a tls port property when adb wifi is enabled
private AdbConnectionPortPoller mConnectionPortPoller; private AdbConnectionPortPoller mConnectionPortPoller;
private final PortListenerImpl mPortListener = new PortListenerImpl(); private final PortListenerImpl mPortListener = new PortListenerImpl();
private final Ticker mTicker;
public AdbDebuggingManager(Context context) { public AdbDebuggingManager(Context context) {
mHandler = new AdbDebuggingHandler(FgThread.get().getLooper()); this(
mContext = context; context,
mContentResolver = mContext.getContentResolver(); /* confirmComponent= */ null,
mTestUserKeyFile = null; getAdbFile(ADB_KEYS_FILE),
mConnectedKeys = new HashMap<String, Integer>(); getAdbFile(ADB_TEMP_KEYS_FILE),
mWifiConnectedKeys = new HashSet<String>(); /* adbDebuggingThread= */ null,
mAdbConnectionInfo = new AdbConnectionInfo(); SYSTEM_TICKER);
} }
/** /**
* Constructor that accepts the component to be invoked to confirm if the user wants to allow * Constructor that accepts the component to be invoked to confirm if the user wants to allow
* an adb connection from the key. * an adb connection from the key.
*/ */
@TestApi @VisibleForTesting
protected AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile) { AdbDebuggingManager(
mHandler = new AdbDebuggingHandler(FgThread.get().getLooper()); Context context,
String confirmComponent,
File testUserKeyFile,
File tempKeysFile,
AdbDebuggingThread adbDebuggingThread,
Ticker ticker) {
mContext = context; mContext = context;
mContentResolver = mContext.getContentResolver(); mContentResolver = mContext.getContentResolver();
mConfirmComponent = confirmComponent; mConfirmComponent = confirmComponent;
mTestUserKeyFile = testUserKeyFile; mUserKeyFile = testUserKeyFile;
mConnectedKeys = new HashMap<String, Integer>(); mTempKeysFile = tempKeysFile;
mWifiConnectedKeys = new HashSet<String>(); mThread = adbDebuggingThread;
mAdbConnectionInfo = new AdbConnectionInfo(); mTicker = ticker;
mHandler = new AdbDebuggingHandler(FgThread.get().getLooper(), mThread);
} }
static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent, static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent,
@@ -189,8 +213,7 @@ public class AdbDebuggingManager {
// consisting of only letters, digits, and hyphens, must begin and end // consisting of only letters, digits, and hyphens, must begin and end
// with a letter or digit, must not contain consecutive hyphens, and // with a letter or digit, must not contain consecutive hyphens, and
// must contain at least one letter. // must contain at least one letter.
@VisibleForTesting @VisibleForTesting static final String SERVICE_PROTOCOL = "adb-tls-pairing";
static final String SERVICE_PROTOCOL = "adb-tls-pairing";
private final String mServiceType = String.format("_%s._tcp.", SERVICE_PROTOCOL); private final String mServiceType = String.format("_%s._tcp.", SERVICE_PROTOCOL);
private int mPort; private int mPort;
@@ -352,16 +375,24 @@ public class AdbDebuggingManager {
} }
} }
class AdbDebuggingThread extends Thread { @VisibleForTesting
static class AdbDebuggingThread extends Thread {
private boolean mStopped; private boolean mStopped;
private LocalSocket mSocket; private LocalSocket mSocket;
private OutputStream mOutputStream; private OutputStream mOutputStream;
private InputStream mInputStream; private InputStream mInputStream;
private Handler mHandler;
@VisibleForTesting
AdbDebuggingThread() { AdbDebuggingThread() {
super(TAG); super(TAG);
} }
@VisibleForTesting
void setHandler(Handler handler) {
mHandler = handler;
}
@Override @Override
public void run() { public void run() {
if (DEBUG) Slog.d(TAG, "Entering thread"); if (DEBUG) Slog.d(TAG, "Entering thread");
@@ -536,7 +567,7 @@ public class AdbDebuggingManager {
} }
} }
class AdbConnectionInfo { private static class AdbConnectionInfo {
private String mBssid; private String mBssid;
private String mSsid; private String mSsid;
private int mPort; private int mPort;
@@ -743,11 +774,14 @@ public class AdbDebuggingManager {
// Notification when adbd socket is disconnected. // Notification when adbd socket is disconnected.
static final int MSG_ADBD_SOCKET_DISCONNECTED = 27; static final int MSG_ADBD_SOCKET_DISCONNECTED = 27;
// === Messages from other parts of the system
private static final int MESSAGE_KEY_FILES_UPDATED = 28;
// === Messages we can send to adbd =========== // === Messages we can send to adbd ===========
static final String MSG_DISCONNECT_DEVICE = "DD"; static final String MSG_DISCONNECT_DEVICE = "DD";
static final String MSG_DISABLE_ADBDWIFI = "DA"; static final String MSG_DISABLE_ADBDWIFI = "DA";
private AdbKeyStore mAdbKeyStore; @Nullable @VisibleForTesting AdbKeyStore mAdbKeyStore;
// Usb, Wi-Fi transports can be enabled together or separately, so don't break the framework // Usb, Wi-Fi transports can be enabled together or separately, so don't break the framework
// connection unless all transport types are disconnected. // connection unless all transport types are disconnected.
@@ -762,19 +796,19 @@ public class AdbDebuggingManager {
} }
}; };
AdbDebuggingHandler(Looper looper) { /** Constructor that accepts the AdbDebuggingThread to which responses should be sent. */
super(looper); @VisibleForTesting
} AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread) {
/**
* Constructor that accepts the AdbDebuggingThread to which responses should be sent
* and the AdbKeyStore to be used to store the temporary grants.
*/
@TestApi
AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
super(looper); super(looper);
mThread = thread; mThread = thread;
mAdbKeyStore = adbKeyStore; }
/** Initialize the AdbKeyStore so tests can grab mAdbKeyStore immediately. */
@VisibleForTesting
void initKeyStore() {
if (mAdbKeyStore == null) {
mAdbKeyStore = new AdbKeyStore();
}
} }
// Show when at least one device is connected. // Show when at least one device is connected.
@@ -805,6 +839,7 @@ public class AdbDebuggingManager {
registerForAuthTimeChanges(); registerForAuthTimeChanges();
mThread = new AdbDebuggingThread(); mThread = new AdbDebuggingThread();
mThread.setHandler(mHandler);
mThread.start(); mThread.start();
mAdbKeyStore.updateKeyStore(); mAdbKeyStore.updateKeyStore();
@@ -825,8 +860,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.isEmpty()) { if (!mConnectedKeys.isEmpty()) {
for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) { for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
mAdbKeyStore.setLastConnectionTime(entry.getKey(), mAdbKeyStore.setLastConnectionTime(entry.getKey(), mTicker.currentTimeMillis());
System.currentTimeMillis());
} }
sendPersistKeyStoreMessage(); sendPersistKeyStoreMessage();
mConnectedKeys.clear(); mConnectedKeys.clear();
@@ -836,9 +870,7 @@ public class AdbDebuggingManager {
} }
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
if (mAdbKeyStore == null) { initKeyStore();
mAdbKeyStore = new AdbKeyStore();
}
switch (msg.what) { switch (msg.what) {
case MESSAGE_ADB_ENABLED: case MESSAGE_ADB_ENABLED:
@@ -873,7 +905,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.containsKey(key)) { if (!mConnectedKeys.containsKey(key)) {
mConnectedKeys.put(key, 1); mConnectedKeys.put(key, 1);
} }
mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis()); mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage(); sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore(); scheduleJobToUpdateAdbKeyStore();
} }
@@ -920,9 +952,7 @@ public class AdbDebuggingManager {
mConnectedKeys.clear(); mConnectedKeys.clear();
// If the key store has not yet been instantiated then do so now; this avoids // If the key store has not yet been instantiated then do so now; this avoids
// the unnecessary creation of the key store when adb is not enabled. // the unnecessary creation of the key store when adb is not enabled.
if (mAdbKeyStore == null) { initKeyStore();
mAdbKeyStore = new AdbKeyStore();
}
mWifiConnectedKeys.clear(); mWifiConnectedKeys.clear();
mAdbKeyStore.deleteKeyStore(); mAdbKeyStore.deleteKeyStore();
cancelJobToUpdateAdbKeyStore(); cancelJobToUpdateAdbKeyStore();
@@ -937,7 +967,8 @@ public class AdbDebuggingManager {
alwaysAllow = true; alwaysAllow = true;
int refcount = mConnectedKeys.get(key) - 1; int refcount = mConnectedKeys.get(key) - 1;
if (refcount == 0) { if (refcount == 0) {
mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis()); mAdbKeyStore.setLastConnectionTime(
key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage(); sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore(); scheduleJobToUpdateAdbKeyStore();
mConnectedKeys.remove(key); mConnectedKeys.remove(key);
@@ -963,7 +994,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.isEmpty()) { if (!mConnectedKeys.isEmpty()) {
for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) { for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
mAdbKeyStore.setLastConnectionTime(entry.getKey(), mAdbKeyStore.setLastConnectionTime(entry.getKey(),
System.currentTimeMillis()); mTicker.currentTimeMillis());
} }
sendPersistKeyStoreMessage(); sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore(); scheduleJobToUpdateAdbKeyStore();
@@ -984,7 +1015,7 @@ public class AdbDebuggingManager {
} else { } else {
mConnectedKeys.put(key, mConnectedKeys.get(key) + 1); mConnectedKeys.put(key, mConnectedKeys.get(key) + 1);
} }
mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis()); mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage(); sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore(); scheduleJobToUpdateAdbKeyStore();
logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true); logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
@@ -1206,6 +1237,10 @@ public class AdbDebuggingManager {
} }
break; break;
} }
case MESSAGE_KEY_FILES_UPDATED: {
mAdbKeyStore.reloadKeyMap();
break;
}
} }
} }
@@ -1377,8 +1412,7 @@ public class AdbDebuggingManager {
AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent,
UserHandle.ALL); UserHandle.ALL);
// Add the key into the keystore // Add the key into the keystore
mAdbKeyStore.setLastConnectionTime(publicKey, mAdbKeyStore.setLastConnectionTime(publicKey, mTicker.currentTimeMillis());
System.currentTimeMillis());
sendPersistKeyStoreMessage(); sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore(); scheduleJobToUpdateAdbKeyStore();
} }
@@ -1449,19 +1483,13 @@ public class AdbDebuggingManager {
extras.add(new AbstractMap.SimpleEntry<String, String>("ssid", ssid)); extras.add(new AbstractMap.SimpleEntry<String, String>("ssid", ssid));
extras.add(new AbstractMap.SimpleEntry<String, String>("bssid", bssid)); extras.add(new AbstractMap.SimpleEntry<String, String>("bssid", bssid));
int currentUserId = ActivityManager.getCurrentUser(); int currentUserId = ActivityManager.getCurrentUser();
UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId); String componentString =
String componentString; Resources.getSystem().getString(
if (userInfo.isAdmin()) { R.string.config_customAdbWifiNetworkConfirmationComponent);
componentString = Resources.getSystem().getString(
com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
} else {
componentString = Resources.getSystem().getString(
com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
}
ComponentName componentName = ComponentName.unflattenFromString(componentString); ComponentName componentName = ComponentName.unflattenFromString(componentString);
UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras) if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras)
|| startConfirmationService(componentName, userInfo.getUserHandle(), || startConfirmationService(componentName, userInfo.getUserHandle(), extras)) {
extras)) {
return; return;
} }
Slog.e(TAG, "Unable to start customAdbWifiNetworkConfirmation[SecondaryUser]Component " Slog.e(TAG, "Unable to start customAdbWifiNetworkConfirmation[SecondaryUser]Component "
@@ -1543,7 +1571,7 @@ public class AdbDebuggingManager {
/** /**
* Returns a new File with the specified name in the adb directory. * Returns a new File with the specified name in the adb directory.
*/ */
private File getAdbFile(String fileName) { private static File getAdbFile(String fileName) {
File dataDir = Environment.getDataDirectory(); File dataDir = Environment.getDataDirectory();
File adbDir = new File(dataDir, ADB_DIRECTORY); File adbDir = new File(dataDir, ADB_DIRECTORY);
@@ -1556,66 +1584,38 @@ public class AdbDebuggingManager {
} }
File getAdbTempKeysFile() { File getAdbTempKeysFile() {
return getAdbFile(ADB_TEMP_KEYS_FILE); return mTempKeysFile;
} }
File getUserKeyFile() { File getUserKeyFile() {
return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile; return mUserKeyFile;
}
private void writeKey(String key) {
try {
File keyFile = getUserKeyFile();
if (keyFile == null) {
return;
}
FileOutputStream fo = new FileOutputStream(keyFile, true);
fo.write(key.getBytes());
fo.write('\n');
fo.close();
FileUtils.setPermissions(keyFile.toString(),
FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
} catch (IOException ex) {
Slog.e(TAG, "Error writing key:" + ex);
}
} }
private void writeKeys(Iterable<String> keys) { private void writeKeys(Iterable<String> keys) {
AtomicFile atomicKeyFile = null; if (mUserKeyFile == null) {
return;
}
AtomicFile atomicKeyFile = new AtomicFile(mUserKeyFile);
// Note: Do not use a try-with-resources with the FileOutputStream, because AtomicFile
// requires that it's cleaned up with AtomicFile.failWrite();
FileOutputStream fo = null; FileOutputStream fo = null;
try { try {
File keyFile = getUserKeyFile();
if (keyFile == null) {
return;
}
atomicKeyFile = new AtomicFile(keyFile);
fo = atomicKeyFile.startWrite(); fo = atomicKeyFile.startWrite();
for (String key : keys) { for (String key : keys) {
fo.write(key.getBytes()); fo.write(key.getBytes());
fo.write('\n'); fo.write('\n');
} }
atomicKeyFile.finishWrite(fo); atomicKeyFile.finishWrite(fo);
FileUtils.setPermissions(keyFile.toString(),
FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
} catch (IOException ex) { } catch (IOException ex) {
Slog.e(TAG, "Error writing keys: " + ex); Slog.e(TAG, "Error writing keys: " + ex);
if (atomicKeyFile != null) { atomicKeyFile.failWrite(fo);
atomicKeyFile.failWrite(fo); return;
}
} }
}
private void deleteKeyFile() { FileUtils.setPermissions(
File keyFile = getUserKeyFile(); mUserKeyFile.toString(),
if (keyFile != null) { FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
keyFile.delete();
}
} }
/** /**
@@ -1744,6 +1744,13 @@ public class AdbDebuggingManager {
return mAdbWifiEnabled; return mAdbWifiEnabled;
} }
/**
* Notify that they key files were updated so the AdbKeyManager reloads the keys.
*/
public void notifyKeyFilesUpdated() {
mHandler.sendEmptyMessage(AdbDebuggingHandler.MESSAGE_KEY_FILES_UPDATED);
}
/** /**
* Sends a message to the handler to persist the keystore. * Sends a message to the handler to persist the keystore.
*/ */
@@ -1778,7 +1785,7 @@ public class AdbDebuggingManager {
try { try {
dump.write("keystore", AdbDebuggingManagerProto.KEYSTORE, dump.write("keystore", AdbDebuggingManagerProto.KEYSTORE,
FileUtils.readTextFile(getAdbTempKeysFile(), 0, null)); FileUtils.readTextFile(mTempKeysFile, 0, null));
} catch (IOException e) { } catch (IOException e) {
Slog.i(TAG, "Cannot read keystore: ", e); Slog.i(TAG, "Cannot read keystore: ", e);
} }
@@ -1792,12 +1799,12 @@ public class AdbDebuggingManager {
* ADB_ALLOWED_CONNECTION_TIME setting. * ADB_ALLOWED_CONNECTION_TIME setting.
*/ */
class AdbKeyStore { class AdbKeyStore {
private Map<String, Long> mKeyMap;
private Set<String> mSystemKeys;
private File mKeyFile;
private AtomicFile mAtomicKeyFile; private AtomicFile mAtomicKeyFile;
private List<String> mTrustedNetworks; private final Set<String> mSystemKeys;
private final Map<String, Long> mKeyMap = new HashMap<>();
private final List<String> mTrustedNetworks = new ArrayList<>();
private static final int KEYSTORE_VERSION = 1; private static final int KEYSTORE_VERSION = 1;
private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1; private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1;
private static final String XML_KEYSTORE_START_TAG = "keyStore"; private static final String XML_KEYSTORE_START_TAG = "keyStore";
@@ -1819,26 +1826,22 @@ public class AdbDebuggingManager {
public static final long NO_PREVIOUS_CONNECTION = 0; public static final long NO_PREVIOUS_CONNECTION = 0;
/** /**
* Constructor that uses the default location for the persistent adb keystore. * Create an AdbKeyStore instance.
*
* <p>Upon creation, we parse {@link #mTempKeysFile} to determine authorized WiFi APs and
* retrieve the map of stored ADB keys and their last connected times. After that, we read
* the {@link #mUserKeyFile}, and any keys that exist in that file that do not exist in the
* map are added to the map (for backwards compatibility).
*/ */
AdbKeyStore() { AdbKeyStore() {
init();
}
/**
* Constructor that uses the specified file as the location for the persistent adb keystore.
*/
AdbKeyStore(File keyFile) {
mKeyFile = keyFile;
init();
}
private void init() {
initKeyFile(); initKeyFile();
mKeyMap = getKeyMap(); readTempKeysFile();
mTrustedNetworks = getTrustedNetworks();
mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE); mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE);
addUserKeysToKeyStore(); addExistingUserKeysToKeyStore();
}
public void reloadKeyMap() {
readTempKeysFile();
} }
public void addTrustedNetwork(String bssid) { public void addTrustedNetwork(String bssid) {
@@ -1877,7 +1880,6 @@ public class AdbDebuggingManager {
public void removeKey(String key) { public void removeKey(String key) {
if (mKeyMap.containsKey(key)) { if (mKeyMap.containsKey(key)) {
mKeyMap.remove(key); mKeyMap.remove(key);
writeKeys(mKeyMap.keySet());
sendPersistKeyStoreMessage(); sendPersistKeyStoreMessage();
} }
} }
@@ -1886,12 +1888,9 @@ public class AdbDebuggingManager {
* Initializes the key file that will be used to persist the adb grants. * Initializes the key file that will be used to persist the adb grants.
*/ */
private void initKeyFile() { private void initKeyFile() {
if (mKeyFile == null) { // mTempKeysFile can be null if the adb file cannot be obtained
mKeyFile = getAdbTempKeysFile(); if (mTempKeysFile != null) {
} mAtomicKeyFile = new AtomicFile(mTempKeysFile);
// getAdbTempKeysFile can return null if the adb file cannot be obtained
if (mKeyFile != null) {
mAtomicKeyFile = new AtomicFile(mKeyFile);
} }
} }
@@ -1932,201 +1931,108 @@ public class AdbDebuggingManager {
} }
/** /**
* Returns the key map with the keys and last connection times from the key file. * Update the key map and the trusted networks list with values parsed from the temp keys
* file.
*/ */
private Map<String, Long> getKeyMap() { private void readTempKeysFile() {
Map<String, Long> keyMap = new HashMap<String, Long>(); mKeyMap.clear();
// if the AtomicFile could not be instantiated before attempt again; if it still fails mTrustedNetworks.clear();
// return an empty key map.
if (mAtomicKeyFile == null) { if (mAtomicKeyFile == null) {
initKeyFile(); initKeyFile();
if (mAtomicKeyFile == null) { if (mAtomicKeyFile == null) {
Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading"); Slog.e(
return keyMap; TAG,
"Unable to obtain the key file, " + mTempKeysFile + ", for reading");
return;
} }
} }
if (!mAtomicKeyFile.exists()) { if (!mAtomicKeyFile.exists()) {
return keyMap; return;
} }
try (FileInputStream keyStream = mAtomicKeyFile.openRead()) { try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
TypedXmlPullParser parser = Xml.resolvePullParser(keyStream); TypedXmlPullParser parser;
// Check for supported keystore version. try {
XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG); parser = Xml.resolvePullParser(keyStream);
if (parser.next() != XmlPullParser.END_DOCUMENT) { XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
String tagName = parser.getName();
if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
+ tagName);
return keyMap;
}
int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION); int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION);
if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) { if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
Slog.e(TAG, "Keystore version=" + keystoreVersion Slog.e(TAG, "Keystore version=" + keystoreVersion
+ " not supported (max_supported=" + " not supported (max_supported="
+ MAX_SUPPORTED_KEYSTORE_VERSION + ")"); + MAX_SUPPORTED_KEYSTORE_VERSION + ")");
return keyMap; return;
} }
} catch (XmlPullParserException e) {
// This could be because the XML document doesn't start with
// XML_KEYSTORE_START_TAG. Try again, instead just starting the document with
// the adbKey tag (the old format).
parser = Xml.resolvePullParser(keyStream);
} }
while (parser.next() != XmlPullParser.END_DOCUMENT) { readKeyStoreContents(parser);
String tagName = parser.getName();
if (tagName == null) {
break;
} else if (!tagName.equals(XML_TAG_ADB_KEY)) {
XmlUtils.skipCurrentTag(parser);
continue;
}
String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
long connectionTime;
try {
connectionTime = parser.getAttributeLong(null,
XML_ATTRIBUTE_LAST_CONNECTION);
} catch (XmlPullParserException e) {
Slog.e(TAG,
"Caught a NumberFormatException parsing the last connection time: "
+ e);
XmlUtils.skipCurrentTag(parser);
continue;
}
keyMap.put(key, connectionTime);
}
} catch (IOException e) { } catch (IOException e) {
Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e); Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e);
} catch (XmlPullParserException e) { } catch (XmlPullParserException e) {
Slog.w(TAG, "Caught XmlPullParserException parsing the XML key file: ", e); Slog.e(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
// The file could be written in a format prior to introducing keystore tag.
return getKeyMapBeforeKeystoreVersion();
} }
return keyMap;
} }
private void readKeyStoreContents(TypedXmlPullParser parser)
/** throws XmlPullParserException, IOException {
* Returns the key map with the keys and last connection times from the key file. // This parser is very forgiving. For backwards-compatibility, we simply iterate through
* This implementation was prior to adding the XML_KEYSTORE_START_TAG. // all the tags in the file, skipping over anything that's not an <adbKey> tag or a
*/ // <wifiAP> tag. Invalid tags (such as ones that don't have a valid "lastConnection"
private Map<String, Long> getKeyMapBeforeKeystoreVersion() { // attribute) are simply ignored.
Map<String, Long> keyMap = new HashMap<String, Long>(); while ((parser.next()) != XmlPullParser.END_DOCUMENT) {
// if the AtomicFile could not be instantiated before attempt again; if it still fails String tagName = parser.getName();
// return an empty key map. if (XML_TAG_ADB_KEY.equals(tagName)) {
if (mAtomicKeyFile == null) { addAdbKeyToKeyMap(parser);
initKeyFile(); } else if (XML_TAG_WIFI_ACCESS_POINT.equals(tagName)) {
if (mAtomicKeyFile == null) { addTrustedNetworkToTrustedNetworks(parser);
Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading"); } else {
return keyMap; Slog.w(TAG, "Ignoring tag '" + tagName + "'. Not recognized.");
} }
XmlUtils.skipCurrentTag(parser);
} }
if (!mAtomicKeyFile.exists()) {
return keyMap;
}
try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
if (tagName == null) {
break;
} else if (!tagName.equals(XML_TAG_ADB_KEY)) {
XmlUtils.skipCurrentTag(parser);
continue;
}
String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
long connectionTime;
try {
connectionTime = parser.getAttributeLong(null,
XML_ATTRIBUTE_LAST_CONNECTION);
} catch (XmlPullParserException e) {
Slog.e(TAG,
"Caught a NumberFormatException parsing the last connection time: "
+ e);
XmlUtils.skipCurrentTag(parser);
continue;
}
keyMap.put(key, connectionTime);
}
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
}
return keyMap;
} }
/** private void addAdbKeyToKeyMap(TypedXmlPullParser parser) {
* Returns the map of trusted networks from the keystore file. String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
* try {
* This was implemented in keystore version 1. long connectionTime =
*/ parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CONNECTION);
private List<String> getTrustedNetworks() { mKeyMap.put(key, connectionTime);
List<String> trustedNetworks = new ArrayList<String>(); } catch (XmlPullParserException e) {
// if the AtomicFile could not be instantiated before attempt again; if it still fails Slog.e(TAG, "Error reading adbKey attributes", e);
// return an empty key map.
if (mAtomicKeyFile == null) {
initKeyFile();
if (mAtomicKeyFile == null) {
Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
return trustedNetworks;
}
} }
if (!mAtomicKeyFile.exists()) { }
return trustedNetworks;
} private void addTrustedNetworkToTrustedNetworks(TypedXmlPullParser parser) {
try (FileInputStream keyStream = mAtomicKeyFile.openRead()) { String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
TypedXmlPullParser parser = Xml.resolvePullParser(keyStream); mTrustedNetworks.add(bssid);
// Check for supported keystore version.
XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
if (parser.next() != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
+ tagName);
return trustedNetworks;
}
int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION);
if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
Slog.e(TAG, "Keystore version=" + keystoreVersion
+ " not supported (max_supported="
+ MAX_SUPPORTED_KEYSTORE_VERSION);
return trustedNetworks;
}
}
while (parser.next() != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
if (tagName == null) {
break;
} else if (!tagName.equals(XML_TAG_WIFI_ACCESS_POINT)) {
XmlUtils.skipCurrentTag(parser);
continue;
}
String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
trustedNetworks.add(bssid);
}
} catch (IOException | XmlPullParserException | NumberFormatException e) {
Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
}
return trustedNetworks;
} }
/** /**
* Updates the keystore with keys that were previously set to be always allowed before the * Updates the keystore with keys that were previously set to be always allowed before the
* connection time of keys was tracked. * connection time of keys was tracked.
*/ */
private void addUserKeysToKeyStore() { private void addExistingUserKeysToKeyStore() {
File userKeyFile = getUserKeyFile(); if (mUserKeyFile == null || !mUserKeyFile.exists()) {
return;
}
boolean mapUpdated = false; boolean mapUpdated = false;
if (userKeyFile != null && userKeyFile.exists()) { try (BufferedReader in = new BufferedReader(new FileReader(mUserKeyFile))) {
try (BufferedReader in = new BufferedReader(new FileReader(userKeyFile))) { String key;
long time = System.currentTimeMillis(); while ((key = in.readLine()) != null) {
String key; // if the keystore does not contain the key from the user key file then add
while ((key = in.readLine()) != null) { // it to the Map with the current system time to prevent it from expiring
// if the keystore does not contain the key from the user key file then add // immediately if the user is actively using this key.
// it to the Map with the current system time to prevent it from expiring if (!mKeyMap.containsKey(key)) {
// immediately if the user is actively using this key. mKeyMap.put(key, mTicker.currentTimeMillis());
if (!mKeyMap.containsKey(key)) { mapUpdated = true;
mKeyMap.put(key, time);
mapUpdated = true;
}
} }
} catch (IOException e) {
Slog.e(TAG, "Caught an exception reading " + userKeyFile + ": " + e);
} }
} catch (IOException e) {
Slog.e(TAG, "Caught an exception reading " + mUserKeyFile + ": " + e);
} }
if (mapUpdated) { if (mapUpdated) {
sendPersistKeyStoreMessage(); sendPersistKeyStoreMessage();
@@ -2147,7 +2053,9 @@ public class AdbDebuggingManager {
if (mAtomicKeyFile == null) { if (mAtomicKeyFile == null) {
initKeyFile(); initKeyFile();
if (mAtomicKeyFile == null) { if (mAtomicKeyFile == null) {
Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing"); Slog.e(
TAG,
"Unable to obtain the key file, " + mTempKeysFile + ", for writing");
return; return;
} }
} }
@@ -2178,17 +2086,21 @@ public class AdbDebuggingManager {
Slog.e(TAG, "Caught an exception writing the key map: ", e); Slog.e(TAG, "Caught an exception writing the key map: ", e);
mAtomicKeyFile.failWrite(keyStream); mAtomicKeyFile.failWrite(keyStream);
} }
writeKeys(mKeyMap.keySet());
} }
private boolean filterOutOldKeys() { private boolean filterOutOldKeys() {
boolean keysDeleted = false;
long allowedTime = getAllowedConnectionTime(); long allowedTime = getAllowedConnectionTime();
long systemTime = System.currentTimeMillis(); if (allowedTime == 0) {
return false;
}
boolean keysDeleted = false;
long systemTime = mTicker.currentTimeMillis();
Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator(); Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
while (keyMapIterator.hasNext()) { while (keyMapIterator.hasNext()) {
Map.Entry<String, Long> keyEntry = keyMapIterator.next(); Map.Entry<String, Long> keyEntry = keyMapIterator.next();
long connectionTime = keyEntry.getValue(); long connectionTime = keyEntry.getValue();
if (allowedTime != 0 && systemTime > (connectionTime + allowedTime)) { if (systemTime > (connectionTime + allowedTime)) {
keyMapIterator.remove(); keyMapIterator.remove();
keysDeleted = true; keysDeleted = true;
} }
@@ -2212,7 +2124,7 @@ public class AdbDebuggingManager {
if (allowedTime == 0) { if (allowedTime == 0) {
return minExpiration; return minExpiration;
} }
long systemTime = System.currentTimeMillis(); long systemTime = mTicker.currentTimeMillis();
Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator(); Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
while (keyMapIterator.hasNext()) { while (keyMapIterator.hasNext()) {
Map.Entry<String, Long> keyEntry = keyMapIterator.next(); Map.Entry<String, Long> keyEntry = keyMapIterator.next();
@@ -2233,7 +2145,9 @@ public class AdbDebuggingManager {
public void deleteKeyStore() { public void deleteKeyStore() {
mKeyMap.clear(); mKeyMap.clear();
mTrustedNetworks.clear(); mTrustedNetworks.clear();
deleteKeyFile(); if (mUserKeyFile != null) {
mUserKeyFile.delete();
}
if (mAtomicKeyFile == null) { if (mAtomicKeyFile == null) {
return; return;
} }
@@ -2260,7 +2174,8 @@ public class AdbDebuggingManager {
* is set to true the time will be set even if it is older than the previously written * is set to true the time will be set even if it is older than the previously written
* connection time. * connection time.
*/ */
public void setLastConnectionTime(String key, long connectionTime, boolean force) { @VisibleForTesting
void setLastConnectionTime(String key, long connectionTime, boolean force) {
// Do not set the connection time to a value that is earlier than what was previously // Do not set the connection time to a value that is earlier than what was previously
// stored as the last connection time unless force is set. // stored as the last connection time unless force is set.
if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime && !force) { if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime && !force) {
@@ -2271,11 +2186,6 @@ public class AdbDebuggingManager {
if (mSystemKeys.contains(key)) { if (mSystemKeys.contains(key)) {
return; return;
} }
// if this is the first time the key is being added then write it to the key file as
// well.
if (!mKeyMap.containsKey(key)) {
writeKey(key);
}
mKeyMap.put(key, connectionTime); mKeyMap.put(key, connectionTime);
} }
@@ -2307,12 +2217,8 @@ public class AdbDebuggingManager {
long allowedConnectionTime = getAllowedConnectionTime(); long allowedConnectionTime = getAllowedConnectionTime();
// if the allowed connection time is 0 then revert to the previous behavior of always // if the allowed connection time is 0 then revert to the previous behavior of always
// allowing previously granted adb grants. // allowing previously granted adb grants.
if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime return allowedConnectionTime == 0
+ allowedConnectionTime))) { || (mTicker.currentTimeMillis() < (lastConnectionTime + allowedConnectionTime));
return true;
} else {
return false;
}
} }
/** /**
@@ -2324,4 +2230,15 @@ public class AdbDebuggingManager {
return mTrustedNetworks.contains(bssid); return mTrustedNetworks.contains(bssid);
} }
} }
/**
* A Guava-like interface for getting the current system time.
*
* This allows us to swap a fake ticker in for testing to reduce "Thread.sleep()" calls and test
* for exact expected times instead of random ones.
*/
@VisibleForTesting
interface Ticker {
long currentTimeMillis();
}
} }

View File

@@ -151,6 +151,14 @@ public class AdbService extends IAdbManager.Stub {
return mDebuggingManager == null ? null : mDebuggingManager.getAdbTempKeysFile(); return mDebuggingManager == null ? null : mDebuggingManager.getAdbTempKeysFile();
} }
@Override
public void notifyKeyFilesUpdated() {
if (mDebuggingManager == null) {
return;
}
mDebuggingManager.notifyKeyFilesUpdated();
}
@Override @Override
public void startAdbdForTransport(byte transportType) { public void startAdbdForTransport(byte transportType) {
FgThread.getHandler().sendMessage(obtainMessage( FgThread.getHandler().sendMessage(obtainMessage(

View File

@@ -189,6 +189,7 @@ public class TestHarnessModeService extends SystemService {
if (adbManager.getAdbTempKeysFile() != null) { if (adbManager.getAdbTempKeysFile() != null) {
writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath()); writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath());
} }
adbManager.notifyKeyFilesUpdated();
} }
private void configureUser() { private void configureUser() {

View File

@@ -36,8 +36,6 @@ import android.util.Log;
import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry;
import com.android.server.FgThread;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -48,6 +46,11 @@ import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileReader; import java.io.FileReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@@ -88,6 +91,7 @@ public final class AdbDebuggingManagerTest {
private long mOriginalAllowedConnectionTime; private long mOriginalAllowedConnectionTime;
private File mAdbKeyXmlFile; private File mAdbKeyXmlFile;
private File mAdbKeyFile; private File mAdbKeyFile;
private FakeTicker mFakeTicker;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@@ -96,14 +100,25 @@ public final class AdbDebuggingManagerTest {
if (mAdbKeyFile.exists()) { if (mAdbKeyFile.exists()) {
mAdbKeyFile.delete(); mAdbKeyFile.delete();
} }
mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile);
mAdbKeyXmlFile = new File(mContext.getFilesDir(), "test_adb_keys.xml"); mAdbKeyXmlFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
if (mAdbKeyXmlFile.exists()) { if (mAdbKeyXmlFile.exists()) {
mAdbKeyXmlFile.delete(); mAdbKeyXmlFile.delete();
} }
mFakeTicker = new FakeTicker();
// Set the ticker time to October 22, 2008 (the day the T-Mobile G1 was released)
mFakeTicker.advance(1224658800L);
mThread = new AdbDebuggingThreadTest(); mThread = new AdbDebuggingThreadTest();
mKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile); mManager = new AdbDebuggingManager(
mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore); mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile, mAdbKeyXmlFile, mThread, mFakeTicker);
mHandler = mManager.mHandler;
mThread.setHandler(mHandler);
mHandler.initKeyStore();
mKeyStore = mHandler.mAdbKeyStore;
mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime(); mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
mBlockingQueue = new ArrayBlockingQueue<>(1); mBlockingQueue = new ArrayBlockingQueue<>(1);
} }
@@ -122,7 +137,7 @@ public final class AdbDebuggingManagerTest {
private void setAllowedConnectionTime(long connectionTime) { private void setAllowedConnectionTime(long connectionTime) {
Settings.Global.putLong(mContext.getContentResolver(), Settings.Global.putLong(mContext.getContentResolver(),
Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime); Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
}; }
@Test @Test
public void testAllowNewKeyOnce() throws Exception { public void testAllowNewKeyOnce() throws Exception {
@@ -158,20 +173,15 @@ public final class AdbDebuggingManagerTest {
// Allow a connection from a new key with the 'Always allow' option selected. // Allow a connection from a new key with the 'Always allow' option selected.
runAdbTest(TEST_KEY_1, true, true, false); runAdbTest(TEST_KEY_1, true, true, false);
// Get the last connection time for the currently connected key to verify that it is updated // Advance the clock by 10ms to ensure there's a difference
// after the disconnect. mFakeTicker.advance(10 * 1_000_000);
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
// Sleep for a small amount of time to ensure a difference can be observed in the last
// connection time after a disconnect.
Thread.sleep(10);
// Send the disconnect message for the currently connected key to trigger an update of the // Send the disconnect message for the currently connected key to trigger an update of the
// last connection time. // last connection time.
disconnectKey(TEST_KEY_1); disconnectKey(TEST_KEY_1);
assertNotEquals( assertEquals(
"The last connection time was not updated after the disconnect", "The last connection time was not updated after the disconnect",
lastConnectionTime, mFakeTicker.currentTimeMillis(),
mKeyStore.getLastConnectionTime(TEST_KEY_1)); mKeyStore.getLastConnectionTime(TEST_KEY_1));
} }
@@ -244,8 +254,8 @@ public final class AdbDebuggingManagerTest {
// Get the current last connection time for comparison after the scheduled job is run // Get the current last connection time for comparison after the scheduled job is run
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
// Sleep a small amount of time to ensure that the updated connection time changes // Advance a small amount of time to ensure that the updated connection time changes
Thread.sleep(10); mFakeTicker.advance(10);
// Send a message to the handler to update the last connection time for the active key // Send a message to the handler to update the last connection time for the active key
updateKeyStore(); updateKeyStore();
@@ -269,13 +279,13 @@ public final class AdbDebuggingManagerTest {
persistKeyStore(); persistKeyStore();
assertTrue( assertTrue(
"The key with the 'Always allow' option selected was not persisted in the keystore", "The key with the 'Always allow' option selected was not persisted in the keystore",
mManager.new AdbKeyStore(mAdbKeyXmlFile).isKeyAuthorized(TEST_KEY_1)); mManager.new AdbKeyStore().isKeyAuthorized(TEST_KEY_1));
// Get the current last connection time to ensure it is updated in the persisted keystore. // Get the current last connection time to ensure it is updated in the persisted keystore.
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
// Sleep a small amount of time to ensure the last connection time is updated. // Advance a small amount of time to ensure the last connection time is updated.
Thread.sleep(10); mFakeTicker.advance(10);
// Send a message to the handler to update the last connection time for the active key. // Send a message to the handler to update the last connection time for the active key.
updateKeyStore(); updateKeyStore();
@@ -286,7 +296,7 @@ public final class AdbDebuggingManagerTest {
assertNotEquals( assertNotEquals(
"The last connection time in the key file was not updated after the update " "The last connection time in the key file was not updated after the update "
+ "connection time message", lastConnectionTime, + "connection time message", lastConnectionTime,
mManager.new AdbKeyStore(mAdbKeyXmlFile).getLastConnectionTime(TEST_KEY_1)); mManager.new AdbKeyStore().getLastConnectionTime(TEST_KEY_1));
// Verify that the key is in the adb_keys file // Verify that the key is in the adb_keys file
assertTrue("The key was not in the adb_keys file after persisting the keystore", assertTrue("The key was not in the adb_keys file after persisting the keystore",
isKeyInFile(TEST_KEY_1, mAdbKeyFile)); isKeyInFile(TEST_KEY_1, mAdbKeyFile));
@@ -327,8 +337,8 @@ public final class AdbDebuggingManagerTest {
// Set the allowed window to a small value to ensure the time is beyond the allowed window. // Set the allowed window to a small value to ensure the time is beyond the allowed window.
setAllowedConnectionTime(1); setAllowedConnectionTime(1);
// Sleep for a small amount of time to exceed the allowed window. // Advance a small amount of time to exceed the allowed window.
Thread.sleep(10); mFakeTicker.advance(10);
// The AdbKeyStore has a method to get the time of the next key expiration to ensure the // The AdbKeyStore has a method to get the time of the next key expiration to ensure the
// scheduled job runs at the time of the next expiration or after 24 hours, whichever occurs // scheduled job runs at the time of the next expiration or after 24 hours, whichever occurs
@@ -478,9 +488,12 @@ public final class AdbDebuggingManagerTest {
// Set the current expiration time to a minute from expiration and verify this new value is // Set the current expiration time to a minute from expiration and verify this new value is
// returned. // returned.
final long newExpirationTime = 60000; final long newExpirationTime = 60000;
mKeyStore.setLastConnectionTime(TEST_KEY_1, mKeyStore.setLastConnectionTime(
System.currentTimeMillis() - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME TEST_KEY_1,
+ newExpirationTime, true); mFakeTicker.currentTimeMillis()
- Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME
+ newExpirationTime,
true);
expirationTime = mKeyStore.getNextExpirationTime(); expirationTime = mKeyStore.getNextExpirationTime();
if (Math.abs(expirationTime - newExpirationTime) > epsilon) { if (Math.abs(expirationTime - newExpirationTime) > epsilon) {
fail("The expiration time for a key about to expire, " + expirationTime fail("The expiration time for a key about to expire, " + expirationTime
@@ -525,7 +538,7 @@ public final class AdbDebuggingManagerTest {
// Get the last connection time for the key to verify that it is updated when the connected // Get the last connection time for the key to verify that it is updated when the connected
// key message is sent. // key message is sent.
long connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); long connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
Thread.sleep(10); mFakeTicker.advance(10);
mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY, mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY,
TEST_KEY_1).sendToTarget(); TEST_KEY_1).sendToTarget();
flushHandlerQueue(); flushHandlerQueue();
@@ -536,7 +549,7 @@ public final class AdbDebuggingManagerTest {
// Verify that the scheduled job updates the connection time of the key. // Verify that the scheduled job updates the connection time of the key.
connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
Thread.sleep(10); mFakeTicker.advance(10);
updateKeyStore(); updateKeyStore();
assertNotEquals( assertNotEquals(
"The connection time for the key must be updated when the update keystore message" "The connection time for the key must be updated when the update keystore message"
@@ -545,7 +558,7 @@ public final class AdbDebuggingManagerTest {
// Verify that the connection time is updated when the key is disconnected. // Verify that the connection time is updated when the key is disconnected.
connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
Thread.sleep(10); mFakeTicker.advance(10);
disconnectKey(TEST_KEY_1); disconnectKey(TEST_KEY_1);
assertNotEquals( assertNotEquals(
"The connection time for the key must be updated when the disconnected message is" "The connection time for the key must be updated when the disconnected message is"
@@ -628,11 +641,11 @@ public final class AdbDebuggingManagerTest {
setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME); setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
// The untracked keys should be added to the keystore as part of the constructor. // The untracked keys should be added to the keystore as part of the constructor.
AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile); AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore();
// Verify that the connection time for each test key is within a small value of the current // Verify that the connection time for each test key is within a small value of the current
// time. // time.
long time = System.currentTimeMillis(); long time = mFakeTicker.currentTimeMillis();
for (String key : testKeys) { for (String key : testKeys) {
long connectionTime = adbKeyStore.getLastConnectionTime(key); long connectionTime = adbKeyStore.getLastConnectionTime(key);
if (Math.abs(connectionTime - connectionTime) > epsilon) { if (Math.abs(connectionTime - connectionTime) > epsilon) {
@@ -651,11 +664,11 @@ public final class AdbDebuggingManagerTest {
runAdbTest(TEST_KEY_1, true, true, false); runAdbTest(TEST_KEY_1, true, true, false);
runAdbTest(TEST_KEY_2, true, true, false); runAdbTest(TEST_KEY_2, true, true, false);
// Sleep a small amount of time to ensure the connection time is updated by the scheduled // Advance a small amount of time to ensure the connection time is updated by the scheduled
// job. // job.
long connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1); long connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
long connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2); long connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
Thread.sleep(10); mFakeTicker.advance(10);
updateKeyStore(); updateKeyStore();
assertNotEquals( assertNotEquals(
"The connection time for test key 1 must be updated after the scheduled job runs", "The connection time for test key 1 must be updated after the scheduled job runs",
@@ -669,7 +682,7 @@ public final class AdbDebuggingManagerTest {
disconnectKey(TEST_KEY_2); disconnectKey(TEST_KEY_2);
connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1); connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2); connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
Thread.sleep(10); mFakeTicker.advance(10);
updateKeyStore(); updateKeyStore();
assertNotEquals( assertNotEquals(
"The connection time for test key 1 must be updated after another key is " "The connection time for test key 1 must be updated after another key is "
@@ -686,8 +699,6 @@ public final class AdbDebuggingManagerTest {
// to clear the adb authorizations when adb is disabled after a boot a NullPointerException // to clear the adb authorizations when adb is disabled after a boot a NullPointerException
// was thrown as deleteKeyStore is invoked against the key store. This test ensures the // was thrown as deleteKeyStore is invoked against the key store. This test ensures the
// key store can be successfully cleared when adb is disabled. // key store can be successfully cleared when adb is disabled.
mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper());
clearKeyStore(); clearKeyStore();
} }
@@ -723,12 +734,104 @@ public final class AdbDebuggingManagerTest {
// Now remove one of the keys and make sure the other key is still there // Now remove one of the keys and make sure the other key is still there
mKeyStore.removeKey(TEST_KEY_1); mKeyStore.removeKey(TEST_KEY_1);
// Wait for the handler queue to receive the MESSAGE_ADB_PERSIST_KEYSTORE
flushHandlerQueue();
assertFalse("The key was still in the adb_keys file after removing the key", assertFalse("The key was still in the adb_keys file after removing the key",
isKeyInFile(TEST_KEY_1, mAdbKeyFile)); isKeyInFile(TEST_KEY_1, mAdbKeyFile));
assertTrue("The key was not in the adb_keys file after removing a different key", assertTrue("The key was not in the adb_keys file after removing a different key",
isKeyInFile(TEST_KEY_2, mAdbKeyFile)); isKeyInFile(TEST_KEY_2, mAdbKeyFile));
} }
@Test
public void testAdbKeyStore_addDuplicateKey_doesNotAddDuplicateToAdbKeyFile() throws Exception {
setAllowedConnectionTime(0);
runAdbTest(TEST_KEY_1, true, true, false);
persistKeyStore();
runAdbTest(TEST_KEY_1, true, true, false);
persistKeyStore();
assertEquals("adb_keys contains duplicate keys", 1, adbKeyFileKeys(mAdbKeyFile).size());
}
@Test
public void testAdbKeyStore_adbTempKeysFile_readsLastConnectionTimeFromXml() throws Exception {
long insertTime = mFakeTicker.currentTimeMillis();
runAdbTest(TEST_KEY_1, true, true, false);
persistKeyStore();
mFakeTicker.advance(10);
AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
assertEquals(
"KeyStore not populated from the XML file.",
insertTime,
newKeyStore.getLastConnectionTime(TEST_KEY_1));
}
@Test
public void test_notifyKeyFilesUpdated_filesDeletedRemovesPreviouslyAddedKey()
throws Exception {
runAdbTest(TEST_KEY_1, true, true, false);
persistKeyStore();
Files.delete(mAdbKeyXmlFile.toPath());
Files.delete(mAdbKeyFile.toPath());
mManager.notifyKeyFilesUpdated();
flushHandlerQueue();
assertFalse(
"Key is authorized after reloading deleted key files. Was state preserved?",
mKeyStore.isKeyAuthorized(TEST_KEY_1));
}
@Test
public void test_notifyKeyFilesUpdated_newKeyIsAuthorized() throws Exception {
runAdbTest(TEST_KEY_1, true, true, false);
persistKeyStore();
// Back up the existing key files
Path tempXmlFile = Files.createTempFile("adbKeyXmlFile", ".tmp");
Path tempAdbKeysFile = Files.createTempFile("adb_keys", ".tmp");
Files.copy(mAdbKeyXmlFile.toPath(), tempXmlFile, StandardCopyOption.REPLACE_EXISTING);
Files.copy(mAdbKeyFile.toPath(), tempAdbKeysFile, StandardCopyOption.REPLACE_EXISTING);
// Delete the existing key files
Files.delete(mAdbKeyXmlFile.toPath());
Files.delete(mAdbKeyFile.toPath());
// Notify the manager that adb key files have changed.
mManager.notifyKeyFilesUpdated();
flushHandlerQueue();
// Copy the files back
Files.copy(tempXmlFile, mAdbKeyXmlFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.copy(tempAdbKeysFile, mAdbKeyFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
// Tell the manager that the key files have changed.
mManager.notifyKeyFilesUpdated();
flushHandlerQueue();
assertTrue(
"Key is not authorized after reloading key files.",
mKeyStore.isKeyAuthorized(TEST_KEY_1));
}
@Test
public void testAdbKeyStore_adbWifiConnect_storesBssidWhenAlwaysAllow() throws Exception {
String trustedNetwork = "My Network";
mKeyStore.addTrustedNetwork(trustedNetwork);
persistKeyStore();
AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
assertTrue(
"Persisted trusted network not found in new keystore instance.",
newKeyStore.isTrustedNetwork(trustedNetwork));
}
@Test @Test
public void testIsValidMdnsServiceName() { public void testIsValidMdnsServiceName() {
// Longer than 15 characters // Longer than 15 characters
@@ -1030,28 +1133,27 @@ public final class AdbDebuggingManagerTest {
if (key == null) { if (key == null) {
return false; return false;
} }
return adbKeyFileKeys(keyFile).contains(key);
}
private static List<String> adbKeyFileKeys(File keyFile) throws Exception {
List<String> keys = new ArrayList<>();
if (keyFile.exists()) { if (keyFile.exists()) {
try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) { try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) {
String currKey; String currKey;
while ((currKey = in.readLine()) != null) { while ((currKey = in.readLine()) != null) {
if (key.equals(currKey)) { keys.add(currKey);
return true;
}
} }
} }
} }
return false; return keys;
} }
/** /**
* Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager * Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
* indicating whether the key should be allowed to connect. * indicating whether the key should be allowed to connect.
*/ */
class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread { private class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
AdbDebuggingThreadTest() {
mManager.super();
}
@Override @Override
public void sendResponse(String msg) { public void sendResponse(String msg) {
TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg); TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
@@ -1091,4 +1193,17 @@ public final class AdbDebuggingManagerTest {
return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}"; return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
} }
} }
private static class FakeTicker implements AdbDebuggingManager.Ticker {
private long mCurrentTime;
private void advance(long milliseconds) {
mCurrentTime += milliseconds;
}
@Override
public long currentTimeMillis() {
return mCurrentTime;
}
}
} }