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
diff --git a/api/system-current.txt b/api/system-current.txt
index 7f3c152..4fece40 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5092,7 +5092,8 @@
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 @@
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 @@
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 @@
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 31a5962..c43a666 100644
--- a/core/java/android/security/keystore/recovery/RecoveryController.java
+++ b/core/java/android/security/keystore/recovery/RecoveryController.java
@@ -533,7 +533,10 @@
* 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 @@
}
/**
+ * 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 @@
* @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 @@
}
/**
+ * 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 ae4448f..dbfd655 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 @@
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 @@
* @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 @@
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 @@
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 void writeToParcel(Parcel out, int flags) {
out.writeString(mAlias);
out.writeByteArray(mEncryptedKeyMaterial);
+ out.writeByteArray(mMetadata);
}
/**
@@ -147,6 +167,10 @@
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 591f15f..9a77802 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -62,7 +62,9 @@
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 61ab152..f78b0e1 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 @@
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 @@
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 @@
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 @@
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 15afbdd..fabc432 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 @@
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 @@
}
@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 @@
@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 @@
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 ea8c792..8734ceb6 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.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.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.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.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 @@
}
@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 fc5184d..81d219c 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_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.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 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.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 @@
* 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 @@
* @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 @@
+ " 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 b486834..8a19d62 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.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.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.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 @@
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 0f2c2fc..8f85a27 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 @@
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 235df69..527e879 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.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 @@
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 @@
}
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 880255d..9c03df8 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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
.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 @@
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();
}
}