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

Commit 34388cff authored by Gabriel Biren's avatar Gabriel Biren
Browse files

Add Keystore migration method to WifiMigration.

Bug: 332560152
Flag: android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration
Test: atest WifiMigrationTest
Change-Id: I2ee61f765c8a1922563fa1242d6ce838a0a22863
parent 2b6e889b
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"
}