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