OSDN Git Service

Update RecoveryController to use KeyStore grant API.
authorDmitry Dementyev <dementyev@google.com>
Thu, 1 Feb 2018 00:09:32 +0000 (16:09 -0800)
committerDmitry Dementyev <dementyev@google.com>
Fri, 2 Feb 2018 01:41:18 +0000 (17:41 -0800)
Missing parts:
1) Whitelist locksettingsservice to use grant API.
2) Probably have similar update for recovered keys - they will live in
system service and RecoveryAgent will use getKey() method to access
them.
3) ApplicationKeyStorageTest

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: I584b89e3f777bed679b2eb5173750f3f1dee3635

core/java/android/security/keystore/recovery/RecoveryController.java
core/java/com/android/internal/widget/ILockSettings.aidl
services/core/java/com/android/server/locksettings/LockSettingsService.java
services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java [new file with mode: 0644]
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java

index 4e4a037..7cd08f7 100644 (file)
@@ -26,9 +26,13 @@ import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
+import android.security.KeyStore;
+import android.security.keystore.AndroidKeyStoreProvider;
 
 import com.android.internal.widget.ILockSettings;
 
+import java.security.Key;
+import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.List;
@@ -113,9 +117,11 @@ public class RecoveryController {
 
 
     private final ILockSettings mBinder;
+    private final KeyStore mKeyStore;
 
-    private RecoveryController(ILockSettings binder) {
+    private RecoveryController(ILockSettings binder, KeyStore keystore) {
         mBinder = binder;
+        mKeyStore = keystore;
     }
 
     /**
@@ -133,7 +139,7 @@ public class RecoveryController {
     public static RecoveryController getInstance(Context context) {
         ILockSettings lockSettings =
                 ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
-        return new RecoveryController(lockSettings);
+        return new RecoveryController(lockSettings, KeyStore.getInstance());
     }
 
     /**
@@ -430,6 +436,7 @@ public class RecoveryController {
     }
 
     /**
+     * Deprecated.
      * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable
      * key store. Returns the raw material of the key.
      *
@@ -444,7 +451,6 @@ public class RecoveryController {
     public byte[] generateAndStoreKey(@NonNull String alias, byte[] account)
             throws InternalRecoveryServiceException, LockScreenRequiredException {
         try {
-            // TODO: add account
             return mBinder.generateAndStoreKey(alias);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -457,6 +463,72 @@ public class RecoveryController {
     }
 
     /**
+     * Generates a AES256/GCM/NoPADDING key called {@code alias} and loads it into the recoverable
+     * key store. Returns {@link javax.crypto.SecretKey}.
+     *
+     * @param alias The key alias.
+     * @param account The account associated with the key.
+     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
+     *     service.
+     * @throws LockScreenRequiredException if the user has not set a lock screen. This is required
+     *     to generate recoverable keys, as the snapshots are encrypted using a key derived from the
+     *     lock screen.
+     * @hide
+     */
+    public Key generateKey(@NonNull String alias, byte[] account)
+            throws InternalRecoveryServiceException, LockScreenRequiredException {
+        // TODO: update RecoverySession.recoverKeys
+        try {
+            String grantAlias = mBinder.generateKey(alias, account);
+            if (grantAlias == null) {
+                return null;
+            }
+            Key result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
+                    mKeyStore,
+                    grantAlias,
+                    KeyStore.UID_SELF);
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (UnrecoverableKeyException e) {
+            throw new InternalRecoveryServiceException("Access to newly generated key failed for");
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == ERROR_INSECURE_USER) {
+                throw new LockScreenRequiredException(e.getMessage());
+            }
+            throw wrapUnexpectedServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Gets a key called {@code alias} from the recoverable key store.
+     *
+     * @param alias The key alias.
+     * @return The key.
+     * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery
+     *     service.
+     * @throws UnrecoverableKeyException if key is permanently invalidated or not found.
+     * @hide
+     */
+    public @Nullable Key getKey(@NonNull String alias)
+            throws InternalRecoveryServiceException, UnrecoverableKeyException {
+        try {
+            String grantAlias = mBinder.getKey(alias);
+            if (grantAlias == null) {
+                return null;
+            }
+            return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(
+                    mKeyStore,
+                    grantAlias,
+                    KeyStore.UID_SELF);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw wrapUnexpectedServiceSpecificException(e);
+        }
+    }
+
+    /**
      * Removes a key called {@code alias} from the recoverable key store.
      *
      * @param alias The key alias.
index 5673814..732534c 100644 (file)
@@ -66,6 +66,8 @@ interface ILockSettings {
     void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList);
     KeyChainSnapshot getKeyChainSnapshot();
     byte[] generateAndStoreKey(String alias);
+    String generateKey(String alias, in byte[] account);
+    String getKey(String alias);
     void removeKey(String alias);
     void setSnapshotCreatedPendingIntent(in PendingIntent intent);
     Map getRecoverySnapshotVersions();
index 28fa86b..2cb8c48 100644 (file)
@@ -383,8 +383,8 @@ public class LockSettingsService extends ILockSettings.Stub {
             return KeyStore.getInstance();
         }
 
-        public RecoverableKeyStoreManager getRecoverableKeyStoreManager() {
-            return RecoverableKeyStoreManager.getInstance(mContext);
+        public RecoverableKeyStoreManager getRecoverableKeyStoreManager(KeyStore keyStore) {
+            return RecoverableKeyStoreManager.getInstance(mContext, keyStore);
         }
 
         public IStorageManager getStorageManager() {
@@ -413,7 +413,7 @@ public class LockSettingsService extends ILockSettings.Stub {
         mInjector = injector;
         mContext = injector.getContext();
         mKeyStore = injector.getKeyStore();
-        mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
+        mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(mKeyStore);
         mHandler = injector.getHandler();
         mStrongAuth = injector.getStrongAuth();
         mActivityManager = injector.getActivityManager();
@@ -2064,6 +2064,16 @@ public class LockSettingsService extends ILockSettings.Stub {
         return mRecoverableKeyStoreManager.generateAndStoreKey(alias);
     }
 
+    @Override
+    public String generateKey(@NonNull String alias, byte[] account) throws RemoteException {
+        return mRecoverableKeyStoreManager.generateKey(alias, account);
+    }
+
+    @Override
+    public String getKey(@NonNull String alias) throws RemoteException {
+        return mRecoverableKeyStoreManager.getKey(alias);
+    }
+
     private static final String[] VALID_SETTINGS = new String[] {
             LockPatternUtils.LOCKOUT_PERMANENT_KEY,
             LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
index 59132da..285e722 100644 (file)
 
 package com.android.server.locksettings.recoverablekeystore;
 
+import java.io.IOException;
+import java.security.cert.CertificateException;
 import java.security.Key;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchAlgorithmException;
 import java.security.UnrecoverableKeyException;
 
 /**
@@ -27,6 +30,7 @@ import java.security.UnrecoverableKeyException;
  */
 public class KeyStoreProxyImpl implements KeyStoreProxy {
 
+    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
     private final KeyStore mKeyStore;
 
     /**
@@ -57,4 +61,21 @@ public class KeyStoreProxyImpl implements KeyStoreProxy {
     public void deleteEntry(String alias) throws KeyStoreException {
         mKeyStore.deleteEntry(alias);
     }
+
+    /**
+     * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked
+     * {@link KeyStore#load(KeyStore.LoadStoreParameter)}.
+     *
+     * @throws KeyStoreException if there was a problem getting or initializing the key store.
+     */
+    public static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException {
+        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
+        try {
+            keyStore.load(/*param=*/ null);
+        } catch (CertificateException | IOException | NoSuchAlgorithmException e) {
+            // Should never happen.
+            throw new KeyStoreException("Unable to load keystore.", e);
+        }
+        return keyStore;
+    }
 }
index ec72b22..fda6cdf 100644 (file)
@@ -37,21 +37,23 @@ import android.security.keystore.recovery.KeyChainProtectionParams;
 import android.security.keystore.recovery.KeyChainSnapshot;
 import android.security.keystore.recovery.RecoveryController;
 import android.security.keystore.recovery.WrappedApplicationKey;
+import android.security.KeyStore;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
+import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
 
 import java.security.InvalidKeyException;
-import java.security.KeyStoreException;
 import java.security.KeyFactory;
+import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
-import java.security.UnrecoverableKeyException;
 import java.security.spec.InvalidKeySpecException;
+import java.security.UnrecoverableKeyException;
 import java.security.spec.X509EncodedKeySpec;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -82,18 +84,23 @@ public class RecoverableKeyStoreManager {
     private final RecoverableKeyGenerator mRecoverableKeyGenerator;
     private final RecoverySnapshotStorage mSnapshotStorage;
     private final PlatformKeyManager mPlatformKeyManager;
+    private final KeyStore mKeyStore;
+    private final ApplicationKeyStorage mApplicationKeyStorage;
 
     /**
      * Returns a new or existing instance.
      *
      * @hide
      */
-    public static synchronized RecoverableKeyStoreManager getInstance(Context context) {
+    public static synchronized RecoverableKeyStoreManager
+            getInstance(Context context, KeyStore keystore) {
         if (mInstance == null) {
             RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
             PlatformKeyManager platformKeyManager;
+            ApplicationKeyStorage applicationKeyStorage;
             try {
                 platformKeyManager = PlatformKeyManager.getInstance(context, db);
+                applicationKeyStorage = ApplicationKeyStorage.getInstance(keystore);
             } catch (NoSuchAlgorithmException e) {
                 // Impossible: all algorithms must be supported by AOSP
                 throw new RuntimeException(e);
@@ -103,12 +110,14 @@ public class RecoverableKeyStoreManager {
 
             mInstance = new RecoverableKeyStoreManager(
                     context.getApplicationContext(),
+                    keystore,
                     db,
                     new RecoverySessionStorage(),
                     Executors.newSingleThreadExecutor(),
                     new RecoverySnapshotStorage(),
                     new RecoverySnapshotListenersStorage(),
-                    platformKeyManager);
+                    platformKeyManager,
+                    applicationKeyStorage);
         }
         return mInstance;
     }
@@ -116,19 +125,23 @@ public class RecoverableKeyStoreManager {
     @VisibleForTesting
     RecoverableKeyStoreManager(
             Context context,
+            KeyStore keystore,
             RecoverableKeyStoreDb recoverableKeyStoreDb,
             RecoverySessionStorage recoverySessionStorage,
             ExecutorService executorService,
             RecoverySnapshotStorage snapshotStorage,
             RecoverySnapshotListenersStorage listenersStorage,
-            PlatformKeyManager platformKeyManager) {
+            PlatformKeyManager platformKeyManager,
+            ApplicationKeyStorage applicationKeyStorage) {
         mContext = context;
+        mKeyStore = keystore;
         mDatabase = recoverableKeyStoreDb;
         mRecoverySessionStorage = recoverySessionStorage;
         mExecutorService = executorService;
         mListenersStorage = listenersStorage;
         mSnapshotStorage = snapshotStorage;
         mPlatformKeyManager = platformKeyManager;
+        mApplicationKeyStorage = applicationKeyStorage;
 
         try {
             mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
@@ -406,6 +419,7 @@ public class RecoverableKeyStoreManager {
     }
 
     /**
+     * Deprecated
      * Generates a key named {@code alias} in the recoverable store for the calling uid. Then
      * returns the raw key material.
      *
@@ -450,9 +464,55 @@ public class RecoverableKeyStoreManager {
         boolean wasRemoved = mDatabase.removeKey(uid, alias);
         if (wasRemoved) {
             mDatabase.setShouldCreateSnapshot(userId, uid, true);
+            mApplicationKeyStorage.deleteEntry(userId, uid, alias);
         }
     }
 
+    /**
+     * Generates a key named {@code alias} in caller's namespace.
+     * The key is stored in system service keystore namespace.
+     *
+     * @return grant alias, which caller can use to access the key.
+     */
+    public String generateKey(@NonNull String alias, byte[] account) throws RemoteException {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+
+        PlatformEncryptionKey encryptionKey;
+        try {
+            encryptionKey = mPlatformKeyManager.getEncryptKey(userId);
+        } catch (NoSuchAlgorithmException e) {
+            // Impossible: all algorithms must be supported by AOSP
+            throw new RuntimeException(e);
+        } catch (KeyStoreException | UnrecoverableKeyException e) {
+            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        } catch (InsecureUserException e) {
+            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
+        }
+
+        try {
+            byte[] secretKey =
+                    mRecoverableKeyGenerator.generateAndStoreKey(encryptionKey, userId, uid, alias);
+            mApplicationKeyStorage.setSymmetricKeyEntry(userId, uid, alias, secretKey);
+            String grantAlias = mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
+            return grantAlias;
+        } catch (KeyStoreException | InvalidKeyException | RecoverableKeyStorageException e) {
+            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        }
+    }
+
+    /**
+     * Gets a key named {@code alias} in caller's namespace.
+     *
+     * @return grant alias, which caller can use to access the key.
+     */
+    public String getKey(@NonNull String alias) throws RemoteException {
+        int uid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
+        String grantAlias = mApplicationKeyStorage.getGrantAlias(userId, uid, alias);
+        return grantAlias;
+    }
+
     private byte[] decryptRecoveryKey(
             RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
             throws RemoteException, ServiceSpecificException {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
new file mode 100644 (file)
index 0000000..600a534
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 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.recoverablekeystore.storage;
+
+import static android.security.keystore.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR;
+
+import android.annotation.Nullable;
+import android.os.ServiceSpecificException;
+import android.security.Credentials;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.security.keystore.recovery.KeyChainSnapshot;
+import android.security.KeyStore;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.KeyStoreProxy;
+import com.android.server.locksettings.recoverablekeystore.KeyStoreProxyImpl;
+
+import java.security.KeyStore.SecretKeyEntry;
+import java.security.KeyStoreException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Storage for Application keys in LockSettings service KeyStore namespace.
+ *
+ * <p> Uses KeyStore's grant mechanism to make keys usable by application process without
+ * revealing key material
+ */
+public class ApplicationKeyStorage {
+    private static final String APPLICATION_KEY_ALIAS_PREFIX =
+            "com.android.server.locksettings.recoverablekeystore/application/";
+
+    KeyStoreProxy mKeyStore;
+    KeyStore mKeystoreService;
+
+    public static ApplicationKeyStorage getInstance(KeyStore keystoreService)
+            throws KeyStoreException {
+        return new ApplicationKeyStorage(
+                new KeyStoreProxyImpl(KeyStoreProxyImpl.getAndLoadAndroidKeyStore()),
+                keystoreService);
+    }
+
+    @VisibleForTesting
+    ApplicationKeyStorage(KeyStoreProxy keyStore, KeyStore keystoreService) {
+        mKeyStore = keyStore;
+        mKeystoreService = keystoreService;
+    }
+
+    /**
+     * Returns grant alias, valid in Applications namespace.
+     */
+    public @Nullable String getGrantAlias(int userId, int uid, String alias) {
+        // Aliases used by {@link KeyStore} are different than used by public API.
+        // {@code USER_PRIVATE_KEY} prefix is used secret keys.
+        String keystoreAlias = Credentials.USER_PRIVATE_KEY + getInternalAlias(userId, uid, alias);
+        return mKeystoreService.grant(keystoreAlias, uid);
+    }
+
+    public void setSymmetricKeyEntry(int userId, int uid, String alias, byte[] secretKey)
+            throws KeyStoreException {
+        try {
+            mKeyStore.setEntry(
+                getInternalAlias(userId, uid, alias),
+                new SecretKeyEntry(
+                    new SecretKeySpec(secretKey, KeyProperties.KEY_ALGORITHM_AES)),
+                new KeyProtection.Builder(
+                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                    .build());
+        } catch (KeyStoreException e) {
+            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        }
+    }
+
+    public void deleteEntry(int userId, int uid, String alias) {
+        try {
+            mKeyStore.deleteEntry(getInternalAlias(userId, uid, alias));
+        } catch (KeyStoreException e) {
+            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        }
+    }
+
+    /**
+     * Returns the alias in locksettins service's KeyStore namespace used for given application key.
+     *
+     * <p>These IDs look as follows:
+     * {@code com.security.recoverablekeystore/application/<userId>/<uid>/<alias>}
+     *
+     * @param userId The ID of the user
+     * @param uid The uid
+     * @param alias - alias in application's namespace
+     * @return The alias.
+     */
+    private String getInternalAlias(int userId, int uid, String alias) {
+        return APPLICATION_KEY_ALIAS_PREFIX + userId + "/" + uid + "/" + alias;
+    }
+}
index c863aab..473a813 100644 (file)
@@ -39,6 +39,7 @@ import android.Manifest;
 import android.os.Binder;
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
+import android.security.KeyStore;
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
@@ -49,6 +50,7 @@ import android.support.test.filters.SmallTest;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -135,6 +137,8 @@ public class RecoverableKeyStoreManagerTest {
     @Mock private RecoverySnapshotListenersStorage mMockListenersStorage;
     @Mock private KeyguardManager mKeyguardManager;
     @Mock private PlatformKeyManager mPlatformKeyManager;
+    @Mock private KeyStore mKeyStore;
+    @Mock private ApplicationKeyStorage mApplicationKeyStorage;
 
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
     private File mDatabaseFile;
@@ -164,12 +168,14 @@ public class RecoverableKeyStoreManagerTest {
 
         mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
                 mMockContext,
+                mKeyStore,
                 mRecoverableKeyStoreDb,
                 mRecoverySessionStorage,
                 Executors.newSingleThreadExecutor(),
                 mRecoverySnapshotStorage,
                 mMockListenersStorage,
-                mPlatformKeyManager);
+                mPlatformKeyManager,
+                mApplicationKeyStorage);
     }
 
     @After