Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 77183eff authored by Dmitry Dementyev's avatar Dmitry Dementyev
Browse files

Update recovery snapshot version.

There is exactly one snapshot per userId - recovery agent uid pair.
Version is incremented when
1) User credential is updated
2) User unlockes phone and list of application keys was changes since
last snapshot creation.

Bug: 66499222
Test: adb shell am instrument -w -e package \
com.android.server.locksettings.recoverablekeystore \
com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner

Change-Id: I6ab98fcbbb05e33958e6def644b40441cb52de6a
parent 032626a7
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -1284,6 +1284,7 @@ public class LockSettingsService extends ILockSettings.Stub {
            fixateNewestUserKeyAuth(userId);
            synchronizeUnifiedWorkChallengeForProfiles(userId, null);
            notifyActivePasswordMetricsAvailable(null, userId);
            mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId);
            return;
        }
        if (credential == null) {
@@ -1333,6 +1334,8 @@ public class LockSettingsService extends ILockSettings.Stub {
                    .verifyChallenge(userId, 0, willStore.hash, credential.getBytes());
            setUserKeyProtection(userId, credential, convertResponse(gkResponse));
            fixateNewestUserKeyAuth(userId);
            mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential,
                userId);
            // Refresh the auth token
            doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */);
            synchronizeUnifiedWorkChallengeForProfiles(userId, null);
