Merge "Update RecoveryController to use KeyStore grant API."

This commit is contained in:
Dmitry Dementyev
2018-02-02 05:28:29 +00:00
committed by Android (Google) Code Review
7 changed files with 296 additions and 12 deletions

View 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();
@@ -456,6 +462,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.
*

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

View 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,

View File

@@ -16,10 +16,13 @@
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;
}
}

View 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 {

View File

@@ -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;
}
}

View 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