Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +2 −1 Original line number Diff line number Diff line Loading @@ -409,7 +409,8 @@ public class LockSettingsService extends ILockSettings.Stub { } public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) { return new SyntheticPasswordManager(getContext(), storage, getUserManager()); return new SyntheticPasswordManager(getContext(), storage, getUserManager(), new PasswordSlotManager()); } public boolean hasBiometrics() { Loading services/core/java/com/android/server/locksettings/PasswordSlotManager.java 0 → 100644 +191 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.locksettings; import android.os.SystemProperties; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; /** * A class that maintains a mapping of which password slots are used by alternate OS images when * dual-booting a device. Currently, slots can either be owned by the host OS or a live GSI. * This mapping is stored in /metadata/password_slots/slot_map using Java Properties. * * If a /metadata partition does not exist, GSIs are not supported, and PasswordSlotManager will * simply not persist the slot mapping. */ public class PasswordSlotManager { private static final String TAG = "PasswordSlotManager"; private static final String GSI_RUNNING_PROP = "ro.gsid.image_running"; private static final String SLOT_MAP_DIR = "/metadata/password_slots"; // This maps each used password slot to the OS image that created it. Password slots are // integer keys/indices into secure storage. The OS image is recorded as a string. The factory // image is "host" and GSIs are "gsi<N>" where N >= 1. private final Map<Integer, String> mSlotMap; public PasswordSlotManager() { mSlotMap = loadSlotMap(); } @VisibleForTesting protected String getSlotMapDir() { return SLOT_MAP_DIR; } @VisibleForTesting protected int getGsiImageNumber() { return SystemProperties.getInt(GSI_RUNNING_PROP, 0); } /** * Notify the manager of which slots are definitively in use by the current OS image. * * @throws RuntimeException */ public void refreshActiveSlots(Set<Integer> activeSlots) throws RuntimeException { // Update which slots are owned by the current image. final HashSet<Integer> slotsToDelete = new HashSet<Integer>(); for (Map.Entry<Integer, String> entry : mSlotMap.entrySet()) { // Delete possibly stale entries for the current image. if (entry.getValue().equals(getMode())) { slotsToDelete.add(entry.getKey()); } } for (Integer slot : slotsToDelete) { mSlotMap.remove(slot); } // Add slots for the current image. for (Integer slot : activeSlots) { mSlotMap.put(slot, getMode()); } saveSlotMap(); } /** * Mark the given slot as in use by the current OS image. * * @throws RuntimeException */ public void markSlotInUse(int slot) throws RuntimeException { if (mSlotMap.containsKey(slot) && !mSlotMap.get(slot).equals(getMode())) { throw new RuntimeException("password slot " + slot + " is not available"); } mSlotMap.put(slot, getMode()); saveSlotMap(); } /** * Mark the given slot as no longer in use by the current OS image. * * @throws RuntimeException */ public void markSlotDeleted(int slot) throws RuntimeException { if (mSlotMap.containsKey(slot) && mSlotMap.get(slot) != getMode()) { throw new RuntimeException("password slot " + slot + " cannot be deleted"); } mSlotMap.remove(slot); saveSlotMap(); } /** * Return the set of slots used across all OS images. * * @return Integer set of all used slots. */ public Set<Integer> getUsedSlots() { return Collections.unmodifiableSet(mSlotMap.keySet()); } private File getSlotMapFile() { return Paths.get(getSlotMapDir(), "slot_map").toFile(); } private String getMode() { int gsiIndex = getGsiImageNumber(); if (gsiIndex > 0) { return "gsi" + gsiIndex; } return "host"; } @VisibleForTesting protected Map<Integer, String> loadSlotMap(InputStream stream) throws IOException { final HashMap<Integer, String> map = new HashMap<Integer, String>(); final Properties props = new Properties(); props.load(stream); for (String slotString : props.stringPropertyNames()) { final int slot = Integer.parseInt(slotString); final String owner = props.getProperty(slotString); map.put(slot, owner); } return map; } private Map<Integer, String> loadSlotMap() { // It's okay if the file doesn't exist. final File file = getSlotMapFile(); if (file.exists()) { try (FileInputStream stream = new FileInputStream(file)) { return loadSlotMap(stream); } catch (Exception e) { Slog.e(TAG, "Could not load slot map file", e); } } return new HashMap<Integer, String>(); } @VisibleForTesting protected void saveSlotMap(OutputStream stream) throws IOException { final Properties props = new Properties(); for (Map.Entry<Integer, String> entry : mSlotMap.entrySet()) { props.setProperty(entry.getKey().toString(), entry.getValue()); } props.store(stream, ""); } private void saveSlotMap() { if (!getSlotMapFile().getParentFile().exists()) { Slog.w(TAG, "Not saving slot map, " + getSlotMapDir() + " does not exist"); return; } try (FileOutputStream fos = new FileOutputStream(getSlotMapFile())) { saveSlotMap(fos); } catch (IOException e) { Slog.e(TAG, "failed to save password slot map", e); } } } services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +8 −1 Original line number Diff line number Diff line Loading @@ -287,14 +287,16 @@ public class SyntheticPasswordManager { private LockSettingsStorage mStorage; private IWeaver mWeaver; private WeaverConfig mWeaverConfig; private PasswordSlotManager mPasswordSlotManager; private final UserManager mUserManager; public SyntheticPasswordManager(Context context, LockSettingsStorage storage, UserManager userManager) { UserManager userManager, PasswordSlotManager passwordSlotManager) { mContext = context; mStorage = storage; mUserManager = userManager; mPasswordSlotManager = passwordSlotManager; } @VisibleForTesting Loading Loading @@ -324,6 +326,7 @@ public class SyntheticPasswordManager { mWeaver = null; } }); mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots()); } } catch (RemoteException e) { Slog.e(TAG, "Failed to get weaver service", e); Loading Loading @@ -561,6 +564,7 @@ public class SyntheticPasswordManager { Log.i(TAG, "Destroy weaver slot " + slot + " for user " + userId); try { weaverEnroll(slot, null, null); mPasswordSlotManager.markSlotDeleted(slot); } catch (RemoteException e) { Log.w(TAG, "Failed to destroy slot", e); } Loading Loading @@ -595,6 +599,7 @@ public class SyntheticPasswordManager { private int getNextAvailableWeaverSlot() { Set<Integer> usedSlots = getUsedWeaverSlots(); usedSlots.addAll(mPasswordSlotManager.getUsedSlots()); for (int i = 0; i < mWeaverConfig.slots; i++) { if (!usedSlots.contains(i)) { return i; Loading Loading @@ -640,6 +645,7 @@ public class SyntheticPasswordManager { return DEFAULT_HANDLE; } saveWeaverSlot(weaverSlot, handle, userId); mPasswordSlotManager.markSlotInUse(weaverSlot); synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot); pwd.passwordHandle = null; Loading Loading @@ -798,6 +804,7 @@ public class SyntheticPasswordManager { return false; } saveWeaverSlot(slot, handle, userId); mPasswordSlotManager.markSlotInUse(slot); } saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId); createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken, Loading services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +5 −1 Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { IAuthSecret mAuthSecretService; WindowManagerInternal mMockWindowManager; FakeGsiService mGsiService; PasswordSlotManagerTestable mPasswordSlotManager; protected boolean mHasSecureLockScreen; @Override Loading @@ -103,6 +104,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class); mMockWindowManager = mock(WindowManagerInternal.class); mGsiService = new FakeGsiService(); mPasswordSlotManager = new PasswordSlotManagerTestable(); LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); Loading Loading @@ -135,7 +137,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { } }; mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager); mUserManager, mPasswordSlotManager); mAuthSecretService = mock(IAuthSecret.class); mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage, mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager, Loading Loading @@ -223,6 +225,8 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { File storageDir = mStorage.mStorageDir; assertTrue(FileUtils.deleteContents(storageDir)); mPasswordSlotManager.cleanup(); } protected void assertNotEquals(long expected, long actual) { Loading services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java +4 −3 Original line number Diff line number Diff line Loading @@ -35,10 +35,12 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager { private FakeGateKeeperService mGateKeeper; private IWeaver mWeaverService; private PasswordSlotManagerTestable mPasswordSlotManager; public MockSyntheticPasswordManager(Context context, LockSettingsStorage storage, FakeGateKeeperService gatekeeper, UserManager userManager) { super(context, storage, userManager); FakeGateKeeperService gatekeeper, UserManager userManager, PasswordSlotManager passwordSlotManager) { super(context, storage, userManager, passwordSlotManager); mGateKeeper = gatekeeper; } Loading Loading @@ -117,5 +119,4 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager { mWeaverService = new MockWeaverService(); initWeaverService(); } } Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +2 −1 Original line number Diff line number Diff line Loading @@ -409,7 +409,8 @@ public class LockSettingsService extends ILockSettings.Stub { } public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) { return new SyntheticPasswordManager(getContext(), storage, getUserManager()); return new SyntheticPasswordManager(getContext(), storage, getUserManager(), new PasswordSlotManager()); } public boolean hasBiometrics() { Loading
services/core/java/com/android/server/locksettings/PasswordSlotManager.java 0 → 100644 +191 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.locksettings; import android.os.SystemProperties; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; /** * A class that maintains a mapping of which password slots are used by alternate OS images when * dual-booting a device. Currently, slots can either be owned by the host OS or a live GSI. * This mapping is stored in /metadata/password_slots/slot_map using Java Properties. * * If a /metadata partition does not exist, GSIs are not supported, and PasswordSlotManager will * simply not persist the slot mapping. */ public class PasswordSlotManager { private static final String TAG = "PasswordSlotManager"; private static final String GSI_RUNNING_PROP = "ro.gsid.image_running"; private static final String SLOT_MAP_DIR = "/metadata/password_slots"; // This maps each used password slot to the OS image that created it. Password slots are // integer keys/indices into secure storage. The OS image is recorded as a string. The factory // image is "host" and GSIs are "gsi<N>" where N >= 1. private final Map<Integer, String> mSlotMap; public PasswordSlotManager() { mSlotMap = loadSlotMap(); } @VisibleForTesting protected String getSlotMapDir() { return SLOT_MAP_DIR; } @VisibleForTesting protected int getGsiImageNumber() { return SystemProperties.getInt(GSI_RUNNING_PROP, 0); } /** * Notify the manager of which slots are definitively in use by the current OS image. * * @throws RuntimeException */ public void refreshActiveSlots(Set<Integer> activeSlots) throws RuntimeException { // Update which slots are owned by the current image. final HashSet<Integer> slotsToDelete = new HashSet<Integer>(); for (Map.Entry<Integer, String> entry : mSlotMap.entrySet()) { // Delete possibly stale entries for the current image. if (entry.getValue().equals(getMode())) { slotsToDelete.add(entry.getKey()); } } for (Integer slot : slotsToDelete) { mSlotMap.remove(slot); } // Add slots for the current image. for (Integer slot : activeSlots) { mSlotMap.put(slot, getMode()); } saveSlotMap(); } /** * Mark the given slot as in use by the current OS image. * * @throws RuntimeException */ public void markSlotInUse(int slot) throws RuntimeException { if (mSlotMap.containsKey(slot) && !mSlotMap.get(slot).equals(getMode())) { throw new RuntimeException("password slot " + slot + " is not available"); } mSlotMap.put(slot, getMode()); saveSlotMap(); } /** * Mark the given slot as no longer in use by the current OS image. * * @throws RuntimeException */ public void markSlotDeleted(int slot) throws RuntimeException { if (mSlotMap.containsKey(slot) && mSlotMap.get(slot) != getMode()) { throw new RuntimeException("password slot " + slot + " cannot be deleted"); } mSlotMap.remove(slot); saveSlotMap(); } /** * Return the set of slots used across all OS images. * * @return Integer set of all used slots. */ public Set<Integer> getUsedSlots() { return Collections.unmodifiableSet(mSlotMap.keySet()); } private File getSlotMapFile() { return Paths.get(getSlotMapDir(), "slot_map").toFile(); } private String getMode() { int gsiIndex = getGsiImageNumber(); if (gsiIndex > 0) { return "gsi" + gsiIndex; } return "host"; } @VisibleForTesting protected Map<Integer, String> loadSlotMap(InputStream stream) throws IOException { final HashMap<Integer, String> map = new HashMap<Integer, String>(); final Properties props = new Properties(); props.load(stream); for (String slotString : props.stringPropertyNames()) { final int slot = Integer.parseInt(slotString); final String owner = props.getProperty(slotString); map.put(slot, owner); } return map; } private Map<Integer, String> loadSlotMap() { // It's okay if the file doesn't exist. final File file = getSlotMapFile(); if (file.exists()) { try (FileInputStream stream = new FileInputStream(file)) { return loadSlotMap(stream); } catch (Exception e) { Slog.e(TAG, "Could not load slot map file", e); } } return new HashMap<Integer, String>(); } @VisibleForTesting protected void saveSlotMap(OutputStream stream) throws IOException { final Properties props = new Properties(); for (Map.Entry<Integer, String> entry : mSlotMap.entrySet()) { props.setProperty(entry.getKey().toString(), entry.getValue()); } props.store(stream, ""); } private void saveSlotMap() { if (!getSlotMapFile().getParentFile().exists()) { Slog.w(TAG, "Not saving slot map, " + getSlotMapDir() + " does not exist"); return; } try (FileOutputStream fos = new FileOutputStream(getSlotMapFile())) { saveSlotMap(fos); } catch (IOException e) { Slog.e(TAG, "failed to save password slot map", e); } } }
services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +8 −1 Original line number Diff line number Diff line Loading @@ -287,14 +287,16 @@ public class SyntheticPasswordManager { private LockSettingsStorage mStorage; private IWeaver mWeaver; private WeaverConfig mWeaverConfig; private PasswordSlotManager mPasswordSlotManager; private final UserManager mUserManager; public SyntheticPasswordManager(Context context, LockSettingsStorage storage, UserManager userManager) { UserManager userManager, PasswordSlotManager passwordSlotManager) { mContext = context; mStorage = storage; mUserManager = userManager; mPasswordSlotManager = passwordSlotManager; } @VisibleForTesting Loading Loading @@ -324,6 +326,7 @@ public class SyntheticPasswordManager { mWeaver = null; } }); mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots()); } } catch (RemoteException e) { Slog.e(TAG, "Failed to get weaver service", e); Loading Loading @@ -561,6 +564,7 @@ public class SyntheticPasswordManager { Log.i(TAG, "Destroy weaver slot " + slot + " for user " + userId); try { weaverEnroll(slot, null, null); mPasswordSlotManager.markSlotDeleted(slot); } catch (RemoteException e) { Log.w(TAG, "Failed to destroy slot", e); } Loading Loading @@ -595,6 +599,7 @@ public class SyntheticPasswordManager { private int getNextAvailableWeaverSlot() { Set<Integer> usedSlots = getUsedWeaverSlots(); usedSlots.addAll(mPasswordSlotManager.getUsedSlots()); for (int i = 0; i < mWeaverConfig.slots; i++) { if (!usedSlots.contains(i)) { return i; Loading Loading @@ -640,6 +645,7 @@ public class SyntheticPasswordManager { return DEFAULT_HANDLE; } saveWeaverSlot(weaverSlot, handle, userId); mPasswordSlotManager.markSlotInUse(weaverSlot); synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot); pwd.passwordHandle = null; Loading Loading @@ -798,6 +804,7 @@ public class SyntheticPasswordManager { return false; } saveWeaverSlot(slot, handle, userId); mPasswordSlotManager.markSlotInUse(slot); } saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId); createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken, Loading
services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +5 −1 Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { IAuthSecret mAuthSecretService; WindowManagerInternal mMockWindowManager; FakeGsiService mGsiService; PasswordSlotManagerTestable mPasswordSlotManager; protected boolean mHasSecureLockScreen; @Override Loading @@ -103,6 +104,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class); mMockWindowManager = mock(WindowManagerInternal.class); mGsiService = new FakeGsiService(); mPasswordSlotManager = new PasswordSlotManagerTestable(); LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); Loading Loading @@ -135,7 +137,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { } }; mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager); mUserManager, mPasswordSlotManager); mAuthSecretService = mock(IAuthSecret.class); mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage, mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager, Loading Loading @@ -223,6 +225,8 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { File storageDir = mStorage.mStorageDir; assertTrue(FileUtils.deleteContents(storageDir)); mPasswordSlotManager.cleanup(); } protected void assertNotEquals(long expected, long actual) { Loading
services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java +4 −3 Original line number Diff line number Diff line Loading @@ -35,10 +35,12 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager { private FakeGateKeeperService mGateKeeper; private IWeaver mWeaverService; private PasswordSlotManagerTestable mPasswordSlotManager; public MockSyntheticPasswordManager(Context context, LockSettingsStorage storage, FakeGateKeeperService gatekeeper, UserManager userManager) { super(context, storage, userManager); FakeGateKeeperService gatekeeper, UserManager userManager, PasswordSlotManager passwordSlotManager) { super(context, storage, userManager, passwordSlotManager); mGateKeeper = gatekeeper; } Loading Loading @@ -117,5 +119,4 @@ public class MockSyntheticPasswordManager extends SyntheticPasswordManager { mWeaverService = new MockWeaverService(); initWeaverService(); } }