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
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 0ab2588..e4a8407 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -318,6 +318,14 @@
}
+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 {
diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java
index 4fabc0b..1a20a12 100644
--- a/wifi/java/src/android/net/wifi/WifiMigration.java
+++ b/wifi/java/src/android/net/wifi/WifiMigration.java
@@ -19,16 +19,23 @@
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.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 @@
*/
@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 @@
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);
+ }
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
new file mode 100644
index 0000000..8a5912f
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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);
+ }
+}
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index c5bc039..c1effe1 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -17,3 +17,11 @@
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"
+}