Support non-user-0 profile in ManagedProfilePasswordCache
Bug: 272704160
Test: com.android.server.locksettings
com.android.cts.devicepolicy.QuietModeHostsideTest
KeyGenParameterSpecTest
Manual
Change-Id: I620cc4455ca0f7a8508f12b7550039200b42b8e8
This commit is contained in:
@@ -320,6 +320,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
|
||||
private final boolean mCriticalToDeviceEncryption;
|
||||
private final int mMaxUsageCount;
|
||||
private final String mAttestKeyAlias;
|
||||
private final long mBoundToSecureUserId;
|
||||
|
||||
/*
|
||||
* ***NOTE***: All new fields MUST also be added to the following:
|
||||
* ParcelableKeyGenParameterSpec class.
|
||||
@@ -362,7 +364,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
|
||||
boolean unlockedDeviceRequired,
|
||||
boolean criticalToDeviceEncryption,
|
||||
int maxUsageCount,
|
||||
String attestKeyAlias) {
|
||||
String attestKeyAlias,
|
||||
long boundToSecureUserId) {
|
||||
if (TextUtils.isEmpty(keyStoreAlias)) {
|
||||
throw new IllegalArgumentException("keyStoreAlias must not be empty");
|
||||
}
|
||||
@@ -422,6 +425,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
|
||||
mCriticalToDeviceEncryption = criticalToDeviceEncryption;
|
||||
mMaxUsageCount = maxUsageCount;
|
||||
mAttestKeyAlias = attestKeyAlias;
|
||||
mBoundToSecureUserId = boundToSecureUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -842,10 +846,20 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the secure user id that this key should be bound to.
|
||||
*
|
||||
* Normally an authentication-bound key is tied to the secure user id of the current user
|
||||
* (either the root SID from GateKeeper for auth-bound keys with a timeout, or the authenticator
|
||||
* id of the current biometric set for keys requiring explicit biometric authorization).
|
||||
* If this parameter is set (this method returning non-zero value), the key should be tied to
|
||||
* the specified secure user id, overriding the logic above.
|
||||
*
|
||||
* This is only applicable when {@link #isUserAuthenticationRequired} is {@code true}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public long getBoundToSpecificSecureUserId() {
|
||||
return GateKeeper.INVALID_SECURE_USER_ID;
|
||||
return mBoundToSecureUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -920,6 +934,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
|
||||
private boolean mCriticalToDeviceEncryption = false;
|
||||
private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
|
||||
private String mAttestKeyAlias = null;
|
||||
private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the {@code Builder}.
|
||||
@@ -990,6 +1005,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
|
||||
mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
|
||||
mMaxUsageCount = sourceSpec.getMaxUsageCount();
|
||||
mAttestKeyAlias = sourceSpec.getAttestKeyAlias();
|
||||
mBoundToSecureUserId = sourceSpec.getBoundToSpecificSecureUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1724,6 +1740,27 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the secure user id that this key should be bound to.
|
||||
*
|
||||
* Normally an authentication-bound key is tied to the secure user id of the current user
|
||||
* (either the root SID from GateKeeper for auth-bound keys with a timeout, or the
|
||||
* authenticator id of the current biometric set for keys requiring explicit biometric
|
||||
* authorization). If this parameter is set (this method returning non-zero value), the key
|
||||
* should be tied to the specified secure user id, overriding the logic above.
|
||||
*
|
||||
* This is only applicable when {@link #setUserAuthenticationRequired} is set to
|
||||
* {@code true}
|
||||
*
|
||||
* @see KeyGenParameterSpec#getBoundToSpecificSecureUserId()
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setBoundToSpecificSecureUserId(long secureUserId) {
|
||||
mBoundToSecureUserId = secureUserId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@code KeyGenParameterSpec}.
|
||||
*/
|
||||
@@ -1762,7 +1799,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
|
||||
mUnlockedDeviceRequired,
|
||||
mCriticalToDeviceEncryption,
|
||||
mMaxUsageCount,
|
||||
mAttestKeyAlias);
|
||||
mAttestKeyAlias,
|
||||
mBoundToSecureUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
|
||||
out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
|
||||
out.writeInt(mSpec.getMaxUsageCount());
|
||||
out.writeString(mSpec.getAttestKeyAlias());
|
||||
out.writeLong(mSpec.getBoundToSpecificSecureUserId());
|
||||
}
|
||||
|
||||
private static Date readDateOrNull(Parcel in) {
|
||||
@@ -172,6 +173,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
|
||||
final boolean criticalToDeviceEncryption = in.readBoolean();
|
||||
final int maxUsageCount = in.readInt();
|
||||
final String attestKeyAlias = in.readString();
|
||||
final long boundToSecureUserId = in.readLong();
|
||||
// The KeyGenParameterSpec is intentionally not constructed using a Builder here:
|
||||
// The intention is for this class to break if new parameters are added to the
|
||||
// KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
|
||||
@@ -208,7 +210,8 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
|
||||
unlockedDeviceRequired,
|
||||
criticalToDeviceEncryption,
|
||||
maxUsageCount,
|
||||
attestKeyAlias);
|
||||
attestKeyAlias,
|
||||
boundToSecureUserId);
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
|
||||
|
||||
@@ -400,11 +400,13 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
profileUserId, /* isLockTiedToParent= */ true);
|
||||
return;
|
||||
}
|
||||
final long parentSid;
|
||||
// Do not tie when the parent has no SID (but does have a screen lock).
|
||||
// This can only happen during an upgrade path where SID is yet to be
|
||||
// generated when the user unlocks for the first time.
|
||||
try {
|
||||
if (getGateKeeperService().getSecureUserId(parentId) == 0) {
|
||||
parentSid = getGateKeeperService().getSecureUserId(parentId);
|
||||
if (parentSid == 0) {
|
||||
return;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
@@ -415,7 +417,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId,
|
||||
/* isLockTiedToParent= */ true);
|
||||
tieProfileLockToParent(profileUserId, parentId, unifiedProfilePassword);
|
||||
mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword);
|
||||
mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
|
||||
parentSid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,7 +577,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
|
||||
public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache(
|
||||
java.security.KeyStore ks) {
|
||||
return new ManagedProfilePasswordCache(ks, getUserManager());
|
||||
return new ManagedProfilePasswordCache(ks);
|
||||
}
|
||||
|
||||
public boolean isHeadlessSystemUserMode() {
|
||||
@@ -1325,7 +1328,13 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
LockscreenCredential credential = LockscreenCredential.createManagedPassword(
|
||||
decryptionResult);
|
||||
Arrays.fill(decryptionResult, (byte) 0);
|
||||
mManagedProfilePasswordCache.storePassword(userId, credential);
|
||||
try {
|
||||
long parentSid = getGateKeeperService().getSecureUserId(
|
||||
mUserManager.getProfileParent(userId).id);
|
||||
mManagedProfilePasswordCache.storePassword(userId, credential, parentSid);
|
||||
} catch (RemoteException e) {
|
||||
Slogf.w(TAG, "Failed to talk to GateKeeper service", e);
|
||||
}
|
||||
return credential;
|
||||
}
|
||||
|
||||
@@ -2208,6 +2217,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
|
||||
int userId, @LockPatternUtils.VerifyFlag int flags) {
|
||||
checkPasswordReadPermission();
|
||||
Slogf.i(TAG, "Verifying tied profile challenge for user %d", userId);
|
||||
|
||||
if (!isProfileWithUnifiedLock(userId)) {
|
||||
throw new IllegalArgumentException(
|
||||
"User id must be managed/clone profile with unified lock");
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
package com.android.server.locksettings;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.security.GateKeeper;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.security.keystore.UserNotAuthenticatedException;
|
||||
@@ -45,12 +43,9 @@ import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
/**
|
||||
* Caches *unified* work challenge for user 0's managed profiles. Only user 0's profile is supported
|
||||
* at the moment because the cached credential is encrypted using a keystore key auth-bound to
|
||||
* user 0: this is to match how unified work challenge is similarly auth-bound to its parent user's
|
||||
* lockscreen credential normally. It's possible to extend this class to support managed profiles
|
||||
* for secondary users, that will require generating auth-bound keys to their corresponding parent
|
||||
* user though (which {@link KeyGenParameterSpec} does not support right now).
|
||||
* Caches *unified* work challenge for managed profiles. The cached credential is encrypted using
|
||||
* a keystore key auth-bound to the parent user's lockscreen credential, similar to how unified
|
||||
* work challenge is normally secured.
|
||||
*
|
||||
* <p> The cache is filled whenever the managed profile's unified challenge is created or derived
|
||||
* (as part of the parent user's credential verification flow). It's removed when the profile is
|
||||
@@ -70,28 +65,23 @@ public class ManagedProfilePasswordCache {
|
||||
|
||||
private final SparseArray<byte[]> mEncryptedPasswords = new SparseArray<>();
|
||||
private final KeyStore mKeyStore;
|
||||
private final UserManager mUserManager;
|
||||
|
||||
public ManagedProfilePasswordCache(KeyStore keyStore, UserManager userManager) {
|
||||
public ManagedProfilePasswordCache(KeyStore keyStore) {
|
||||
mKeyStore = keyStore;
|
||||
mUserManager = userManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt and store the password in the cache. Does NOT overwrite existing password cache
|
||||
* if one for the given user already exists.
|
||||
*
|
||||
* Should only be called on a profile userId.
|
||||
*/
|
||||
public void storePassword(int userId, LockscreenCredential password) {
|
||||
public void storePassword(int userId, LockscreenCredential password, long parentSid) {
|
||||
if (parentSid == GateKeeper.INVALID_SECURE_USER_ID) return;
|
||||
synchronized (mEncryptedPasswords) {
|
||||
if (mEncryptedPasswords.contains(userId)) {
|
||||
return;
|
||||
}
|
||||
UserInfo parent = mUserManager.getProfileParent(userId);
|
||||
if (parent == null || parent.id != UserHandle.USER_SYSTEM) {
|
||||
// Since the cached password is encrypted using a keystore key auth-bound to user 0,
|
||||
// only support caching password for user 0's profile.
|
||||
return;
|
||||
}
|
||||
String keyName = getEncryptionKeyName(userId);
|
||||
KeyGenerator generator;
|
||||
SecretKey key;
|
||||
@@ -104,8 +94,8 @@ public class ManagedProfilePasswordCache {
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setNamespace(SyntheticPasswordCrypto.keyNamespace())
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
// Generate auth-bound key to user 0 (since we the caller is user 0)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setBoundToSpecificSecureUserId(parentSid)
|
||||
.setUserAuthenticationValidityDurationSeconds(CACHE_TIMEOUT_SECONDS)
|
||||
.build());
|
||||
key = generator.generateKey();
|
||||
|
||||
Reference in New Issue
Block a user