Merge "Fix ADB key file reading" into tm-dev am: 9523639bb0

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19049555

Change-Id: Iccea075182ba10344cabe1cc9d9b94c28569711c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Raju Kulkarni
2022-06-29 20:29:40 +00:00
committed by Automerger Merge Worker
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(time - connectionTime) > epsilon) { if (Math.abs(time - 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;
}
}
} }