diff options
author | Paul Crowley <paulcrowley@google.com> | 2016-02-08 15:58:29 +0000 |
---|---|---|
committer | Paul Crowley <paulcrowley@google.com> | 2016-02-08 15:58:29 +0000 |
commit | faeb3eb0ba190e6d6cfe2b82ce20af587848de57 (patch) | |
tree | eb611cc0e57ef8467dfd575dc6a0d274f98d7277 | |
parent | e64f3da729ae6a00fb627f00b8a97e7c5feb8bcb (diff) |
Password security for FBE disk encryption keys
Add the means to protect FBE keys with a combination of an auth token
from Gatekeeper, and a hash of the password. Both of these must be
passed to unlock_user_key. Keys are created unprotected, and
change_user_key changes the way they are protected.
Bug: 22950892
Change-Id: Ie13bc6f82059ce941b0e664a5b60355e52b45f30
9 files changed, 151 insertions, 42 deletions
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index acc68cffaa98..6206323a89f5 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -1126,14 +1126,19 @@ public class Am extends BaseCommand { } } + private byte[] argToBytes(String arg) { + if (arg.equals("!")) { + return null; + } else { + return HexDump.hexStringToByteArray(arg); + } + } + private void runUnlockUser() throws Exception { int userId = Integer.parseInt(nextArgRequired()); - String tokenHex = nextArg(); - byte[] token = null; - if (tokenHex != null) { - token = HexDump.hexStringToByteArray(tokenHex); - } - boolean success = mAm.unlockUser(userId, token); + byte[] token = argToBytes(nextArgRequired()); + byte[] secret = argToBytes(nextArgRequired()); + boolean success = mAm.unlockUser(userId, token, secret); if (success) { System.out.println("Success: user unlocked"); } else { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index a3160f4e113c..1954774e1177 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2080,7 +2080,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); int userId = data.readInt(); byte[] token = data.createByteArray(); - boolean result = unlockUser(userId, token); + byte[] secret = data.createByteArray(); + boolean result = unlockUser(userId, token, secret); reply.writeNoException(); reply.writeInt(result ? 1 : 0); return true; @@ -5571,12 +5572,13 @@ class ActivityManagerProxy implements IActivityManager return result; } - public boolean unlockUser(int userId, byte[] token) throws RemoteException { + public boolean unlockUser(int userId, byte[] token, byte[] secret) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(userId); data.writeByteArray(token); + data.writeByteArray(secret); mRemote.transact(IActivityManager.UNLOCK_USER_TRANSACTION, data, reply, 0); reply.readException(); boolean result = reply.readInt() != 0; diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index f5e7d78a655e..b5ca6ee5a343 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -426,7 +426,7 @@ public interface IActivityManager extends IInterface { // Multi-user APIs public boolean switchUser(int userid) throws RemoteException; public boolean startUserInBackground(int userid) throws RemoteException; - public boolean unlockUser(int userid, byte[] token) throws RemoteException; + public boolean unlockUser(int userid, byte[] token, byte[] secret) throws RemoteException; public int stopUser(int userid, boolean force, IStopUserCallback callback) throws RemoteException; public UserInfo getCurrentUser() throws RemoteException; public boolean isUserRunning(int userid, int flags) throws RemoteException; diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index dd8eb5f2c634..fc440d2b446b 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -1233,7 +1233,8 @@ public interface IMountService extends IInterface { } @Override - public void unlockUserKey(int userId, int serialNumber, byte[] token) throws RemoteException { + public void changeUserKey(int userId, int serialNumber, + byte[] token, byte[] oldSecret, byte[] newSecret) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { @@ -1241,6 +1242,27 @@ public interface IMountService extends IInterface { _data.writeInt(userId); _data.writeInt(serialNumber); _data.writeByteArray(token); + _data.writeByteArray(oldSecret); + _data.writeByteArray(newSecret); + mRemote.transact(Stub.TRANSACTION_changeUserKey, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void unlockUserKey(int userId, int serialNumber, + byte[] token, byte[] secret) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(userId); + _data.writeInt(serialNumber); + _data.writeByteArray(token); + _data.writeByteArray(secret); mRemote.transact(Stub.TRANSACTION_unlockUserKey, _data, _reply, 0); _reply.readException(); } finally { @@ -1448,6 +1470,8 @@ public interface IMountService extends IInterface { static final int TRANSACTION_mountAppFuse = IBinder.FIRST_CALL_TRANSACTION + 69; + static final int TRANSACTION_changeUserKey = IBinder.FIRST_CALL_TRANSACTION + 70; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -2026,12 +2050,24 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_changeUserKey: { + data.enforceInterface(DESCRIPTOR); + int userId = data.readInt(); + int serialNumber = data.readInt(); + byte[] token = data.createByteArray(); + byte[] oldSecret = data.createByteArray(); + byte[] newSecret = data.createByteArray(); + changeUserKey(userId, serialNumber, token, oldSecret, newSecret); + reply.writeNoException(); + return true; + } case TRANSACTION_unlockUserKey: { data.enforceInterface(DESCRIPTOR); int userId = data.readInt(); int serialNumber = data.readInt(); byte[] token = data.createByteArray(); - unlockUserKey(userId, serialNumber, token); + byte[] secret = data.createByteArray(); + unlockUserKey(userId, serialNumber, token, secret); reply.writeNoException(); return true; } @@ -2383,8 +2419,11 @@ public interface IMountService extends IInterface { public void createUserKey(int userId, int serialNumber, boolean ephemeral) throws RemoteException; public void destroyUserKey(int userId) throws RemoteException; + public void changeUserKey(int userId, int serialNumber, + byte[] token, byte[] oldSecret, byte[] newSecret) throws RemoteException; - public void unlockUserKey(int userId, int serialNumber, byte[] token) throws RemoteException; + public void unlockUserKey(int userId, int serialNumber, + byte[] token, byte[] secret) throws RemoteException; public void lockUserKey(int userId) throws RemoteException; public boolean isUserKeyUnlocked(int userId) throws RemoteException; diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index b82638aa005b..e7dfbd72292a 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -991,9 +991,9 @@ public class StorageManager { } /** {@hide} */ - public void unlockUserKey(int userId, int serialNumber, byte[] token) { + public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) { try { - mMountService.unlockUserKey(userId, serialNumber, token); + mMountService.unlockUserKey(userId, serialNumber, token, secret); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index ecba0a48b493..4dbb49005c28 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -62,6 +62,10 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; import com.android.server.LockSettingsStorage.CredentialHash; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + import java.util.Arrays; import java.util.List; @@ -510,9 +514,9 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private void unlockUser(int userId, byte[] token) { + private void unlockUser(int userId, byte[] token, byte[] secret) { try { - ActivityManagerNative.getDefault().unlockUser(userId, token); + ActivityManagerNative.getDefault().unlockUser(userId, token, secret); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -560,6 +564,7 @@ public class LockSettingsService extends ILockSettings.Stub { getGateKeeperService().clearSecureUserId(userId); mStorage.writePatternHash(null, userId); setKeystorePassword(null, userId); + clearUserKeyProtection(userId); return; } @@ -573,6 +578,7 @@ public class LockSettingsService extends ILockSettings.Stub { byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId); if (enrolledHandle != null) { mStorage.writePatternHash(enrolledHandle, userId); + setUserKeyProtection(userId, pattern, verifyPattern(pattern, 0, userId)); } else { throw new RemoteException("Failed to enroll pattern"); } @@ -588,6 +594,7 @@ public class LockSettingsService extends ILockSettings.Stub { getGateKeeperService().clearSecureUserId(userId); mStorage.writePasswordHash(null, userId); setKeystorePassword(null, userId); + clearUserKeyProtection(userId); return; } @@ -601,6 +608,7 @@ public class LockSettingsService extends ILockSettings.Stub { byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId); if (enrolledHandle != null) { mStorage.writePasswordHash(enrolledHandle, userId); + setUserKeyProtection(userId, password, verifyPassword(password, 0, userId)); } else { throw new RemoteException("Failed to enroll password"); } @@ -633,6 +641,48 @@ public class LockSettingsService extends ILockSettings.Stub { return hash; } + private void setUserKeyProtection(int userId, String credential, VerifyCredentialResponse vcr) + throws RemoteException { + if (vcr == null) { + throw new RemoteException("Null response verifying a credential we just set"); + } + if (vcr.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { + throw new RemoteException("Non-OK response verifying a credential we just set: " + + vcr.getResponseCode()); + } + byte[] token = vcr.getPayload(); + if (token == null) { + throw new RemoteException("Empty payload verifying a credential we just set"); + } + changeUserKey(userId, token, secretFromCredential(credential)); + } + + private void clearUserKeyProtection(int userId) throws RemoteException { + changeUserKey(userId, null, null); + } + + private static byte[] secretFromCredential(String credential) throws RemoteException { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + // Personalize the hash + byte[] personalization = "Android FBE credential hash" + .getBytes(StandardCharsets.UTF_8); + // Pad it to the block size of the hash function + personalization = Arrays.copyOf(personalization, 128); + digest.update(personalization); + digest.update(credential.getBytes(StandardCharsets.UTF_8)); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("NoSuchAlgorithmException for SHA-512"); + } + } + + private void changeUserKey(int userId, byte[] token, byte[] secret) + throws RemoteException { + final UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId); + getMountService().changeUserKey(userId, userInfo.serialNumber, token, null, secret); + } + @Override public VerifyCredentialResponse checkPattern(String pattern, int userId) throws RemoteException { return doVerifyPattern(pattern, false, 0, userId); @@ -742,11 +792,11 @@ public class LockSettingsService extends ILockSettings.Stub { if (Arrays.equals(hash, storedHash.hash)) { unlockKeystore(credentialUtil.adjustForKeystore(credential), userId); - // TODO: pass through a meaningful token from gatekeeper to - // unlock credential keys; for now pass through a stub value to - // indicate that we came from a user challenge. - final byte[] token = String.valueOf(userId).getBytes(); - unlockUser(userId, token); + // Users with legacy credentials don't have credential-backed + // FBE keys, so just pass through a fake token/secret + Slog.i(TAG, "Unlocking user with fake token: " + userId); + final byte[] fakeToken = String.valueOf(userId).getBytes(); + unlockUser(userId, fakeToken, fakeToken); // migrate credential to GateKeeper credentialUtil.setCredential(credential, null, userId); @@ -786,11 +836,9 @@ public class LockSettingsService extends ILockSettings.Stub { // credential has matched unlockKeystore(credential, userId); - // TODO: pass through a meaningful token from gatekeeper to - // unlock credential keys; for now pass through a stub value to - // indicate that we came from a user challenge. - final byte[] token = String.valueOf(userId).getBytes(); - unlockUser(userId, token); + Slog.i(TAG, "Unlocking user " + userId + + " with token length " + response.getPayload().length); + unlockUser(userId, response.getPayload(), secretFromCredential(credential)); UserInfo info = UserManager.get(mContext).getUserInfo(userId); if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 5120e1b08f13..cbd477a9500b 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -2742,8 +2742,30 @@ class MountService extends IMountService.Stub } } + private SensitiveArg encodeBytes(byte[] bytes) { + if (ArrayUtils.isEmpty(bytes)) { + return new SensitiveArg("!"); + } else { + return new SensitiveArg(HexDump.toHexString(bytes)); + } + } + @Override - public void unlockUserKey(int userId, int serialNumber, byte[] token) { + public void changeUserKey(int userId, int serialNumber, + byte[] token, byte[] oldSecret, byte[] newSecret) { + enforcePermission(android.Manifest.permission.STORAGE_INTERNAL); + waitForReady(); + + try { + mCryptConnector.execute("cryptfs", "change_user_key", userId, serialNumber, + encodeBytes(token), encodeBytes(oldSecret), encodeBytes(newSecret)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) { enforcePermission(android.Manifest.permission.STORAGE_INTERNAL); waitForReady(); @@ -2753,16 +2775,9 @@ class MountService extends IMountService.Stub throw new IllegalStateException("Token required to unlock secure user " + userId); } - final String encodedToken; - if (ArrayUtils.isEmpty(token)) { - encodedToken = "!"; - } else { - encodedToken = HexDump.toHexString(token); - } - try { mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber, - new SensitiveArg(encodedToken)); + encodeBytes(token), encodeBytes(secret)); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5125133904c3..9dae74050812 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -20340,8 +20340,8 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public boolean unlockUser(int userId, byte[] token) { - return mUserController.unlockUser(userId, token); + public boolean unlockUser(int userId, byte[] token, byte[] secret) { + return mUserController.unlockUser(userId, token, secret); } @Override diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 2f63b2d3e9bf..a355fa4750eb 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -783,7 +783,7 @@ final class UserController { return result; } - boolean unlockUser(final int userId, byte[] token) { + boolean unlockUser(final int userId, byte[] token, byte[] secret) { if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: unlockUser() from pid=" @@ -796,7 +796,7 @@ final class UserController { final long binderToken = Binder.clearCallingIdentity(); try { - return unlockUserCleared(userId, token); + return unlockUserCleared(userId, token, secret); } finally { Binder.restoreCallingIdentity(binderToken); } @@ -810,10 +810,10 @@ final class UserController { */ boolean maybeUnlockUser(final int userId) { // Try unlocking storage using empty token - return unlockUserCleared(userId, null); + return unlockUserCleared(userId, null, null); } - boolean unlockUserCleared(final int userId, byte[] token) { + boolean unlockUserCleared(final int userId, byte[] token, byte[] secret) { synchronized (mService) { // Bail if already running unlocked final UserState uss = mStartedUsers.get(userId); @@ -824,7 +824,7 @@ final class UserController { final UserInfo userInfo = getUserInfo(userId); final IMountService mountService = getMountService(); try { - mountService.unlockUserKey(userId, userInfo.serialNumber, token); + mountService.unlockUserKey(userId, userInfo.serialNumber, token, secret); } catch (RemoteException | RuntimeException e) { Slog.w(TAG, "Failed to unlock: " + e.getMessage()); return false; |