Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8cf3d9af authored by Sherif Eid's avatar Sherif Eid
Browse files

Move adbd key store logic to AdbdKeyStoreStorage

AdbdKeyStoreStorage handles logic to read/save/delete keys that are
saved in a text file.

Test: AdbDebuggingManagerTest
Bug: 420613813
Flag: EXEMPT refactor
Change-Id: I86a1682ca381f12d92f7f4d1d3a01bb922f772f6
parent 42c55a4e
Loading
Loading
Loading
Loading
+17 −46
Original line number Diff line number Diff line
@@ -1335,35 +1335,6 @@ public class AdbDebuggingManager {
        return mUserKeyFile;
    }

    private void writeKeys(Iterable<String> keys) {
        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;
        try {
            fo = atomicKeyFile.startWrite();
            for (String key : keys) {
                fo.write(key.getBytes());
                fo.write('\n');
            }
            atomicKeyFile.finishWrite(fo);
        } catch (IOException ex) {
            Slog.e(TAG, "Error writing keys: " + ex);
            atomicKeyFile.failWrite(fo);
            return;
        }

        FileUtils.setPermissions(
                mUserKeyFile.toString(),
                FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP,
                -1,
                -1);
    }

    /**
     * When {@code enabled} is {@code true}, this allows ADB debugging and starts the ADB handler
     * thread. When {@code enabled} is {@code false}, this disallows ADB debugging for the
@@ -1545,6 +1516,14 @@ public class AdbDebuggingManager {
        private final Map<String, Long> mKeyMap = new HashMap<>();
        private final List<String> mTrustedNetworks = new ArrayList<>();

        /**
         * Manages the list of keys that adbd always allows to connect, regardless of last
         * connection-time.
         *
         * <p>This list of keys along with #{mSystemKeys} represents the source of truth for adbd.
         */
        private final AdbdKeyStoreStorage mAdbKeyUser;

        private static final int KEYSTORE_VERSION = 1;
        private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1;
        private static final String XML_KEYSTORE_START_TAG = "keyStore";
@@ -1574,6 +1553,7 @@ public class AdbDebuggingManager {
         * map are added to the map (for backwards compatibility).
         */
        AdbKeyStore() {
            mAdbKeyUser = new AdbdKeyStoreStorage(mUserKeyFile);
            initKeyFile();
            readTempKeysFile();
            mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE);
@@ -1755,24 +1735,17 @@ public class AdbDebuggingManager {
         * connection time of keys was tracked.
         */
        private void addExistingUserKeysToKeyStore() {
            if (mUserKeyFile == null || !mUserKeyFile.exists()) {
                return;
            }
            Set<String> keys = mAdbKeyUser.loadKeys();
            boolean mapUpdated = false;
            try (BufferedReader in = new BufferedReader(new FileReader(mUserKeyFile))) {
                String key;
                while ((key = in.readLine()) != null) {
            for (String key : keys) {
                if (!mKeyMap.containsKey(key)) {
                    // if the keystore does not contain the key from the user key file then add
                    // it to the Map with the current system time to prevent it from expiring
                    // immediately if the user is actively using this key.
                    if (!mKeyMap.containsKey(key)) {
                    mKeyMap.put(key, mTicker.currentTimeMillis());
                    mapUpdated = true;
                }
            }
            } catch (IOException e) {
                Slog.e(TAG, "Caught an exception reading " + mUserKeyFile + ": " + e);
            }
            if (mapUpdated) {
                sendPersistKeyStoreMessage();
            }
@@ -1823,7 +1796,7 @@ public class AdbDebuggingManager {
                Slog.e(TAG, "Caught an exception writing the key map: ", e);
                mAtomicKeyFile.failWrite(keyStream);
            }
            writeKeys(mKeyMap.keySet());
            mAdbKeyUser.saveKeys(mKeyMap.keySet());
        }

        private boolean filterOutOldKeys() {
@@ -1845,7 +1818,7 @@ public class AdbDebuggingManager {
            // if any keys were deleted then the key file should be rewritten with the active keys
            // to prevent authorizing a key that is now beyond the allowed window.
            if (keysDeleted) {
                writeKeys(mKeyMap.keySet());
                mAdbKeyUser.saveKeys(mKeyMap.keySet());
            }
            return keysDeleted;
        }
@@ -1880,9 +1853,7 @@ public class AdbDebuggingManager {
        public void deleteKeyStore() {
            mKeyMap.clear();
            mTrustedNetworks.clear();
            if (mUserKeyFile != null) {
                mUserKeyFile.delete();
            }
            mAdbKeyUser.delete();
            if (mAtomicKeyFile == null) {
                return;
            }
+122 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.adb;

import android.annotation.NonNull;
import android.os.FileUtils;
import android.util.AtomicFile;
import android.util.Slog;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * This class handles the adbd plain-text keystore format. The keys are stored in two files:
 *
 * <ol>
 *   <li>/adb_keys: stores system keys that are permanently trusted, and can't be modified by user.
 *   <li>/data/misc/adb/adb_keys: stores user keys that are allowed to connect to the device, and
 *       can be modified by user.
 * </ol>
 */
class AdbdKeyStoreStorage {
    private static final String TAG = AdbdKeyStoreStorage.class.getSimpleName();

    private final File mTextFile;

    /**
     * Constructs a new storage manager for the given text file.
     *
     * @param textFile The file to read from and write to. Must not be null.
     */
    AdbdKeyStoreStorage(@NonNull File textFile) {
        Objects.requireNonNull(textFile, "textFile must not be null");
        this.mTextFile = textFile;
    }

    /**
     * Loads a set of ADB keys from the plain-text file.
     *
     * @return A {@link Set} of public key strings. Returns an empty set if the file does not exist
     *     or an error occurs.
     */
    Set<String> loadKeys() {
        Set<String> keys = new HashSet<>();
        if (!mTextFile.exists()) {
            return keys;
        }

        try (BufferedReader in = new BufferedReader(new FileReader(mTextFile))) {
            String key;
            while ((key = in.readLine()) != null) {
                key = key.trim();
                if (!key.isEmpty()) {
                    keys.add(key);
                }
            }
        } catch (IOException e) {
            Slog.e(TAG, "Caught an exception reading " + mTextFile, e);
        }
        return keys;
    }

    /**
     * Saves a set of ADB keys to the plain-text file, overwriting existing content.
     *
     * @param keys The {@link Set} of public key strings to save.
     */
    void saveKeys(@NonNull Set<String> keys) {
        Objects.requireNonNull(keys, "keys set must not be null");

        AtomicFile atomicKeyFile = new AtomicFile(mTextFile);
        // 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;
        try {
            fo = atomicKeyFile.startWrite();
            for (String key : keys) {
                fo.write(key.getBytes());
                fo.write('\n');
            }
            atomicKeyFile.finishWrite(fo);
        } catch (IOException ex) {
            Slog.e(TAG, "Error writing keys to " + atomicKeyFile.getBaseFile(), ex);
            if (fo != null) {
                atomicKeyFile.failWrite(fo);
            }
            return;
        }

        FileUtils.setPermissions(
                atomicKeyFile.getBaseFile().toString(),
                FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP,
                -1,
                -1);
    }

    /** Deletes the underlying text key file. */
    void delete() {
        if (mTextFile.exists()) {
            mTextFile.delete();
        }
    }
}