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

Commit 0ea38e6b authored by Gabriel Biren's avatar Gabriel Biren Committed by Android (Google) Code Review
Browse files

Merge "Add Keystore migration method to WifiMigration." into main

parents 929f8cad 34388cff
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -318,6 +318,14 @@ package android.net.netstats {

}

package android.net.wifi {

  public final class WifiMigration {
    method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration") public static void migrateLegacyKeystoreToWifiBlobstore();
  }

}

package android.nfc {

  public class NfcServiceManager {
+58 −0
Original line number Diff line number Diff line
@@ -19,16 +19,23 @@ package android.net.wifi;
import static android.os.Environment.getDataMiscCeDirectory;
import static android.os.Environment.getDataMiscDirectory;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.net.wifi.flags.Flags;
import android.os.Binder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.provider.Settings;
import android.security.legacykeystore.ILegacyKeystore;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseArray;

import java.io.File;
@@ -36,7 +43,11 @@ import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * Class used to provide one time hooks for existing OEM devices to migrate their config store
@@ -45,6 +56,8 @@ import java.util.Objects;
 */
@SystemApi
public final class WifiMigration {
    private static final String TAG = "WifiMigration";

    /**
     * Directory to read the wifi config store files from under.
     */
@@ -555,4 +568,49 @@ public final class WifiMigration {
        return data;

    }

    /**
     * Migrate any certificates in Legacy Keystore to the newer WifiBlobstore database.
     *
     * @hide
     */
    @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION)
    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
    public static void migrateLegacyKeystoreToWifiBlobstore() {
        final long identity = Binder.clearCallingIdentity();
        try {
            ILegacyKeystore legacyKeystore = WifiBlobStore.getLegacyKeystore();
            String[] legacyAliases = legacyKeystore.list("", Process.WIFI_UID);
            if (legacyAliases == null || legacyAliases.length == 0) {
                Log.i(TAG, "No aliases need to be migrated");
                return;
            }

            WifiBlobStore wifiBlobStore = WifiBlobStore.getInstance();
            List<String> blobstoreAliasList = Arrays.asList(wifiBlobStore.list(""));
            Set<String> blobstoreAliases = new HashSet<>();
            blobstoreAliases.addAll(blobstoreAliasList);

            for (String legacyAlias : legacyAliases) {
                // Only migrate if the alias is not already in WifiBlobstore,
                // since WifiBlobstore should already contain the latest value.
                if (!blobstoreAliases.contains(legacyAlias)) {
                    byte[] value = legacyKeystore.get(legacyAlias, Process.WIFI_UID);
                    wifiBlobStore.put(legacyAlias, value);
                }
                legacyKeystore.remove(legacyAlias, Process.WIFI_UID);
            }
            Log.i(TAG, "Successfully migrated aliases from Legacy Keystore");
        } catch (ServiceSpecificException e) {
            if (e.errorCode == ILegacyKeystore.ERROR_SYSTEM_ERROR) {
                Log.i(TAG, "Legacy Keystore service has been deprecated");
            } else {
                Log.e(TAG, "Encountered an exception while migrating aliases. " + e);
            }
        } catch (Exception e) {
            Log.e(TAG, "Encountered an exception while migrating aliases. " + e);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }
}
+118 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.net.wifi;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;

import android.security.legacykeystore.ILegacyKeystore;

import com.android.dx.mockito.inline.extended.ExtendedMockito;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;

/**
 * Unit tests for {@link WifiMigration}.
 */
public class WifiMigrationTest {
    public static final String TEST_ALIAS = "someAliasString";
    public static final byte[] TEST_VALUE = new byte[]{10, 11, 12};

    @Mock private ILegacyKeystore mLegacyKeystore;
    @Mock private WifiBlobStore mWifiBlobStore;

    private MockitoSession mSession;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mSession = ExtendedMockito.mockitoSession()
                .mockStatic(WifiBlobStore.class, withSettings().lenient())
                .startMocking();
        when(WifiBlobStore.getLegacyKeystore()).thenReturn(mLegacyKeystore);
        when(WifiBlobStore.getInstance()).thenReturn(mWifiBlobStore);
        when(mLegacyKeystore.get(anyString(), anyInt())).thenReturn(TEST_VALUE);
    }

    @After
    public void cleanup() {
        validateMockitoUsage();
        if (mSession != null) {
            mSession.finishMocking();
        }
    }

    /**
     * Verify that the Keystore migration method returns immediately if no aliases
     * are found in Legacy Keystore.
     */
    @Test
    public void testKeystoreMigrationNoLegacyAliases() throws Exception {
        when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(new String[0]);
        WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
        verify(mLegacyKeystore).list(anyString(), anyInt());
        verifyNoMoreInteractions(mLegacyKeystore, mWifiBlobStore);
    }

    /**
     * Verify that if all aliases in Legacy Keystore are unique to that database,
     * all aliases are migrated to WifiBlobstore.
     */
    @Test
    public void testKeystoreMigrationUniqueLegacyAliases() throws Exception {
        String[] legacyAliases = new String[]{TEST_ALIAS + "1", TEST_ALIAS + "2"};
        String[] blobstoreAliases = new String[0];
        when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(legacyAliases);
        when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases);

        WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
        verify(mWifiBlobStore, times(legacyAliases.length)).put(anyString(), any(byte[].class));
    }

    /**
     * Verify that if some aliases are shared between Legacy Keystore and WifiBlobstore,
     * only the ones unique to Legacy Keystore are migrated.
     */
    @Test
    public void testKeystoreMigrationDuplicateLegacyAliases() throws Exception {
        String uniqueLegacyAlias = TEST_ALIAS + "1";
        String[] blobstoreAliases = new String[]{TEST_ALIAS + "2", TEST_ALIAS + "3"};
        String[] legacyAliases =
                new String[]{blobstoreAliases[0], blobstoreAliases[1], uniqueLegacyAlias};
        when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(legacyAliases);
        when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases);

        // Expect that only the unique legacy alias is migrated to the blobstore
        WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
        verify(mWifiBlobStore).list(anyString());
        verify(mWifiBlobStore).put(eq(uniqueLegacyAlias), any(byte[].class));
        verifyNoMoreInteractions(mWifiBlobStore);
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -17,3 +17,11 @@ flag {
    description: "Control the API that allows setting / reading the NetworkProviderInfo's battery charging status"
    bug: "305067231"
}

flag {
    name: "legacy_keystore_to_wifi_blobstore_migration"
    is_exported: true
    namespace: "wifi"
    description: "Add API to migrate all values from Legacy Keystore to the new Wifi Blobstore database"
    bug: "332560152"
}