diff options
| author | 2019-01-03 14:04:58 -0800 | |
|---|---|---|
| committer | 2019-01-17 17:38:19 -0800 | |
| commit | c704834cb65eee08282ba2a53be2d15cecbdd866 (patch) | |
| tree | 3388347d0b9537da4f7051311f62efa1146a32d6 | |
| parent | 252e8d0447eb64a1429a1198ff71f3785db2d2aa (diff) | |
Add an optional metadata blob for recoverable application keys
This metadata, if present, will be authenticated (but unencrypted)
together with the application key material.
Bug: 112191661
Test: atest FrameworksCoreTests:android.security.keystore.recovery
atest FrameworksServicesTests:com.android.server.locksettings.recoverablekeystore
atest -m RecoveryControllerHostTest RecoverableKeyStoreEndtoEndHostTest RecoverySessionHostTest
Change-Id: I2846952758a2c1a7b1f0849e1adda1f05a3e305e
12 files changed, 358 insertions, 42 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 7f3c152364b0..4fece4057723 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5092,7 +5092,8 @@ package android.security.keystore.recovery { public class RecoveryController { method public android.security.keystore.recovery.RecoverySession createRecoverySession(); - method public java.security.Key generateKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException; + method public deprecated java.security.Key generateKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException; + method public java.security.Key generateKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException; method public java.util.List<java.lang.String> getAliases() throws android.security.keystore.recovery.InternalRecoveryServiceException; method public static android.security.keystore.recovery.RecoveryController getInstance(android.content.Context); method public java.security.Key getKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException, java.security.UnrecoverableKeyException; @@ -5100,7 +5101,8 @@ package android.security.keystore.recovery { method public int[] getRecoverySecretTypes() throws android.security.keystore.recovery.InternalRecoveryServiceException; method public int getRecoveryStatus(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException; method public java.util.Map<java.lang.String, java.security.cert.X509Certificate> getRootCertificates(); - method public java.security.Key importKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException; + method public deprecated java.security.Key importKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException; + method public java.security.Key importKey(java.lang.String, byte[], byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException; method public void initRecoveryService(java.lang.String, byte[], byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException; method public static boolean isRecoverableKeyStoreEnabled(android.content.Context); method public void removeKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException; @@ -5127,6 +5129,7 @@ package android.security.keystore.recovery { method public int describeContents(); method public java.lang.String getAlias(); method public byte[] getEncryptedKeyMaterial(); + method public byte[] getMetadata(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.security.keystore.recovery.WrappedApplicationKey> CREATOR; } @@ -5136,6 +5139,7 @@ package android.security.keystore.recovery { method public android.security.keystore.recovery.WrappedApplicationKey build(); method public android.security.keystore.recovery.WrappedApplicationKey.Builder setAlias(java.lang.String); method public android.security.keystore.recovery.WrappedApplicationKey.Builder setEncryptedKeyMaterial(byte[]); + method public android.security.keystore.recovery.WrappedApplicationKey.Builder setMetadata(byte[]); } } diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java index 31a5962c7e9a..c43a6668b9c3 100644 --- a/core/java/android/security/keystore/recovery/RecoveryController.java +++ b/core/java/android/security/keystore/recovery/RecoveryController.java @@ -533,7 +533,10 @@ public class RecoveryController { * service. * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock * screen is required to generate recoverable keys. + * + * @deprecated Use the method {@link #generateKey(String, byte[])} instead. */ + @Deprecated @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) public @NonNull Key generateKey(@NonNull String alias) throws InternalRecoveryServiceException, LockScreenRequiredException { @@ -556,6 +559,47 @@ public class RecoveryController { } /** + * Generates a recoverable key with the given {@code alias} and {@code metadata}. + * + * <p>The metadata should contain any data that needs to be cryptographically bound to the + * generated key, but does not need to be encrypted by the key. For example, the metadata can + * be a byte string describing the algorithms and non-secret parameters to be used with the + * key. The supplied metadata can later be obtained via + * {@link WrappedApplicationKey#getMetadata()}. + * + * <p>During the key recovery process, the same metadata has to be supplied via + * {@link WrappedApplicationKey.Builder#setMetadata(byte[])}; otherwise, the recovery process + * will fail due to the checking of the cryptographic binding. This can help prevent + * potential attacks that try to swap key materials on the backup server and trick the + * application to use keys with different algorithms or parameters. + * + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock + * screen is required to generate recoverable keys. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public @NonNull Key generateKey(@NonNull String alias, @Nullable byte[] metadata) + throws InternalRecoveryServiceException, LockScreenRequiredException { + try { + String grantAlias = mBinder.generateKeyWithMetadata(alias, metadata); + if (grantAlias == null) { + throw new InternalRecoveryServiceException("null grant alias"); + } + return getKeyFromGrant(grantAlias); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (UnrecoverableKeyException e) { + throw new InternalRecoveryServiceException("Failed to get key from keystore", e); + } catch (ServiceSpecificException e) { + if (e.errorCode == ERROR_INSECURE_USER) { + throw new LockScreenRequiredException(e.getMessage()); + } + throw wrapUnexpectedServiceSpecificException(e); + } + } + + /** * Imports a 256-bit recoverable AES key with the given {@code alias} and the raw bytes {@code * keyBytes}. * @@ -564,7 +608,9 @@ public class RecoveryController { * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock * screen is required to generate recoverable keys. * + * @deprecated Use the method {@link #importKey(String, byte[], byte[])} instead. */ + @Deprecated @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) public @NonNull Key importKey(@NonNull String alias, @NonNull byte[] keyBytes) throws InternalRecoveryServiceException, LockScreenRequiredException { @@ -587,6 +633,49 @@ public class RecoveryController { } /** + * Imports a recoverable 256-bit AES key with the given {@code alias}, the raw bytes {@code + * keyBytes}, and the {@code metadata}. + * + * <p>The metadata should contain any data that needs to be cryptographically bound to the + * imported key, but does not need to be encrypted by the key. For example, the metadata can + * be a byte string describing the algorithms and non-secret parameters to be used with the + * key. The supplied metadata can later be obtained via + * {@link WrappedApplicationKey#getMetadata()}. + * + * <p>During the key recovery process, the same metadata has to be supplied via + * {@link WrappedApplicationKey.Builder#setMetadata(byte[])}; otherwise, the recovery process + * will fail due to the checking of the cryptographic binding. This can help prevent + * potential attacks that try to swap key materials on the backup server and trick the + * application to use keys with different algorithms or parameters. + * + * @throws InternalRecoveryServiceException if an unexpected error occurred in the recovery + * service. + * @throws LockScreenRequiredException if the user does not have a lock screen set. A lock + * screen is required to generate recoverable keys. + */ + @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE) + public @NonNull Key importKey(@NonNull String alias, @NonNull byte[] keyBytes, + @Nullable byte[] metadata) + throws InternalRecoveryServiceException, LockScreenRequiredException { + try { + String grantAlias = mBinder.importKeyWithMetadata(alias, keyBytes, metadata); + if (grantAlias == null) { + throw new InternalRecoveryServiceException("Null grant alias"); + } + return getKeyFromGrant(grantAlias); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (UnrecoverableKeyException e) { + throw new InternalRecoveryServiceException("Failed to get key from keystore", e); + } 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. diff --git a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java index ae4448f9c908..dbfd655953d6 100644 --- a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java +++ b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java @@ -17,6 +17,7 @@ package android.security.keystore.recovery; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -41,6 +42,8 @@ public final class WrappedApplicationKey implements Parcelable { private String mAlias; // The only supported format is AES-256 symmetric key. private byte[] mEncryptedKeyMaterial; + // The optional metadata that's authenticated (but unencrypted) with the key material. + private byte[] mMetadata; // IMPORTANT! PLEASE READ! // ----------------------- @@ -80,13 +83,23 @@ public final class WrappedApplicationKey implements Parcelable { * @param encryptedKeyMaterial The key material * @return This builder */ - public Builder setEncryptedKeyMaterial(@NonNull byte[] encryptedKeyMaterial) { mInstance.mEncryptedKeyMaterial = encryptedKeyMaterial; return this; } /** + * Sets the metadata that is authenticated (but unecrypted) with the key material. + * + * @param metadata The metadata + * @return This builder + */ + public Builder setMetadata(@Nullable byte[] metadata) { + mInstance.mMetadata = metadata; + return this; + } + + /** * Creates a new {@link WrappedApplicationKey} instance. * * @return new instance @@ -102,9 +115,10 @@ public final class WrappedApplicationKey implements Parcelable { private WrappedApplicationKey() { } /** - * Deprecated - consider using Builder. + * @deprecated Use the builder instead. * @hide */ + @Deprecated public WrappedApplicationKey(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) { mAlias = Preconditions.checkNotNull(alias); mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial); @@ -124,6 +138,11 @@ public final class WrappedApplicationKey implements Parcelable { return mEncryptedKeyMaterial; } + /** The metadata with the key. */ + public @Nullable byte[] getMetadata() { + return mMetadata; + } + public static final Parcelable.Creator<WrappedApplicationKey> CREATOR = new Parcelable.Creator<WrappedApplicationKey>() { public WrappedApplicationKey createFromParcel(Parcel in) { @@ -139,6 +158,7 @@ public final class WrappedApplicationKey implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeString(mAlias); out.writeByteArray(mEncryptedKeyMaterial); + out.writeByteArray(mMetadata); } /** @@ -147,6 +167,10 @@ public final class WrappedApplicationKey implements Parcelable { protected WrappedApplicationKey(Parcel in) { mAlias = in.readString(); mEncryptedKeyMaterial = in.createByteArray(); + // Check if there is still data to be read. + if (in.dataAvail() > 0) { + mMetadata = in.createByteArray(); + } } @Override diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 591f15fd5676..9a77802aa541 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -62,7 +62,9 @@ interface ILockSettings { in byte[] recoveryServiceCertFile, in byte[] recoveryServiceSigFile); KeyChainSnapshot getKeyChainSnapshot(); String generateKey(String alias); + String generateKeyWithMetadata(String alias, in byte[] metadata); String importKey(String alias, in byte[] keyBytes); + String importKeyWithMetadata(String alias, in byte[] keyBytes, in byte[] metadata); String getKey(String alias); void removeKey(String alias); void setSnapshotCreatedPendingIntent(in PendingIntent intent); diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java index 61ab1526abc4..f78b0e1348b0 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java @@ -44,6 +44,7 @@ public class KeyChainSnapshotTest { private static final int USER_SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN; private static final String KEY_ALIAS = "steph"; private static final byte[] KEY_MATERIAL = new byte[] { 3, 5, 7, 9, 1 }; + private static final byte[] KEY_METADATA = new byte[] { 5, 3, 11, 13 }; private static final CertPath CERT_PATH = TestData.getThmCertPath(); @Test @@ -99,6 +100,7 @@ public class KeyChainSnapshotTest { WrappedApplicationKey wrappedApplicationKey = snapshot.getWrappedApplicationKeys().get(0); assertEquals(KEY_ALIAS, wrappedApplicationKey.getAlias()); assertArrayEquals(KEY_MATERIAL, wrappedApplicationKey.getEncryptedKeyMaterial()); + assertArrayEquals(KEY_METADATA, wrappedApplicationKey.getMetadata()); } @Test @@ -165,6 +167,7 @@ public class KeyChainSnapshotTest { WrappedApplicationKey wrappedApplicationKey = snapshot.getWrappedApplicationKeys().get(0); assertEquals(KEY_ALIAS, wrappedApplicationKey.getAlias()); assertArrayEquals(KEY_MATERIAL, wrappedApplicationKey.getEncryptedKeyMaterial()); + assertArrayEquals(KEY_METADATA, wrappedApplicationKey.getMetadata()); } private static KeyChainSnapshot createKeyChainSnapshot() throws Exception { @@ -196,6 +199,7 @@ public class KeyChainSnapshotTest { return new WrappedApplicationKey.Builder() .setAlias(KEY_ALIAS) .setEncryptedKeyMaterial(KEY_MATERIAL) + .setMetadata(KEY_METADATA) .build(); } diff --git a/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java b/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java index 15afbddf6f02..fabc43271bc2 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java @@ -34,6 +34,7 @@ public class WrappedApplicationKeyTest { private static final String ALIAS = "karlin"; private static final byte[] KEY_MATERIAL = new byte[] { 0, 1, 2, 3, 4 }; + private static final byte[] METADATA = new byte[] {3, 2, 1, 0}; private Parcel mParcel; @@ -58,8 +59,18 @@ public class WrappedApplicationKeyTest { } @Test + public void build_setsMetadata_nonNull() { + assertArrayEquals(METADATA, buildTestKeyWithMetadata(METADATA).getMetadata()); + } + + @Test + public void build_setsMetadata_null() { + assertArrayEquals(null, buildTestKeyWithMetadata(null).getMetadata()); + } + + @Test public void writeToParcel_writesAliasToParcel() { - buildTestKey().writeToParcel(mParcel, /*flags=*/ 0); + buildTestKeyWithMetadata(/*metadata=*/ null).writeToParcel(mParcel, /*flags=*/ 0); mParcel.setDataPosition(0); WrappedApplicationKey readFromParcel = @@ -69,7 +80,7 @@ public class WrappedApplicationKeyTest { @Test public void writeToParcel_writesKeyMaterial() { - buildTestKey().writeToParcel(mParcel, /*flags=*/ 0); + buildTestKeyWithMetadata(/*metadata=*/ null).writeToParcel(mParcel, /*flags=*/ 0); mParcel.setDataPosition(0); WrappedApplicationKey readFromParcel = @@ -77,10 +88,48 @@ public class WrappedApplicationKeyTest { assertArrayEquals(KEY_MATERIAL, readFromParcel.getEncryptedKeyMaterial()); } + @Test + public void writeToParcel_writesMetadata_nonNull() { + buildTestKeyWithMetadata(METADATA).writeToParcel(mParcel, /*flags=*/ 0); + + mParcel.setDataPosition(0); + WrappedApplicationKey readFromParcel = + WrappedApplicationKey.CREATOR.createFromParcel(mParcel); + assertArrayEquals(METADATA, readFromParcel.getMetadata()); + } + + @Test + public void writeToParcel_writesMetadata_null() { + buildTestKeyWithMetadata(/*metadata=*/ null).writeToParcel(mParcel, /*flags=*/ 0); + + mParcel.setDataPosition(0); + WrappedApplicationKey readFromParcel = + WrappedApplicationKey.CREATOR.createFromParcel(mParcel); + assertArrayEquals(null, readFromParcel.getMetadata()); + } + + @Test + public void writeToParcel_writesMetadata_absent() { + buildTestKey().writeToParcel(mParcel, /*flags=*/ 0); + + mParcel.setDataPosition(0); + WrappedApplicationKey readFromParcel = + WrappedApplicationKey.CREATOR.createFromParcel(mParcel); + assertArrayEquals(null, readFromParcel.getMetadata()); + } + private WrappedApplicationKey buildTestKey() { return new WrappedApplicationKey.Builder() .setAlias(ALIAS) .setEncryptedKeyMaterial(KEY_MATERIAL) .build(); } + + private WrappedApplicationKey buildTestKeyWithMetadata(byte[] metadata) { + return new WrappedApplicationKey.Builder() + .setAlias(ALIAS) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setMetadata(metadata) + .build(); + } } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index ea8c7922d831..8734ceb614a9 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -21,10 +21,10 @@ import static android.Manifest.permission.READ_CONTACTS; import static android.content.Context.KEYGUARD_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY; import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.USER_FRP; import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled; import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential; @@ -81,9 +81,9 @@ import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.security.keystore.UserNotAuthenticatedException; import android.security.keystore.recovery.KeyChainProtectionParams; +import android.security.keystore.recovery.KeyChainSnapshot; import android.security.keystore.recovery.RecoveryCertPath; import android.security.keystore.recovery.WrappedApplicationKey; -import android.security.keystore.recovery.KeyChainSnapshot; import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; @@ -109,9 +109,9 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.locksettings.LockSettingsStorage.CredentialHash; import com.android.server.locksettings.LockSettingsStorage.PersistentData; -import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; +import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import com.android.server.wm.WindowManagerInternal; import libcore.util.HexEncoding; @@ -130,8 +130,8 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -2103,11 +2103,24 @@ public class LockSettingsService extends ILockSettings.Stub { } @Override - public @Nullable String importKey(@NonNull String alias, byte[] keyBytes) throws RemoteException { + public @Nullable String generateKeyWithMetadata( + @NonNull String alias, @Nullable byte[] metadata) throws RemoteException { + return mRecoverableKeyStoreManager.generateKeyWithMetadata(alias, metadata); + } + + @Override + public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes) + throws RemoteException { return mRecoverableKeyStoreManager.importKey(alias, keyBytes); } @Override + public @Nullable String importKeyWithMetadata(@NonNull String alias, @NonNull byte[] keyBytes, + @Nullable byte[] metadata) throws RemoteException { + return mRecoverableKeyStoreManager.importKeyWithMetadata(alias, keyBytes, metadata); + } + + @Override public @Nullable String getKey(@NonNull String alias) throws RemoteException { return mRecoverableKeyStoreManager.getKey(alias); } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index fc5184d1438f..81d219c2d626 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -20,8 +20,8 @@ import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CE import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED; import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE; import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER; -import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; +import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING; import static android.security.keystore.recovery.RecoveryController.ERROR_SERVICE_INTERNAL_ERROR; import static android.security.keystore.recovery.RecoveryController.ERROR_SESSION_EXPIRED; @@ -35,12 +35,12 @@ import android.os.Binder; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; +import android.security.KeyStore; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; import android.security.keystore.recovery.RecoveryCertPath; import android.security.keystore.recovery.RecoveryController; import android.security.keystore.recovery.WrappedApplicationKey; -import android.security.KeyStore; import android.util.ArrayMap; import android.util.Log; @@ -59,7 +59,6 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnaps import java.io.IOException; import java.security.InvalidKeyException; -import java.security.KeyFactory; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; @@ -70,7 +69,6 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -674,14 +672,36 @@ public class RecoverableKeyStoreManager { * Generates a key named {@code alias} in caller's namespace. * The key is stored in system service keystore namespace. * + * @param alias the alias provided by caller as a reference to the key. * @return grant alias, which caller can use to access the key. + * @throws RemoteException if certain internal errors occur. + * + * @deprecated Use {@link #generateKeyWithMetadata(String, byte[])} instead. */ + @Deprecated public String generateKey(@NonNull String alias) throws RemoteException { + return generateKeyWithMetadata(alias, /*metadata=*/ null); + } + + /** + * Generates a key named {@code alias} with the {@code metadata} in caller's namespace. + * The key is stored in system service keystore namespace. + * + * @param alias the alias provided by caller as a reference to the key. + * @param metadata the optional metadata blob that will authenticated (but unencrypted) together + * with the key material when the key is uploaded to cloud. + * @return grant alias, which caller can use to access the key. + * @throws RemoteException if certain internal errors occur. + */ + public String generateKeyWithMetadata(@NonNull String alias, @Nullable byte[] metadata) + throws RemoteException { checkRecoverKeyStorePermission(); Preconditions.checkNotNull(alias, "alias is null"); int uid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); + // TODO: Include metadata in the processes of authentication and storage + PlatformEncryptionKey encryptionKey; try { encryptionKey = mPlatformKeyManager.getEncryptKey(userId); @@ -713,10 +733,30 @@ public class RecoverableKeyStoreManager { * @return grant alias, which caller can use to access the key. * @throws RemoteException if the given key is invalid or some internal errors occur. * + * @deprecated Use {{@link #importKeyWithMetadata(String, byte[], byte[])}} instead. + * * @hide */ + @Deprecated public @Nullable String importKey(@NonNull String alias, @NonNull byte[] keyBytes) throws RemoteException { + return importKeyWithMetadata(alias, keyBytes, /*metadata=*/ null); + } + + /** + * Imports a 256-bit AES-GCM key named {@code alias} with the given {@code metadata}. The key is + * stored in system service keystore namespace. + * + * @param alias the alias provided by caller as a reference to the key. + * @param keyBytes the raw bytes of the 256-bit AES key. + * @param metadata the metadata to be authenticated (but unencrypted) together with the key. + * @return grant alias, which caller can use to access the key. + * @throws RemoteException if the given key is invalid or some internal errors occur. + * + * @hide + */ + public @Nullable String importKeyWithMetadata(@NonNull String alias, @NonNull byte[] keyBytes, + @Nullable byte[] metadata) throws RemoteException { checkRecoverKeyStorePermission(); Preconditions.checkNotNull(alias, "alias is null"); Preconditions.checkNotNull(keyBytes, "keyBytes is null"); @@ -728,6 +768,8 @@ public class RecoverableKeyStoreManager { + " bits."); } + // TODO: Include metadata in the processes of authentication and storage + int uid = Binder.getCallingUid(); int userId = UserHandle.getCallingUserId(); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java index b486834235dc..8a19d62de0b9 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java @@ -23,19 +23,18 @@ import static com.android.server.locksettings.recoverablekeystore.serialization. import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS; - -import static com.android.server.locksettings.recoverablekeystore.serialization - .KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID; -import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_METADATA; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION; @@ -49,6 +48,9 @@ import android.security.keystore.recovery.WrappedApplicationKey; import android.util.Base64; import android.util.Xml; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -59,9 +61,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - /** * Deserializes a {@link android.security.keystore.recovery.KeyChainSnapshot} instance from XML. */ @@ -191,6 +190,10 @@ public class KeyChainSnapshotDeserializer { builder.setEncryptedKeyMaterial(readBlobTag(parser, TAG_KEY_MATERIAL)); break; + case TAG_KEY_METADATA: + builder.setMetadata(readBlobTag(parser, TAG_KEY_METADATA)); + break; + default: throw new KeyChainSnapshotParserException(String.format( Locale.US, "Unexpected tag %s in wrappedApplicationKey", name)); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java index 0f2c2fc6c959..8f85a27d4690 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java @@ -52,6 +52,7 @@ class KeyChainSnapshotSchema { static final String TAG_APPLICATION_KEY = "applicationKey"; static final String TAG_ALIAS = "alias"; static final String TAG_KEY_MATERIAL = "keyMaterial"; + static final String TAG_KEY_METADATA = "keyMetadata"; // Statics only private KeyChainSnapshotSchema() {} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java index 235df698a674..527e879a198b 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java @@ -24,25 +24,24 @@ import static com.android.server.locksettings.recoverablekeystore.serialization. import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS; - -import static com.android.server.locksettings.recoverablekeystore.serialization - .KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID; -import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_METADATA; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY; +import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_TRUSTED_HARDWARE_CERT_PATH; import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_USER_SECRET_TYPE; +import android.annotation.Nullable; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; import android.security.keystore.recovery.KeyDerivationParams; @@ -103,6 +102,7 @@ public class KeyChainSnapshotSerializer { XmlSerializer xmlSerializer, WrappedApplicationKey applicationKey) throws IOException { writePropertyTag(xmlSerializer, TAG_ALIAS, applicationKey.getAlias()); writePropertyTag(xmlSerializer, TAG_KEY_MATERIAL, applicationKey.getEncryptedKeyMaterial()); + writePropertyTag(xmlSerializer, TAG_KEY_METADATA, applicationKey.getMetadata()); } private static void writeKeyChainProtectionParams( @@ -181,8 +181,11 @@ public class KeyChainSnapshotSerializer { } private static void writePropertyTag( - XmlSerializer xmlSerializer, String propertyName, byte[] propertyValue) + XmlSerializer xmlSerializer, String propertyName, @Nullable byte[] propertyValue) throws IOException { + if (propertyValue == null) { + return; + } xmlSerializer.startTag(NAMESPACE, propertyName); xmlSerializer.text(Base64.encodeToString(propertyValue, /*flags=*/ Base64.DEFAULT)); xmlSerializer.endTag(NAMESPACE, propertyName); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java index 880255d79eb7..9c03df8e4369 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java @@ -55,12 +55,15 @@ public class KeyChainSnapshotSerializerTest { private static final String TEST_KEY_1_ALIAS = "key1"; private static final byte[] TEST_KEY_1_BYTES = new byte[] { 66, 77, 88 }; + private static final byte[] TEST_KEY_1_METADATA = new byte[] { 89, 87 }; private static final String TEST_KEY_2_ALIAS = "key2"; private static final byte[] TEST_KEY_2_BYTES = new byte[] { 99, 33, 11 }; + private static final byte[] TEST_KEY_2_METADATA = new byte[] {}; private static final String TEST_KEY_3_ALIAS = "key3"; private static final byte[] TEST_KEY_3_BYTES = new byte[] { 2, 8, 100 }; + private static final byte[] TEST_KEY_3_METADATA = new byte[] { 121 }; @Test public void roundTrip_persistsCounterId() throws Exception { @@ -144,6 +147,17 @@ public class KeyChainSnapshotSerializerTest { } @Test + public void roundTripKeys_0_persistsKeyMetadata_absent() throws Exception { + assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(0).getMetadata()).isEqualTo(null); + } + + @Test + public void roundTripKeys_0_persistsKeyMetadata_present() throws Exception { + assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(0).getMetadata()) + .isEqualTo(TEST_KEY_1_METADATA); + } + + @Test public void roundTripKeys_1_persistsAlias() throws Exception { assertThat(roundTripKeys().get(1).getAlias()).isEqualTo(TEST_KEY_2_ALIAS); } @@ -154,6 +168,17 @@ public class KeyChainSnapshotSerializerTest { } @Test + public void roundTripKeys_1_persistsKeyMetadata_absent() throws Exception { + assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(1).getMetadata()).isEqualTo(null); + } + + @Test + public void roundTripKeys_1_persistsKeyMetadata_present() throws Exception { + assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(1).getMetadata()) + .isEqualTo(TEST_KEY_2_METADATA); + } + + @Test public void roundTripKeys_2_persistsAlias() throws Exception { assertThat(roundTripKeys().get(2).getAlias()).isEqualTo(TEST_KEY_3_ALIAS); } @@ -164,28 +189,74 @@ public class KeyChainSnapshotSerializerTest { } @Test - public void serialize_doesNotThrowForTestSnapshot() throws Exception { + public void roundTripKeys_2_persistsKeyMetadata_absent() throws Exception { + assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(2).getMetadata()).isEqualTo(null); + } + + @Test + public void roundTripKeys_2_persistsKeyMetadata_present() throws Exception { + assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(2).getMetadata()) + .isEqualTo(TEST_KEY_3_METADATA); + } + + @Test + public void serialize_doesNotThrowForTestSnapshotWithoutKeyMetadata() throws Exception { KeyChainSnapshotSerializer.serialize( - createTestKeyChainSnapshot(), new ByteArrayOutputStream()); + createTestKeyChainSnapshot(/*withKeyMetadata=*/ false), + new ByteArrayOutputStream()); + } + + @Test + public void serialize_doesNotThrowForTestSnapshotWithKeyMetadata() throws Exception { + KeyChainSnapshotSerializer.serialize( + createTestKeyChainSnapshotWithKeyMetadata(), new ByteArrayOutputStream()); } private static List<WrappedApplicationKey> roundTripKeys() throws Exception { return roundTrip().getWrappedApplicationKeys(); } + private static List<WrappedApplicationKey> roundTripKeys(boolean withKeyMetadata) + throws Exception { + return roundTrip(withKeyMetadata).getWrappedApplicationKeys(); + } + private static KeyChainProtectionParams roundTripParams() throws Exception { - return roundTrip().getKeyChainProtectionParams().get(0); + return roundTrip(/*withKeyMetadata=*/ false).getKeyChainProtectionParams().get(0); } public static KeyChainSnapshot roundTrip() throws Exception { - KeyChainSnapshot snapshot = createTestKeyChainSnapshot(); + return roundTrip(/*withKeyMetadata=*/ false); + } + + public static KeyChainSnapshot roundTrip(boolean withKeyMetadata) throws Exception { + KeyChainSnapshot snapshot = createTestKeyChainSnapshot(withKeyMetadata); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); KeyChainSnapshotSerializer.serialize(snapshot, byteArrayOutputStream); return KeyChainSnapshotDeserializer.deserialize( new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); } - private static KeyChainSnapshot createTestKeyChainSnapshot() throws Exception { + private static KeyChainSnapshot createTestKeyChainSnapshot(boolean withKeyMetadata) + throws Exception { + KeyChainSnapshot.Builder builder = new KeyChainSnapshot.Builder() + .setCounterId(COUNTER_ID) + .setSnapshotVersion(SNAPSHOT_VERSION) + .setServerParams(SERVER_PARAMS) + .setMaxAttempts(MAX_ATTEMPTS) + .setEncryptedRecoveryKeyBlob(KEY_BLOB) + .setKeyChainProtectionParams(createKeyChainProtectionParamsList()) + .setTrustedHardwareCertPath(CERT_PATH); + if (withKeyMetadata) { + builder.setWrappedApplicationKeys(createKeysWithMetadata()); + } else { + builder.setWrappedApplicationKeys(createKeysWithoutMetadata()); + } + return builder.build(); + } + + private static KeyChainSnapshot createTestKeyChainSnapshotWithKeyMetadata() + throws Exception { return new KeyChainSnapshot.Builder() .setCounterId(COUNTER_ID) .setSnapshotVersion(SNAPSHOT_VERSION) @@ -193,16 +264,24 @@ public class KeyChainSnapshotSerializerTest { .setMaxAttempts(MAX_ATTEMPTS) .setEncryptedRecoveryKeyBlob(KEY_BLOB) .setKeyChainProtectionParams(createKeyChainProtectionParamsList()) - .setWrappedApplicationKeys(createKeys()) + .setWrappedApplicationKeys(createKeysWithMetadata()) .setTrustedHardwareCertPath(CERT_PATH) .build(); } - private static List<WrappedApplicationKey> createKeys() { + private static List<WrappedApplicationKey> createKeysWithoutMetadata() { ArrayList<WrappedApplicationKey> keyList = new ArrayList<>(); - keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES)); - keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES)); - keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES)); + keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES, /*metadata=*/ null)); + keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES, /*metadata=*/ null)); + keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES, /*metadata=*/ null)); + return keyList; + } + + private static List<WrappedApplicationKey> createKeysWithMetadata() { + ArrayList<WrappedApplicationKey> keyList = new ArrayList<>(); + keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES, TEST_KEY_1_METADATA)); + keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES, TEST_KEY_2_METADATA)); + keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES, TEST_KEY_3_METADATA)); return keyList; } @@ -221,10 +300,13 @@ public class KeyChainSnapshotSerializerTest { return keyChainProtectionParamsList; } - private static WrappedApplicationKey createKey(String alias, byte[] bytes) { - return new WrappedApplicationKey.Builder() + private static WrappedApplicationKey createKey(String alias, byte[] bytes, byte[] metadata) { + WrappedApplicationKey.Builder builder = new WrappedApplicationKey.Builder() .setAlias(alias) - .setEncryptedKeyMaterial(bytes) - .build(); + .setEncryptedKeyMaterial(bytes); + if (metadata != null) { + builder.setMetadata(metadata); + } + return builder.build(); } } |