Add weak escrow token APIs
Bug: 206485383 Test: atest LockPatternUtilsTest WeakEscrowTokenTests Change-Id: I24cdf9af6c8f29db03ef214a1a0f8effad394264
This commit is contained in:
@@ -176,6 +176,7 @@ package android {
|
||||
field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
|
||||
field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
|
||||
field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
|
||||
field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
|
||||
field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
|
||||
field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
|
||||
field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED";
|
||||
@@ -757,18 +758,32 @@ package android.app {
|
||||
}
|
||||
|
||||
public class KeyguardManager {
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public long addWeakEscrowToken(@NonNull byte[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenActivatedListener);
|
||||
method public android.content.Intent createConfirmFactoryResetCredentialIntent(CharSequence, CharSequence, CharSequence);
|
||||
method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public int getMinLockLength(boolean, int);
|
||||
method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public boolean getPrivateNotificationsAllowed();
|
||||
method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean isValidLockPasswordComplexity(int, @NonNull byte[], int);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenActive(long, @NonNull android.os.UserHandle);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenValid(long, @NonNull byte[], @NonNull android.os.UserHandle);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean registerWeakEscrowTokenRemovedListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean removeWeakEscrowToken(long, @NonNull android.os.UserHandle);
|
||||
method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback);
|
||||
method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int);
|
||||
method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean unregisterWeakEscrowTokenRemovedListener(@NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
|
||||
field public static final int PASSWORD = 0; // 0x0
|
||||
field public static final int PATTERN = 2; // 0x2
|
||||
field public static final int PIN = 1; // 0x1
|
||||
}
|
||||
|
||||
public static interface KeyguardManager.WeakEscrowTokenActivatedListener {
|
||||
method public void onWeakEscrowTokenActivated(long, @NonNull android.os.UserHandle);
|
||||
}
|
||||
|
||||
public static interface KeyguardManager.WeakEscrowTokenRemovedListener {
|
||||
method public void onWeakEscrowTokenRemoved(long, @NonNull android.os.UserHandle);
|
||||
}
|
||||
|
||||
public class LocaleManager {
|
||||
method @NonNull @RequiresPermission(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES) public android.os.LocaleList getApplicationLocales(@NonNull String);
|
||||
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void setApplicationLocales(@NonNull String, @NonNull android.os.LocaleList);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package android.app;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
@@ -40,8 +41,10 @@ import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceManager.ServiceNotFoundException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.service.persistentdata.IPersistentDataBlockService;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.view.IOnKeyguardExitResult;
|
||||
import android.view.IWindowManager;
|
||||
@@ -49,6 +52,9 @@ import android.view.WindowManager.LayoutParams;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import com.android.internal.policy.IKeyguardDismissCallback;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
|
||||
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockPatternView;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
@@ -57,6 +63,8 @@ import com.android.internal.widget.VerifyCredentialResponse;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Class that can be used to lock and unlock the keyguard. The
|
||||
@@ -69,10 +77,13 @@ public class KeyguardManager {
|
||||
private static final String TAG = "KeyguardManager";
|
||||
|
||||
private final Context mContext;
|
||||
private final LockPatternUtils mLockPatternUtils;
|
||||
private final IWindowManager mWM;
|
||||
private final IActivityManager mAm;
|
||||
private final ITrustManager mTrustManager;
|
||||
private final INotificationManager mNotificationManager;
|
||||
private final ArrayMap<WeakEscrowTokenRemovedListener, IWeakEscrowTokenRemovedListener>
|
||||
mListeners = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Intent used to prompt user for device credentials.
|
||||
@@ -455,8 +466,42 @@ public class KeyguardManager {
|
||||
public void onDismissCancelled() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback passed to
|
||||
* {@link KeyguardManager#addWeakEscrowToken}
|
||||
* to notify caller of state change.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public interface WeakEscrowTokenActivatedListener {
|
||||
/**
|
||||
* The method to be called when the token is activated.
|
||||
* @param handle 64 bit handle corresponding to the escrow token
|
||||
* @param user user for whom the weak escrow token has been added
|
||||
*/
|
||||
void onWeakEscrowTokenActivated(long handle, @NonNull UserHandle user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener passed to
|
||||
* {@link KeyguardManager#registerWeakEscrowTokenRemovedListener} and
|
||||
* {@link KeyguardManager#unregisterWeakEscrowTokenRemovedListener}
|
||||
* to notify caller of an weak escrow token has been removed.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public interface WeakEscrowTokenRemovedListener {
|
||||
/**
|
||||
* The method to be called when the token is removed.
|
||||
* @param handle 64 bit handle corresponding to the escrow token
|
||||
* @param user user for whom the escrow token has been added
|
||||
*/
|
||||
void onWeakEscrowTokenRemoved(long handle, @NonNull UserHandle user);
|
||||
}
|
||||
|
||||
KeyguardManager(Context context) throws ServiceNotFoundException {
|
||||
mContext = context;
|
||||
mLockPatternUtils = new LockPatternUtils(context);
|
||||
mWM = WindowManagerGlobal.getWindowManagerService();
|
||||
mAm = ActivityManager.getService();
|
||||
mTrustManager = ITrustManager.Stub.asInterface(
|
||||
@@ -785,7 +830,6 @@ public class KeyguardManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
|
||||
int userId = mContext.getUserId();
|
||||
if (isDeviceSecure(userId)) {
|
||||
Log.e(TAG, "Password already set, rejecting call to setLock");
|
||||
@@ -799,7 +843,7 @@ public class KeyguardManager {
|
||||
try {
|
||||
LockscreenCredential credential = createLockscreenCredential(
|
||||
lockType, password);
|
||||
success = lockPatternUtils.setLockCredential(
|
||||
success = mLockPatternUtils.setLockCredential(
|
||||
credential,
|
||||
/* savedPassword= */ LockscreenCredential.createNone(),
|
||||
userId);
|
||||
@@ -812,6 +856,150 @@ public class KeyguardManager {
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a weak escrow token for the current user, which can later be used to unlock FBE
|
||||
* or change user password.
|
||||
*
|
||||
* After adding, if the user currently has a secure lockscreen, they will need to perform a
|
||||
* confirm credential operation in order to activate the token for future use. If the user
|
||||
* has no secure lockscreen, then the token is activated immediately.
|
||||
*
|
||||
* If the user changes or removes the lockscreen password, any activated weak escrow token will
|
||||
* be removed.
|
||||
*
|
||||
* @return a unique 64-bit token handle which is needed to refer to this token later.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
@RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
|
||||
@SystemApi
|
||||
public long addWeakEscrowToken(@NonNull byte[] token, @NonNull UserHandle user,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull WeakEscrowTokenActivatedListener listener) {
|
||||
Objects.requireNonNull(token, "Token cannot be null.");
|
||||
Objects.requireNonNull(user, "User cannot be null.");
|
||||
Objects.requireNonNull(executor, "Executor cannot be null.");
|
||||
Objects.requireNonNull(listener, "Listener cannot be null.");
|
||||
int userId = user.getIdentifier();
|
||||
IWeakEscrowTokenActivatedListener internalListener =
|
||||
new IWeakEscrowTokenActivatedListener.Stub() {
|
||||
@Override
|
||||
public void onWeakEscrowTokenActivated(long handle, int userId) {
|
||||
UserHandle user = UserHandle.of(userId);
|
||||
final long restoreToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
executor.execute(() -> listener.onWeakEscrowTokenActivated(handle, user));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(restoreToken);
|
||||
}
|
||||
Log.i(TAG, "Weak escrow token activated.");
|
||||
}
|
||||
};
|
||||
return mLockPatternUtils.addWeakEscrowToken(token, userId, internalListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a weak escrow token.
|
||||
*
|
||||
* @return true if the given handle refers to a valid weak token previously returned from
|
||||
* {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
@RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
|
||||
@SystemApi
|
||||
public boolean removeWeakEscrowToken(long handle, @NonNull UserHandle user) {
|
||||
Objects.requireNonNull(user, "User cannot be null.");
|
||||
return mLockPatternUtils.removeWeakEscrowToken(handle, user.getIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given weak escrow token is active or not.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
@RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
|
||||
@SystemApi
|
||||
public boolean isWeakEscrowTokenActive(long handle, @NonNull UserHandle user) {
|
||||
Objects.requireNonNull(user, "User cannot be null.");
|
||||
return mLockPatternUtils.isWeakEscrowTokenActive(handle, user.getIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given weak escrow token is validate.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
@RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
|
||||
@SystemApi
|
||||
public boolean isWeakEscrowTokenValid(long handle, @NonNull byte[] token,
|
||||
@NonNull UserHandle user) {
|
||||
Objects.requireNonNull(token, "Token cannot be null.");
|
||||
Objects.requireNonNull(user, "User cannot be null.");
|
||||
return mLockPatternUtils.isWeakEscrowTokenValid(handle, token, user.getIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given WeakEscrowTokenRemovedListener.
|
||||
*
|
||||
* @return true if the listener is registered successfully, return false otherwise.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
@RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
|
||||
@SystemApi
|
||||
public boolean registerWeakEscrowTokenRemovedListener(
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull WeakEscrowTokenRemovedListener listener) {
|
||||
Objects.requireNonNull(listener, "Listener cannot be null.");
|
||||
Objects.requireNonNull(executor, "Executor cannot be null.");
|
||||
Preconditions.checkArgument(!mListeners.containsKey(listener),
|
||||
"Listener already registered: %s", listener);
|
||||
IWeakEscrowTokenRemovedListener internalListener =
|
||||
new IWeakEscrowTokenRemovedListener.Stub() {
|
||||
@Override
|
||||
public void onWeakEscrowTokenRemoved(long handle, int userId) {
|
||||
UserHandle user = UserHandle.of(userId);
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
executor.execute(() -> listener.onWeakEscrowTokenRemoved(handle, user));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (mLockPatternUtils.registerWeakEscrowTokenRemovedListener(internalListener)) {
|
||||
mListeners.put(listener, internalListener);
|
||||
return true;
|
||||
} else {
|
||||
Log.e(TAG, "Listener failed to register");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the given WeakEscrowTokenRemovedListener.
|
||||
*
|
||||
* @return true if the listener is unregistered successfully, return false otherwise.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
@RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
|
||||
@SystemApi
|
||||
public boolean unregisterWeakEscrowTokenRemovedListener(
|
||||
@NonNull WeakEscrowTokenRemovedListener listener) {
|
||||
Objects.requireNonNull(listener, "Listener cannot be null.");
|
||||
IWeakEscrowTokenRemovedListener internalListener = mListeners.get(listener);
|
||||
Preconditions.checkArgument(internalListener != null, "Listener was not registered");
|
||||
if (mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(internalListener)) {
|
||||
mListeners.remove(listener);
|
||||
return true;
|
||||
} else {
|
||||
Log.e(TAG, "Listener failed to unregister.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the lockscreen password to {@code newPassword} after validating the current password
|
||||
* against {@code currentPassword}.
|
||||
@@ -832,13 +1020,12 @@ public class KeyguardManager {
|
||||
})
|
||||
public boolean setLock(@LockTypes int newLockType, @Nullable byte[] newPassword,
|
||||
@LockTypes int currentLockType, @Nullable byte[] currentPassword) {
|
||||
final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
|
||||
final int userId = mContext.getUserId();
|
||||
LockscreenCredential currentCredential = createLockscreenCredential(
|
||||
currentLockType, currentPassword);
|
||||
LockscreenCredential newCredential = createLockscreenCredential(
|
||||
newLockType, newPassword);
|
||||
return lockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
|
||||
return mLockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -857,10 +1044,9 @@ public class KeyguardManager {
|
||||
Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE
|
||||
})
|
||||
public boolean checkLock(@LockTypes int lockType, @Nullable byte[] password) {
|
||||
final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
|
||||
final LockscreenCredential credential = createLockscreenCredential(
|
||||
lockType, password);
|
||||
final VerifyCredentialResponse response = lockPatternUtils.verifyCredential(
|
||||
final VerifyCredentialResponse response = mLockPatternUtils.verifyCredential(
|
||||
credential, mContext.getUserId(), /* flags= */ 0);
|
||||
if (response == null) {
|
||||
return false;
|
||||
|
||||
@@ -24,6 +24,8 @@ import android.security.keystore.recovery.KeyChainSnapshot;
|
||||
import android.security.keystore.recovery.KeyChainProtectionParams;
|
||||
import android.security.keystore.recovery.RecoveryCertPath;
|
||||
import com.android.internal.widget.ICheckCredentialProgressCallback;
|
||||
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
|
||||
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.internal.widget.VerifyCredentialResponse;
|
||||
|
||||
@@ -96,4 +98,10 @@ interface ILockSettings {
|
||||
boolean tryUnlockWithCachedUnifiedChallenge(int userId);
|
||||
void removeCachedUnifiedChallenge(int userId);
|
||||
void updateEncryptionPassword(int type, in byte[] password);
|
||||
boolean registerWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
|
||||
boolean unregisterWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
|
||||
long addWeakEscrowToken(in byte[] token, int userId, in IWeakEscrowTokenActivatedListener callback);
|
||||
boolean removeWeakEscrowToken(long handle, int userId);
|
||||
boolean isWeakEscrowTokenActive(long handle, int userId);
|
||||
boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.internal.widget;
|
||||
|
||||
/** @hide */
|
||||
oneway interface IWeakEscrowTokenActivatedListener {
|
||||
void onWeakEscrowTokenActivated(long handle, int userId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.internal.widget;
|
||||
|
||||
/** @hide */
|
||||
oneway interface IWeakEscrowTokenRemovedListener {
|
||||
void onWeakEscrowTokenRemoved(long handle, int userId);
|
||||
}
|
||||
@@ -1236,6 +1236,28 @@ public class LockPatternUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Register the given WeakEscrowTokenRemovedListener. */
|
||||
public boolean registerWeakEscrowTokenRemovedListener(
|
||||
@NonNull final IWeakEscrowTokenRemovedListener listener) {
|
||||
try {
|
||||
return getLockSettings().registerWeakEscrowTokenRemovedListener(listener);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener.");
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/** Unregister the given WeakEscrowTokenRemovedListener. */
|
||||
public boolean unregisterWeakEscrowTokenRemovedListener(
|
||||
@NonNull final IWeakEscrowTokenRemovedListener listener) {
|
||||
try {
|
||||
return getLockSettings().unregisterWeakEscrowTokenRemovedListener(listener);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener.");
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
|
||||
try {
|
||||
getLockSettings().reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
|
||||
@@ -1354,6 +1376,29 @@ public class LockPatternUtils {
|
||||
return getLockSettingsInternal().addEscrowToken(token, userId, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a weak escrow token for the current user, which can later be used to unlock FBE
|
||||
* or change user password.
|
||||
*
|
||||
* After adding, if the user currently has lockscreen password, they will need to perform a
|
||||
* confirm credential operation in order to activate the token for future use. If the user
|
||||
* has no secure lockscreen, then the token is activated immediately.
|
||||
*
|
||||
* If the user changes or removes lockscreen password, activated weak escrow tokens will be
|
||||
* removed.
|
||||
*
|
||||
* @return a unique 64-bit token handle which is needed to refer to this token later.
|
||||
*/
|
||||
public long addWeakEscrowToken(byte[] token, int userId,
|
||||
@NonNull IWeakEscrowTokenActivatedListener callback) {
|
||||
try {
|
||||
return getLockSettings().addWeakEscrowToken(token, userId, callback);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not add weak token.");
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface to notify when an added escrow token has been activated.
|
||||
*/
|
||||
@@ -1361,9 +1406,9 @@ public class LockPatternUtils {
|
||||
/**
|
||||
* The method to be called when the token is activated.
|
||||
* @param handle 64 bit handle corresponding to the escrow token
|
||||
* @param userid user for whom the escrow token has been added
|
||||
* @param userId user for whom the escrow token has been added
|
||||
*/
|
||||
void onEscrowTokenActivated(long handle, int userid);
|
||||
void onEscrowTokenActivated(long handle, int userId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1378,6 +1423,21 @@ public class LockPatternUtils {
|
||||
return getLockSettingsInternal().removeEscrowToken(handle, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a weak escrow token.
|
||||
*
|
||||
* @return true if the given handle refers to a valid weak token previously returned from
|
||||
* {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise.
|
||||
*/
|
||||
public boolean removeWeakEscrowToken(long handle, int userId) {
|
||||
try {
|
||||
return getLockSettings().removeWeakEscrowToken(handle, userId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not remove the weak token.");
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given escrow token is active or not. Only active token can be used to call
|
||||
* {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
|
||||
@@ -1388,6 +1448,29 @@ public class LockPatternUtils {
|
||||
return getLockSettingsInternal().isEscrowTokenActive(handle, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given weak escrow token is active or not. Only active token can be used to call
|
||||
* {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
|
||||
*/
|
||||
public boolean isWeakEscrowTokenActive(long handle, int userId) {
|
||||
try {
|
||||
return getLockSettings().isWeakEscrowTokenActive(handle, userId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not check the weak token.");
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if the given weak escrow token is valid. */
|
||||
public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
|
||||
try {
|
||||
return getLockSettings().isWeakEscrowTokenValid(handle, token, userId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not validate the weak token.");
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a user's lock credential with a pre-configured escrow token.
|
||||
*
|
||||
|
||||
@@ -5294,6 +5294,12 @@
|
||||
<permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- @SystemApi Allows an application to manage weak escrow token on the device. This permission
|
||||
is not available to third party applications.
|
||||
@hide -->
|
||||
<permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- Allows an application to listen to trust changes. Only allowed for system processes.
|
||||
@hide -->
|
||||
<permission android:name="android.permission.TRUST_LISTENER"
|
||||
|
||||
@@ -21,13 +21,16 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.test.mock.MockContentResolver;
|
||||
@@ -38,12 +41,16 @@ import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.util.test.FakeSettingsProvider;
|
||||
import com.android.internal.widget.ILockSettings;
|
||||
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
|
||||
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class LockPatternUtilsTest {
|
||||
@@ -102,4 +109,84 @@ public class LockPatternUtilsTest {
|
||||
configureTest(false, true, 0);
|
||||
assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddWeakEscrowToken() throws RemoteException {
|
||||
ILockSettings ils = createTestLockSettings();
|
||||
byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
|
||||
int testUserId = 10;
|
||||
IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
|
||||
mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
|
||||
verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
|
||||
ILockSettings ils = createTestLockSettings();
|
||||
IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
|
||||
mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
|
||||
verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
|
||||
ILockSettings ils = createTestLockSettings();
|
||||
IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
|
||||
mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
|
||||
verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAutoEscrowToken() throws RemoteException {
|
||||
ILockSettings ils = createTestLockSettings();
|
||||
int testUserId = 10;
|
||||
long testHandle = 100L;
|
||||
mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
|
||||
verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAutoEscrowTokenActive() throws RemoteException {
|
||||
ILockSettings ils = createTestLockSettings();
|
||||
int testUserId = 10;
|
||||
long testHandle = 100L;
|
||||
mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
|
||||
verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAutoEscrowTokenValid() throws RemoteException {
|
||||
ILockSettings ils = createTestLockSettings();
|
||||
int testUserId = 10;
|
||||
byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
|
||||
long testHandle = 100L;
|
||||
mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
|
||||
verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
|
||||
}
|
||||
|
||||
private ILockSettings createTestLockSettings() {
|
||||
final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||
mLockPatternUtils = spy(new LockPatternUtils(context));
|
||||
final ILockSettings ils = Mockito.mock(ILockSettings.class);
|
||||
when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
|
||||
return ils;
|
||||
}
|
||||
|
||||
private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
|
||||
return new IWeakEscrowTokenActivatedListener.Stub() {
|
||||
@Override
|
||||
public void onWeakEscrowTokenActivated(long handle, int userId) {
|
||||
// Do nothing.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
|
||||
return new IWeakEscrowTokenRemovedListener.Stub() {
|
||||
@Override
|
||||
public void onWeakEscrowTokenRemoved(long handle, int userId) {
|
||||
// Do nothing.
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@ import static com.android.internal.widget.LockPatternUtils.USER_FRP;
|
||||
import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
|
||||
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
|
||||
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
|
||||
import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
|
||||
import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
@@ -123,6 +126,8 @@ import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.widget.ICheckCredentialProgressCallback;
|
||||
import com.android.internal.widget.ILockSettings;
|
||||
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
|
||||
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockSettingsInternal;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
@@ -135,6 +140,7 @@ import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
|
||||
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
|
||||
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
|
||||
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
|
||||
import com.android.server.locksettings.SyntheticPasswordManager.TokenType;
|
||||
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
|
||||
import com.android.server.pm.UserManagerInternal;
|
||||
import com.android.server.wm.WindowManagerInternal;
|
||||
@@ -1123,6 +1129,16 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private void checkManageWeakEscrowTokenMethodUsage() {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN,
|
||||
"Requires MANAGE_WEAK_ESCROW_TOKEN permission.");
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Weak escrow token are only for automotive devices.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSecureLockScreen() {
|
||||
return mHasSecureLockScreen;
|
||||
@@ -1911,6 +1927,97 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
});
|
||||
}
|
||||
|
||||
/** Register the given WeakEscrowTokenRemovedListener. */
|
||||
@Override
|
||||
public boolean registerWeakEscrowTokenRemovedListener(
|
||||
@NonNull IWeakEscrowTokenRemovedListener listener) {
|
||||
checkManageWeakEscrowTokenMethodUsage();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mSpManager.registerWeakEscrowTokenRemovedListener(listener);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
/** Unregister the given WeakEscrowTokenRemovedListener. */
|
||||
@Override
|
||||
public boolean unregisterWeakEscrowTokenRemovedListener(
|
||||
@NonNull IWeakEscrowTokenRemovedListener listener) {
|
||||
checkManageWeakEscrowTokenMethodUsage();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mSpManager.unregisterWeakEscrowTokenRemovedListener(listener);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long addWeakEscrowToken(byte[] token, int userId,
|
||||
@NonNull IWeakEscrowTokenActivatedListener listener) {
|
||||
checkManageWeakEscrowTokenMethodUsage();
|
||||
Objects.requireNonNull(listener, "Listener can not be null.");
|
||||
EscrowTokenStateChangeCallback internalListener = (handle, userId1) -> {
|
||||
try {
|
||||
listener.onWeakEscrowTokenActivated(handle, userId1);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Exception while notifying weak escrow token has been activated", e);
|
||||
}
|
||||
};
|
||||
final long restoreToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return addEscrowToken(token, TOKEN_TYPE_WEAK, userId, internalListener);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(restoreToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeWeakEscrowToken(long handle, int userId) {
|
||||
checkManageWeakEscrowTokenMethodUsage();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return removeEscrowToken(handle, userId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWeakEscrowTokenActive(long handle, int userId) {
|
||||
checkManageWeakEscrowTokenMethodUsage();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return isEscrowTokenActive(handle, userId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
|
||||
checkManageWeakEscrowTokenMethodUsage();
|
||||
final long restoreToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mSpManager) {
|
||||
if (!mSpManager.hasEscrowData(userId)) {
|
||||
Slog.w(TAG, "Escrow token is disabled on the current user");
|
||||
return false;
|
||||
}
|
||||
AuthenticationResult authResult = mSpManager.unwrapWeakTokenBasedSyntheticPassword(
|
||||
getGateKeeperService(), handle, token, userId);
|
||||
if (authResult.authToken == null) {
|
||||
Slog.w(TAG, "Invalid escrow token supplied");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(restoreToken);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting /** Note: this method is overridden in unit tests */
|
||||
protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
|
||||
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
|
||||
@@ -3029,6 +3136,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
private long setLockCredentialWithAuthTokenLocked(LockscreenCredential credential,
|
||||
AuthenticationToken auth, int userId) {
|
||||
if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
|
||||
final int savedCredentialType = getCredentialTypeInternal(userId);
|
||||
long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
|
||||
credential, auth, userId);
|
||||
final Map<Integer, LockscreenCredential> profilePasswords;
|
||||
@@ -3075,6 +3183,9 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
|
||||
setUserPasswordMetrics(credential, userId);
|
||||
mManagedProfilePasswordCache.removePassword(userId);
|
||||
if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
|
||||
mSpManager.destroyAllWeakTokenBasedSyntheticPasswords(userId);
|
||||
}
|
||||
|
||||
if (profilePasswords != null) {
|
||||
for (Map.Entry<Integer, LockscreenCredential> entry : profilePasswords.entrySet()) {
|
||||
@@ -3242,8 +3353,9 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private long addEscrowToken(byte[] token, int userId, EscrowTokenStateChangeCallback callback) {
|
||||
if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
|
||||
private long addEscrowToken(@NonNull byte[] token, @TokenType int type, int userId,
|
||||
@NonNull EscrowTokenStateChangeCallback callback) {
|
||||
if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId + ", type=" + type);
|
||||
synchronized (mSpManager) {
|
||||
// Migrate to synthetic password based credentials if the user has no password,
|
||||
// the token can then be activated immediately.
|
||||
@@ -3264,7 +3376,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
throw new SecurityException("Escrow token is disabled on the current user");
|
||||
}
|
||||
}
|
||||
long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId, callback);
|
||||
long handle = mSpManager.createTokenBasedSyntheticPassword(token, type, userId,
|
||||
callback);
|
||||
if (auth != null) {
|
||||
mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
|
||||
}
|
||||
@@ -3345,8 +3458,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential,
|
||||
long tokenHandle, byte[] token, int userId) {
|
||||
final AuthenticationResult result;
|
||||
result = mSpManager.unwrapTokenBasedSyntheticPassword(
|
||||
getGateKeeperService(), tokenHandle, token, userId);
|
||||
result = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle,
|
||||
token, userId);
|
||||
if (result.authToken == null) {
|
||||
Slog.w(TAG, "Invalid escrow token supplied");
|
||||
return false;
|
||||
@@ -3369,7 +3482,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
AuthenticationResult authResult;
|
||||
synchronized (mSpManager) {
|
||||
if (!mSpManager.hasEscrowData(userId)) {
|
||||
throw new SecurityException("Escrow token is disabled on the current user");
|
||||
Slog.w(TAG, "Escrow token is disabled on the current user");
|
||||
return false;
|
||||
}
|
||||
authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(),
|
||||
tokenHandle, token, userId);
|
||||
@@ -3643,7 +3757,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
@Override
|
||||
public long addEscrowToken(byte[] token, int userId,
|
||||
EscrowTokenStateChangeCallback callback) {
|
||||
return LockSettingsService.this.addEscrowToken(token, userId, callback);
|
||||
return LockSettingsService.this.addEscrowToken(token, TOKEN_TYPE_STRONG, userId,
|
||||
callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.locksettings;
|
||||
|
||||
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.admin.PasswordMetrics;
|
||||
@@ -28,6 +29,7 @@ import android.hardware.weaver.V1_0.WeaverConfig;
|
||||
import android.hardware.weaver.V1_0.WeaverReadResponse;
|
||||
import android.hardware.weaver.V1_0.WeaverReadStatus;
|
||||
import android.hardware.weaver.V1_0.WeaverStatus;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserManager;
|
||||
import android.security.GateKeeper;
|
||||
@@ -41,6 +43,7 @@ import android.util.Slog;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.widget.ICheckCredentialProgressCallback;
|
||||
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.internal.widget.VerifyCredentialResponse;
|
||||
@@ -48,6 +51,8 @@ import com.android.server.locksettings.LockSettingsStorage.PersistentData;
|
||||
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -111,7 +116,8 @@ public class SyntheticPasswordManager {
|
||||
private static final byte SYNTHETIC_PASSWORD_VERSION_V2 = 2;
|
||||
private static final byte SYNTHETIC_PASSWORD_VERSION_V3 = 3;
|
||||
private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
|
||||
private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
|
||||
private static final byte SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED = 1;
|
||||
private static final byte SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED = 2;
|
||||
|
||||
// 256-bit synthetic password
|
||||
private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
|
||||
@@ -366,10 +372,47 @@ public class SyntheticPasswordManager {
|
||||
}
|
||||
}
|
||||
|
||||
static class SyntheticPasswordBlob {
|
||||
byte mVersion;
|
||||
byte mType;
|
||||
byte[] mContent;
|
||||
|
||||
public static SyntheticPasswordBlob create(byte version, byte type, byte[] content) {
|
||||
SyntheticPasswordBlob result = new SyntheticPasswordBlob();
|
||||
result.mVersion = version;
|
||||
result.mType = type;
|
||||
result.mContent = content;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static SyntheticPasswordBlob fromBytes(byte[] data) {
|
||||
SyntheticPasswordBlob result = new SyntheticPasswordBlob();
|
||||
result.mVersion = data[0];
|
||||
result.mType = data[1];
|
||||
result.mContent = Arrays.copyOfRange(data, 2, data.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] toByte() {
|
||||
byte[] blob = new byte[mContent.length + 1 + 1];
|
||||
blob[0] = mVersion;
|
||||
blob[1] = mType;
|
||||
System.arraycopy(mContent, 0, blob, 2, mContent.length);
|
||||
return blob;
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({TOKEN_TYPE_STRONG, TOKEN_TYPE_WEAK})
|
||||
@interface TokenType {}
|
||||
static final int TOKEN_TYPE_STRONG = 0;
|
||||
static final int TOKEN_TYPE_WEAK = 1;
|
||||
|
||||
static class TokenData {
|
||||
byte[] secdiscardableOnDisk;
|
||||
byte[] weaverSecret;
|
||||
byte[] aggregatedSecret;
|
||||
@TokenType int mType;
|
||||
EscrowTokenStateChangeCallback mCallback;
|
||||
}
|
||||
|
||||
@@ -381,6 +424,9 @@ public class SyntheticPasswordManager {
|
||||
|
||||
private final UserManager mUserManager;
|
||||
|
||||
private final RemoteCallbackList<IWeakEscrowTokenRemovedListener> mListeners =
|
||||
new RemoteCallbackList<>();
|
||||
|
||||
public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
|
||||
UserManager userManager, PasswordSlotManager passwordSlotManager) {
|
||||
mContext = context;
|
||||
@@ -880,13 +926,34 @@ public class SyntheticPasswordManager {
|
||||
* Create a token based Synthetic password for the given user.
|
||||
* @return the handle of the token
|
||||
*/
|
||||
public long createTokenBasedSyntheticPassword(byte[] token, int userId,
|
||||
public long createStrongTokenBasedSyntheticPassword(byte[] token, int userId,
|
||||
@Nullable EscrowTokenStateChangeCallback changeCallback) {
|
||||
return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_STRONG, userId,
|
||||
changeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a weak token based Synthetic password for the given user.
|
||||
* @return the handle of the weak token
|
||||
*/
|
||||
public long createWeakTokenBasedSyntheticPassword(byte[] token, int userId,
|
||||
@Nullable EscrowTokenStateChangeCallback changeCallback) {
|
||||
return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_WEAK, userId,
|
||||
changeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a token based Synthetic password of the given type for the given user.
|
||||
* @return the handle of the token
|
||||
*/
|
||||
public long createTokenBasedSyntheticPassword(byte[] token, @TokenType int type, int userId,
|
||||
@Nullable EscrowTokenStateChangeCallback changeCallback) {
|
||||
long handle = generateHandle();
|
||||
if (!tokenMap.containsKey(userId)) {
|
||||
tokenMap.put(userId, new ArrayMap<>());
|
||||
}
|
||||
TokenData tokenData = new TokenData();
|
||||
tokenData.mType = type;
|
||||
final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
|
||||
if (isWeaverAvailable()) {
|
||||
tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
|
||||
@@ -910,6 +977,7 @@ public class SyntheticPasswordManager {
|
||||
return new ArraySet<>(tokenMap.get(userId).keySet());
|
||||
}
|
||||
|
||||
/** Remove the given pending token. */
|
||||
public boolean removePendingToken(long handle, int userId) {
|
||||
if (!tokenMap.containsKey(userId)) {
|
||||
return false;
|
||||
@@ -941,7 +1009,7 @@ public class SyntheticPasswordManager {
|
||||
mPasswordSlotManager.markSlotInUse(slot);
|
||||
}
|
||||
saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId);
|
||||
createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
|
||||
createSyntheticPasswordBlob(handle, getTokenBasedBlobType(tokenData.mType), authToken,
|
||||
tokenData.aggregatedSecret, 0L, userId);
|
||||
tokenMap.get(userId).remove(handle);
|
||||
if (tokenData.mCallback != null) {
|
||||
@@ -953,26 +1021,23 @@ public class SyntheticPasswordManager {
|
||||
private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
|
||||
byte[] applicationId, long sid, int userId) {
|
||||
final byte[] secret;
|
||||
if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
|
||||
if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
|
||||
|| type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
|
||||
secret = authToken.getEscrowSecret();
|
||||
} else {
|
||||
secret = authToken.getSyntheticPassword();
|
||||
}
|
||||
byte[] content = createSPBlob(getKeyName(handle), secret, applicationId, sid);
|
||||
byte[] blob = new byte[content.length + 1 + 1];
|
||||
/*
|
||||
* We can upgrade from v1 to v2 because that's just a change in the way that
|
||||
* the SP is stored. However, we can't upgrade to v3 because that is a change
|
||||
* in the way that passwords are derived from the SP.
|
||||
*/
|
||||
if (authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
|
||||
blob[0] = SYNTHETIC_PASSWORD_VERSION_V3;
|
||||
} else {
|
||||
blob[0] = SYNTHETIC_PASSWORD_VERSION_V2;
|
||||
}
|
||||
blob[1] = type;
|
||||
System.arraycopy(content, 0, blob, 2, content.length);
|
||||
saveState(SP_BLOB_NAME, blob, handle, userId);
|
||||
byte version = authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3
|
||||
? SYNTHETIC_PASSWORD_VERSION_V3 : SYNTHETIC_PASSWORD_VERSION_V2;
|
||||
|
||||
SyntheticPasswordBlob blob = SyntheticPasswordBlob.create(version, type, content);
|
||||
saveState(SP_BLOB_NAME, blob.toByte(), handle, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1089,6 +1154,36 @@ public class SyntheticPasswordManager {
|
||||
*/
|
||||
public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
|
||||
IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
|
||||
SyntheticPasswordBlob blob = SyntheticPasswordBlob
|
||||
.fromBytes(loadState(SP_BLOB_NAME, handle, userId));
|
||||
return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
|
||||
blob.mType, token, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a synthetic password by supplying an strong escrow token and corresponding token
|
||||
* blob handle generated previously. If the decryption is successful, initiate a GateKeeper
|
||||
* verification to referesh the SID & Auth token maintained by the system.
|
||||
*/
|
||||
public @NonNull AuthenticationResult unwrapStrongTokenBasedSyntheticPassword(
|
||||
IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
|
||||
return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
|
||||
SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED, token, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a synthetic password by supplying a weak escrow token and corresponding token
|
||||
* blob handle generated previously. If the decryption is successful, initiate a GateKeeper
|
||||
* verification to referesh the SID & Auth token maintained by the system.
|
||||
*/
|
||||
public @NonNull AuthenticationResult unwrapWeakTokenBasedSyntheticPassword(
|
||||
IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
|
||||
return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
|
||||
SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED, token, userId);
|
||||
}
|
||||
|
||||
private @NonNull AuthenticationResult unwrapTokenBasedSyntheticPasswordInternal(
|
||||
IGateKeeperService gatekeeper, long handle, byte type, byte[] token, int userId) {
|
||||
AuthenticationResult result = new AuthenticationResult();
|
||||
byte[] secdiscardable = loadSecdiscardable(handle, userId);
|
||||
int slotId = loadWeaverSlot(handle, userId);
|
||||
@@ -1109,8 +1204,7 @@ public class SyntheticPasswordManager {
|
||||
PERSONALISATION_WEAVER_TOKEN, secdiscardable);
|
||||
}
|
||||
byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
|
||||
result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
|
||||
applicationId, 0L, userId);
|
||||
result.authToken = unwrapSyntheticPasswordBlob(handle, type, applicationId, 0L, userId);
|
||||
if (result.authToken != null) {
|
||||
result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
|
||||
if (result.gkResponse == null) {
|
||||
@@ -1126,33 +1220,33 @@ public class SyntheticPasswordManager {
|
||||
|
||||
private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
|
||||
byte[] applicationId, long sid, int userId) {
|
||||
byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
|
||||
if (blob == null) {
|
||||
byte[] data = loadState(SP_BLOB_NAME, handle, userId);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
final byte version = blob[0];
|
||||
if (version != SYNTHETIC_PASSWORD_VERSION_V3
|
||||
&& version != SYNTHETIC_PASSWORD_VERSION_V2
|
||||
&& version != SYNTHETIC_PASSWORD_VERSION_V1) {
|
||||
SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(data);
|
||||
if (blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V3
|
||||
&& blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V2
|
||||
&& blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V1) {
|
||||
throw new IllegalArgumentException("Unknown blob version");
|
||||
}
|
||||
if (blob[1] != type) {
|
||||
if (blob.mType != type) {
|
||||
throw new IllegalArgumentException("Invalid blob type");
|
||||
}
|
||||
final byte[] secret;
|
||||
if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
|
||||
secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle),
|
||||
Arrays.copyOfRange(blob, 2, blob.length), applicationId);
|
||||
if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
|
||||
secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle), blob.mContent,
|
||||
applicationId);
|
||||
} else {
|
||||
secret = decryptSPBlob(getKeyName(handle),
|
||||
Arrays.copyOfRange(blob, 2, blob.length), applicationId);
|
||||
secret = decryptSPBlob(getKeyName(handle), blob.mContent, applicationId);
|
||||
}
|
||||
if (secret == null) {
|
||||
Slog.e(TAG, "Fail to decrypt SP for user " + userId);
|
||||
return null;
|
||||
}
|
||||
AuthenticationToken result = new AuthenticationToken(version);
|
||||
if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
|
||||
AuthenticationToken result = new AuthenticationToken(blob.mVersion);
|
||||
if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
|
||||
|| type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
|
||||
if (!loadEscrowData(result, userId)) {
|
||||
Slog.e(TAG, "User is not escrowable: " + userId);
|
||||
return null;
|
||||
@@ -1161,7 +1255,7 @@ public class SyntheticPasswordManager {
|
||||
} else {
|
||||
result.recreateDirectly(secret);
|
||||
}
|
||||
if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
|
||||
if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
|
||||
Slog.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
|
||||
createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
|
||||
}
|
||||
@@ -1233,9 +1327,28 @@ public class SyntheticPasswordManager {
|
||||
return hasState(SP_BLOB_NAME, handle, userId);
|
||||
}
|
||||
|
||||
/** Destroy the escrow token with the given handle for the given user. */
|
||||
public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
|
||||
SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME, handle,
|
||||
userId));
|
||||
destroySyntheticPassword(handle, userId);
|
||||
destroyState(SECDISCARDABLE_NAME, handle, userId);
|
||||
if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
|
||||
notifyWeakEscrowTokenRemovedListeners(handle, userId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Destroy all weak escrow tokens for the given user. */
|
||||
public void destroyAllWeakTokenBasedSyntheticPasswords(int userId) {
|
||||
List<Long> handles = mStorage.listSyntheticPasswordHandlesForUser(SECDISCARDABLE_NAME,
|
||||
userId);
|
||||
for (long handle: handles) {
|
||||
SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
|
||||
handle, userId));
|
||||
if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
|
||||
destroyTokenBasedSyntheticPassword(handle, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
|
||||
@@ -1285,6 +1398,16 @@ public class SyntheticPasswordManager {
|
||||
return loadState(SECDISCARDABLE_NAME, handle, userId);
|
||||
}
|
||||
|
||||
private byte getTokenBasedBlobType(@TokenType int type) {
|
||||
switch (type) {
|
||||
case TOKEN_TYPE_WEAK:
|
||||
return SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED;
|
||||
case TOKEN_TYPE_STRONG:
|
||||
default:
|
||||
return SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the saved password metrics associated with a SP handle. Only meaningful to be
|
||||
* called on the handle of a password-based synthetic password. A valid AuthenticationToken for
|
||||
@@ -1439,4 +1562,33 @@ public class SyntheticPasswordManager {
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/** Register the given IWeakEscrowTokenRemovedListener. */
|
||||
public boolean registerWeakEscrowTokenRemovedListener(
|
||||
IWeakEscrowTokenRemovedListener listener) {
|
||||
return mListeners.register(listener);
|
||||
}
|
||||
|
||||
/** Unregister the given IWeakEscrowTokenRemovedListener. */
|
||||
public boolean unregisterWeakEscrowTokenRemovedListener(
|
||||
IWeakEscrowTokenRemovedListener listener) {
|
||||
return mListeners.unregister(listener);
|
||||
}
|
||||
|
||||
private void notifyWeakEscrowTokenRemovedListeners(long handle, int userId) {
|
||||
int i = mListeners.beginBroadcast();
|
||||
try {
|
||||
while (i > 0) {
|
||||
i--;
|
||||
try {
|
||||
mListeners.getBroadcastItem(i).onWeakEscrowTokenRemoved(handle, userId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Exception while notifying WeakEscrowTokenRemovedListener.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
mListeners.finishBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.admin.PasswordMetrics;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
|
||||
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.internal.widget.VerifyCredentialResponse;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** atest FrameworksServicesTests:WeakEscrowTokenTests */
|
||||
@SmallTest
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class WeakEscrowTokenTests extends BaseLockSettingsServiceTests{
|
||||
|
||||
@Test
|
||||
public void testWeakTokenActivatedImmediatelyIfNoUserPassword()
|
||||
throws RemoteException {
|
||||
mockAutoHardware();
|
||||
final byte[] token = "some-high-entropy-secure-token".getBytes();
|
||||
IWeakEscrowTokenActivatedListener mockListener =
|
||||
mock(IWeakEscrowTokenActivatedListener.Stub.class);
|
||||
long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener);
|
||||
assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
|
||||
assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
|
||||
verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeakTokenActivatedLaterWithUserPassword()
|
||||
throws RemoteException {
|
||||
mockAutoHardware();
|
||||
byte[] token = "some-high-entropy-secure-token".getBytes();
|
||||
IWeakEscrowTokenActivatedListener mockListener =
|
||||
mock(IWeakEscrowTokenActivatedListener.Stub.class);
|
||||
LockscreenCredential password = newPassword("password");
|
||||
mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
|
||||
|
||||
long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener);
|
||||
// Token not activated immediately since user password exists
|
||||
assertFalse(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
|
||||
// Activate token (password gets migrated to SP at the same time)
|
||||
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
|
||||
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
|
||||
// Verify token is activated and valid
|
||||
assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
|
||||
assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
|
||||
verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeakTokensRemovedIfCredentialChanged() throws Exception {
|
||||
mockAutoHardware();
|
||||
byte[] token = "some-high-entropy-secure-token".getBytes();
|
||||
IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
|
||||
IWeakEscrowTokenActivatedListener mockActivateListener =
|
||||
mock(IWeakEscrowTokenActivatedListener.Stub.class);
|
||||
LockscreenCredential password = newPassword("password");
|
||||
LockscreenCredential pattern = newPattern("123654");
|
||||
mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
|
||||
mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
|
||||
|
||||
long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
|
||||
|
||||
// Activate token
|
||||
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
|
||||
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
|
||||
|
||||
// Verify token removed
|
||||
assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
|
||||
assertTrue(mLocalService.setLockCredentialWithToken(
|
||||
pattern, handle, token, PRIMARY_USER_ID));
|
||||
assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
|
||||
verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeakTokenRemovedListenerRegistered() throws Exception {
|
||||
mockAutoHardware();
|
||||
IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
|
||||
IWeakEscrowTokenActivatedListener mockActivateListener =
|
||||
mock(IWeakEscrowTokenActivatedListener.Stub.class);
|
||||
byte[] token = "some-high-entropy-secure-token".getBytes();
|
||||
long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
|
||||
|
||||
mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
|
||||
mService.removeWeakEscrowToken(handle, PRIMARY_USER_ID);
|
||||
|
||||
verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWeakTokenRemovedListenerUnregistered() throws Exception {
|
||||
mockAutoHardware();
|
||||
IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
|
||||
IWeakEscrowTokenActivatedListener mockActivateListener =
|
||||
mock(IWeakEscrowTokenActivatedListener.Stub.class);
|
||||
byte[] token0 = "some-high-entropy-secure-token-0".getBytes();
|
||||
byte[] token1 = "some-high-entropy-secure-token-1".getBytes();
|
||||
long handle0 = mService.addWeakEscrowToken(token0, PRIMARY_USER_ID, mockActivateListener);
|
||||
long handle1 = mService.addWeakEscrowToken(token1, PRIMARY_USER_ID, mockActivateListener);
|
||||
|
||||
mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
|
||||
mService.removeWeakEscrowToken(handle0, PRIMARY_USER_ID);
|
||||
verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle0, PRIMARY_USER_ID);
|
||||
|
||||
mService.unregisterWeakEscrowTokenRemovedListener(mockRemoveListener);
|
||||
mService.removeWeakEscrowToken(handle1, PRIMARY_USER_ID);
|
||||
verify(mockRemoveListener, never()).onWeakEscrowTokenRemoved(handle1, PRIMARY_USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnlockUserWithToken_weakEscrowToken() throws Exception {
|
||||
mockAutoHardware();
|
||||
IWeakEscrowTokenActivatedListener mockActivateListener =
|
||||
mock(IWeakEscrowTokenActivatedListener.Stub.class);
|
||||
LockscreenCredential password = newPassword("password");
|
||||
byte[] token = "some-high-entropy-secure-token".getBytes();
|
||||
mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
|
||||
// Disregard any reportPasswordChanged() invocations as part of credential setup.
|
||||
flushHandlerTasks();
|
||||
reset(mDevicePolicyManager);
|
||||
|
||||
long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
|
||||
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
|
||||
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
|
||||
assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
|
||||
assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
|
||||
|
||||
mService.onCleanupUser(PRIMARY_USER_ID);
|
||||
assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
|
||||
|
||||
assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID));
|
||||
assertEquals(PasswordMetrics.computeForCredential(password),
|
||||
mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
|
||||
}
|
||||
|
||||
private void mockAutoHardware() {
|
||||
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
|
||||
}
|
||||
|
||||
private IWeakEscrowTokenRemovedListener mockAliveRemoveListener() {
|
||||
IWeakEscrowTokenRemovedListener mockListener =
|
||||
mock(IWeakEscrowTokenRemovedListener.Stub.class);
|
||||
IBinder mockIBinder = mock(IBinder.class);
|
||||
when(mockIBinder.isBinderAlive()).thenReturn(true);
|
||||
when(mockListener.asBinder()).thenReturn(mockIBinder);
|
||||
return mockListener;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user