+75 −23
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ public class KeySyncTask implements Runnable {
    private final int mUserId;
    private final int mCredentialType;
    private final String mCredential;
    private final boolean mCredentialUpdated;
    private final PlatformKeyManager.Factory mPlatformKeyManagerFactory;
    private final RecoverySnapshotStorage mRecoverySnapshotStorage;
    private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
@@ -80,7 +81,8 @@ public class KeySyncTask implements Runnable {
            RecoverySnapshotListenersStorage recoverySnapshotListenersStorage,
            int userId,
            int credentialType,
            String credential
            String credential,
            boolean credentialUpdated
    ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException {
        return new KeySyncTask(
                recoverableKeyStoreDb,
@@ -89,6 +91,7 @@ public class KeySyncTask implements Runnable {
                userId,
                credentialType,
                credential,
                credentialUpdated,
                () -> PlatformKeyManager.getInstance(context, recoverableKeyStoreDb));
    }

@@ -99,6 +102,7 @@ public class KeySyncTask implements Runnable {
     * @param userId The uid of the user whose profile has been unlocked.
     * @param credentialType The type of credential - i.e., pattern or password.
     * @param credential The credential, encoded as a {@link String}.
     * @param credentialUpdated signals weather credentials were updated.
     * @param platformKeyManagerFactory Instantiates a {@link PlatformKeyManager} for the user.
     *     This is a factory to enable unit testing, as otherwise it would be impossible to test
     *     without a screen unlock occurring!
@@ -111,12 +115,14 @@ public class KeySyncTask implements Runnable {
            int userId,
            int credentialType,
            String credential,
            boolean credentialUpdated,
            PlatformKeyManager.Factory platformKeyManagerFactory) {
        mSnapshotListenersStorage = recoverySnapshotListenersStorage;
        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
        mUserId = userId;
        mCredentialType = credentialType;
        mCredential = credential;
        mCredentialUpdated = credentialUpdated;
        mPlatformKeyManagerFactory = platformKeyManagerFactory;
        mRecoverySnapshotStorage = snapshotStorage;
    }
@@ -124,29 +130,44 @@ public class KeySyncTask implements Runnable {
    @Override
    public void run() {
        try {
            // Only one task is active If user unlocks phone many times in a short time interval.
            synchronized(KeySyncTask.class) {
                syncKeys();
            }
        } catch (Exception e) {
            Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
        }
    }

    private void syncKeys() {
        if (!isSyncPending()) {
            Log.d(TAG, "Key sync not needed.");
        if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
            // Application keys for the user will not be available for sync.
            Log.w(TAG, "Credentials are not set for user " + mUserId);
            return;
        }

        int recoveryAgentUid = mRecoverableKeyStoreDb.getRecoveryAgentUid(mUserId);
        if (recoveryAgentUid == -1) {
        List<Integer> recoveryAgents = mRecoverableKeyStoreDb.getRecoveryAgents(mUserId);
        for (int uid : recoveryAgents) {
            syncKeysForAgent(uid);
        }
        if (recoveryAgents.isEmpty()) {
            Log.w(TAG, "No recovery agent initialized for user " + mUserId);
        }
    }

    private void syncKeysForAgent(int recoveryAgentUid) {
        if (!shoudCreateSnapshot(recoveryAgentUid)) {
            Log.d(TAG, "Key sync not needed.");
            return;
        }

        if (!mSnapshotListenersStorage.hasListener(recoveryAgentUid)) {
            Log.w(TAG, "No pending intent registered for recovery agent " + recoveryAgentUid);
            return;
        }

        PublicKey publicKey = getVaultPublicKey();
        PublicKey publicKey = mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId,
                recoveryAgentUid);
        if (publicKey == null) {
            Log.w(TAG, "Not initialized for KeySync: no public key set. Cancelling task.");
            return;
@@ -163,7 +184,7 @@ public class KeySyncTask implements Runnable {

        Map<String, SecretKey> rawKeys;
        try {
            rawKeys = getKeysToSync();
            rawKeys = getKeysToSync(recoveryAgentUid);
        } catch (GeneralSecurityException e) {
            Log.e(TAG, "Failed to load recoverable keys for sync", e);
            return;
@@ -196,10 +217,19 @@ public class KeySyncTask implements Runnable {
            return;
        }

        // TODO: where do we get counter_id from here?
        Long counterId;
        // counter id is generated exactly once for each credentials value.
        if (mCredentialUpdated) {
            counterId = generateAndStoreCounterId(recoveryAgentUid);
        } else {
            counterId = mRecoverableKeyStoreDb.getCounterId(mUserId, recoveryAgentUid);
            if (counterId == null) {
                counterId = generateAndStoreCounterId(recoveryAgentUid);
            }
        }
        byte[] vaultParams = KeySyncUtils.packVaultParams(
                publicKey,
                /*counterId=*/ 1,
                counterId,
                TRUSTED_HARDWARE_MAX_ATTEMPTS,
                deviceId);

@@ -217,7 +247,7 @@ public class KeySyncTask implements Runnable {
            Log.e(TAG,"Could not encrypt with recovery key", e);
            return;
        }

        // TODO: store raw data in RecoveryServiceMetadataEntry and generate Parcelables later
        KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata(
                /*userSecretType=*/ TYPE_LOCKSCREEN,
                /*lockScreenUiFormat=*/ mCredentialType,
@@ -226,40 +256,62 @@ public class KeySyncTask implements Runnable {
        ArrayList<KeyStoreRecoveryMetadata> metadataList = new ArrayList<>();
        metadataList.add(metadata);

        // TODO: implement snapshot version
        mRecoverySnapshotStorage.put(mUserId, new KeyStoreRecoveryData(
                /*snapshotVersion=*/ 1,
        int snapshotVersion = incrementSnapshotVersion(recoveryAgentUid);

        // If application keys are not updated, snapshot will not be created on next unlock.
        mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);

        mRecoverySnapshotStorage.put(recoveryAgentUid, new KeyStoreRecoveryData(
                snapshotVersion,
                /*recoveryMetadata=*/ metadataList,
                /*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys),
                /*encryptedRecoveryKeyblob=*/ encryptedRecoveryKey));

        mSnapshotListenersStorage.recoverySnapshotAvailable(recoveryAgentUid);
    }

    private PublicKey getVaultPublicKey() {
        return mRecoverableKeyStoreDb.getRecoveryServicePublicKey(mUserId);
    @VisibleForTesting
    int incrementSnapshotVersion(int recoveryAgentUid) {
        Long snapshotVersion = mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid);
        snapshotVersion = snapshotVersion == null ? 1 : snapshotVersion + 1;
        mRecoverableKeyStoreDb.setSnapshotVersion(mUserId, recoveryAgentUid, snapshotVersion);

        return snapshotVersion.intValue();
    }

    private long generateAndStoreCounterId(int recoveryAgentUid) {
        long counter = new SecureRandom().nextLong();
        mRecoverableKeyStoreDb.setCounterId(mUserId, recoveryAgentUid, counter);
        return counter;
    }

    /**
     * Returns all of the recoverable keys for the user.
     */
    private Map<String, SecretKey> getKeysToSync()
    private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid)
            throws InsecureUserException, KeyStoreException, UnrecoverableKeyException,
            NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
        PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance();
        PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId);
        Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys(
                mUserId, decryptKey.getGenerationId());
                mUserId, recoveryAgentUid, decryptKey.getGenerationId());
        return WrappedKey.unwrapKeys(decryptKey, wrappedKeys);
    }

    /**
     * Returns {@code true} if a sync is pending.
     * @param recoveryAgentUid uid of the recovery agent.
     */
    private boolean isSyncPending() {
        // TODO: implement properly. For now just always syncing if the user has any recoverable
        // keys. We need to keep track of when the store's state actually changes.
        return !mRecoverableKeyStoreDb.getAllKeys(
                mUserId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(mUserId)).isEmpty();
    private boolean shoudCreateSnapshot(int recoveryAgentUid) {
        if (mCredentialUpdated) {
            // Sync credential if at least one snapshot was created.
            if (mRecoverableKeyStoreDb.getSnapshotVersion(mUserId, recoveryAgentUid) != null) {
                mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, true);
                return true;
            }
        }

        return mRecoverableKeyStoreDb.getShouldCreateSnapshot(mUserId, recoveryAgentUid);
    }

    /**
+1 −0
Original line number Diff line number Diff line
@@ -99,6 +99,7 @@ public class RecoverableKeyGenerator {
                            Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
        }

        mDatabase.setShouldCreateSnapshot(userId, uid, true);
        return key.getEncoded();
    }
}
+37 −7
Original line number Diff line number Diff line
@@ -174,8 +174,8 @@ public class RecoverableKeyStoreManager {
    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
            throws RemoteException {
        checkRecoverKeyStorePermission();

        KeyStoreRecoveryData snapshot = mSnapshotStorage.get(UserHandle.getCallingUserId());
        int uid = Binder.getCallingUid();
        KeyStoreRecoveryData snapshot = mSnapshotStorage.get(uid);
        if (snapshot == null) {
            throw new ServiceSpecificException(RecoverableKeyStoreLoader.ERROR_NO_SNAPSHOT_PENDING);
        }
@@ -433,7 +433,13 @@ public class RecoverableKeyStoreManager {
    }

    public void removeKey(@NonNull String alias) throws RemoteException {
        mDatabase.removeKey(Binder.getCallingUid(), alias);
        int uid = Binder.getCallingUid();
        int userId = UserHandle.getCallingUserId();

        boolean wasRemoved = mDatabase.removeKey(uid, alias);
        if (wasRemoved) {
            mDatabase.setShouldCreateSnapshot(userId, uid, true);
        }
    }

    private byte[] decryptRecoveryKey(
@@ -505,7 +511,8 @@ public class RecoverableKeyStoreManager {
                    mListenersStorage,
                    userId,
                    storedHashType,
                    credential));
                    credential,
                    /*credentialUpdated=*/ false));
        } catch (NoSuchAlgorithmException e) {
            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
        } catch (KeyStoreException e) {
@@ -515,12 +522,35 @@ public class RecoverableKeyStoreManager {
        }
    }

    /** This function can only be used inside LockSettingsService. */
    /**
     * This function can only be used inside LockSettingsService.
     * @param storedHashType from {@code CredentialHash}
     * @param credential - unencrypted String
     * @param userId for the user whose lock screen credentials were changed.
     * @hide
     */
    public void lockScreenSecretChanged(
            @KeyStoreRecoveryMetadata.LockScreenUiFormat int type,
            int storedHashType,
            @Nullable String credential,
            int userId) {
        throw new UnsupportedOperationException();
        // So as not to block the critical path unlocking the phone, defer to another thread.
        try {
            mExecutorService.execute(KeySyncTask.newInstance(
                    mContext,
                    mDatabase,
                    mSnapshotStorage,
                    mListenersStorage,
                    userId,
                    storedHashType,
                    credential,
                    /*credentialUpdated=*/ true));
        } catch (NoSuchAlgorithmException e) {
            Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
        } catch (KeyStoreException e) {
            Log.e(TAG, "Key store error encountered during recoverable key sync", e);
        } catch (InsecureUserException e) {
            Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e);
        }
    }

    private void checkRecoverKeyStorePermission() {
+174 −39

File changed.

Preview size limit exceeded, changes collapsed.

Loading