diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index ffd041f60e26f..fe5432fcd9d83 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -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); } } } diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index a6e33664f2b1a..9356eb85bd8a9 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -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 CREATOR = new Creator() { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 0da94ff6aa0fa..12a7b7068a892 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -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"); diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java index ddc0e54244216..1298fe8f07a49 100644 --- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java +++ b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java @@ -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. * *

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 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();