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

Commit fe383b7d authored by Sherif Eid's avatar Sherif Eid
Browse files

Move xml key store logic to AdbAuthorizationStore

AdbAuthorizationStore handles logic to read/save/delete keys that are
saved in the xml file.

Test: AdbDebuggingManagerTest
Bug: 420613813
Flag: EXEMPT refactor
Change-Id: I376bfb0ec7a7906b178d86979ae66382f4a914f9
parent 7697ea30
Loading
Loading
Loading
Loading
+234 −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.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;

import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Persists ADB authorization data to an XML file.
 *
 * <p>This class manages the storage of data such as:
 *
 * <ul>
 *   <li>A mapping of trusted ADB public keys to their last connection timestamp.
 *   <li>A set of trusted Wi-Fi access points (APs).
 * </ul>
 *
 * It is responsible for all I/O and XML serialization/deserialization logic, separating the
 * persistence mechanism from the core business logic in AdbKeyStore.
 */
class AdbAuthorizationStore {
    private static final String TAG = AdbAuthorizationStore.class.getSimpleName();

    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";
    private static final String XML_ATTRIBUTE_VERSION = "version";
    private static final String XML_TAG_ADB_KEY = "adbKey";
    private static final String XML_ATTRIBUTE_KEY = "key";
    private static final String XML_ATTRIBUTE_LAST_CONNECTION = "lastConnection";
    private static final String XML_TAG_WIFI_ACCESS_POINT = "wifiAP";
    private static final String XML_ATTRIBUTE_WIFI_BSSID = "bssid";

    private final File mStoreFile;

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

    /**
     * Loads the ADB key data from the XML file. If the file does not exist or an error occurs
     * during parsing, an empty Entries object is returned.
     *
     * @return A {@link Entries} object with the loaded data.
     */
    Entries load() {
        AtomicFile atomicFile = new AtomicFile(mStoreFile);
        if (!atomicFile.exists()) {
            return new Entries();
        }

        Map<String, Long> trustedKeys = new HashMap<>();
        List<String> trustedNetworks = new ArrayList<>();
        try (FileInputStream keyStream = atomicFile.openRead()) {
            TypedXmlPullParser parser;
            try {
                parser = Xml.resolvePullParser(keyStream);
                XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);

                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 new Entries();
                }
            } 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);
            }
            readKeyStoreContents(parser, trustedKeys, trustedNetworks);
        } catch (IOException e) {
            Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e);
            return new Entries();
        } catch (XmlPullParserException e) {
            Slog.e(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
            return new Entries();
        }
        return new Entries(trustedKeys, trustedNetworks);
    }

    /**
     * Saves the provided ADB key data to the XML file. If the data is empty, the file will be
     * deleted.
     *
     * @param data The {@link Entries} to persist.
     */
    void save(@NonNull Entries data) {
        Objects.requireNonNull(data, "Entries cannot be null");

        if (data.keys().isEmpty() && data.trustedNetworks().isEmpty()) {
            delete();
            return;
        }

        FileOutputStream keyStream = null;
        AtomicFile atomicFile = new AtomicFile(mStoreFile);
        try {
            keyStream = atomicFile.startWrite();
            TypedXmlSerializer serializer = Xml.resolveSerializer(keyStream);
            serializer.startDocument(null, true);

            serializer.startTag(null, XML_KEYSTORE_START_TAG);
            serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, KEYSTORE_VERSION);
            for (Map.Entry<String, Long> keyEntry : data.keys().entrySet()) {
                serializer.startTag(null, XML_TAG_ADB_KEY);
                serializer.attribute(null, XML_ATTRIBUTE_KEY, keyEntry.getKey());
                serializer.attributeLong(null, XML_ATTRIBUTE_LAST_CONNECTION, keyEntry.getValue());
                serializer.endTag(null, XML_TAG_ADB_KEY);
            }
            for (String bssid : data.trustedNetworks()) {
                serializer.startTag(null, XML_TAG_WIFI_ACCESS_POINT);
                serializer.attribute(null, XML_ATTRIBUTE_WIFI_BSSID, bssid);
                serializer.endTag(null, XML_TAG_WIFI_ACCESS_POINT);
            }
            serializer.endTag(null, XML_KEYSTORE_START_TAG);
            serializer.endDocument();
            atomicFile.finishWrite(keyStream);
        } catch (IOException e) {
            Slog.e(TAG, "Caught an exception writing the key map: ", e);
            atomicFile.failWrite(keyStream);
        }
    }

    /** Deletes the underlying XML key file. */
    void delete() {
        AtomicFile atomicFile = new AtomicFile(mStoreFile);
        if (atomicFile.exists()) {
            atomicFile.delete();
        }
    }

    private void readKeyStoreContents(
            TypedXmlPullParser parser, Map<String, Long> keyMap, List<String> trustedNetworks)
            throws XmlPullParserException, IOException {
        while ((parser.next()) != XmlPullParser.END_DOCUMENT) {
            // This parser is very forgiving. For backwards-compatibility, we simply iterate through
            // 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"
            // attribute) are simply ignored.
            String tagName = parser.getName();
            if (XML_TAG_ADB_KEY.equals(tagName)) {
                addAdbKeyToKeyMap(parser, keyMap);
            } else if (XML_TAG_WIFI_ACCESS_POINT.equals(tagName)) {
                addTrustedNetworkToTrustedNetworks(parser, trustedNetworks);
            } else {
                Slog.w(TAG, "Ignoring tag '" + tagName + "'. Not recognized.");
            }
            XmlUtils.skipCurrentTag(parser);
        }
    }

    private void addAdbKeyToKeyMap(TypedXmlPullParser parser, Map<String, Long> keyMap) {
        String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
        try {
            long connectionTime = parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CONNECTION);
            keyMap.put(key, connectionTime);
        } catch (XmlPullParserException e) {
            Slog.e(TAG, "Error reading adbKey attributes", e);
        }
    }

    private void addTrustedNetworkToTrustedNetworks(
            TypedXmlPullParser parser, List<String> trustedNetworks) {
        String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
        trustedNetworks.add(bssid);
    }

    /**
     * Represents the data model for the AdbAuthorizationStore.
     *
     * @param keys A map of public keys to the last connection time.
     * @param trustedNetworks A list of trusted WiFi networks BSSIDs.
     */
    record Entries(Map<String, Long> keys, List<String> trustedNetworks) {

        Entries() {
            this(new HashMap<>(), new ArrayList<>());
        }

        boolean isEmpty() {
            return keys.isEmpty() && trustedNetworks.isEmpty();
        }

        void clear() {
            keys.clear();
            trustedNetworks.clear();
        }
    }
}
+38 −183

File changed.

Preview size limit exceeded, changes collapsed.