Keystore 2.0: Teach keystore to decrypt generic blobs.

This CL addresses various gaps in legacy support.
* Encrypted legacy blobs.
* Encrypted key characteristics files (pre Android Q).
* Encrypted certificate and certificate chain entries
  (pre Android R).

To support key migration even when the corresponding user is locked,
keys can now be migrated in the legacy database by renaming files.
In order to construct a complete a key characteristics cache from old
characteristics files the information must be augmented with the
characteristics that can be extracted from the key blob by calling
KeyMintDevice::getKeyCharacteristics. For this to work, the blob
may need to be decrypted, upgraded, and reencrypted. The crypto steps
may fail with ResponseCode::LOCKED though if the user is locked.
If the key was upgraded in the process both the old and the new key
blob must be inserted into the database in order for the garbage
collector to reap and invalidate the superseded blob correctly.
At the time APPLICATION_ID and APPLICATION_DATA are usually not
available. This would cause such bound keys to fail with
ErrorCode::INVALID_KEY_BLOB. However, APPLICATION_ID/DATA were
never exposed to applications though, so this should be acceptable
for now.

Bug: 213173772
Bug: 213172664
Bug: 203101472
Test: keystore2_test
Change-Id: Id8561d3f98d53182709d9f4feeeecda3b1535077
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index e68b0fd..65ee7ae 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -578,6 +578,36 @@
     cert_chain: Option<Vec<u8>>,
 }
 
+/// This type represents a Blob with its metadata and an optional superseded blob.
+#[derive(Debug)]
+pub struct BlobInfo<'a> {
+    blob: &'a [u8],
+    metadata: &'a BlobMetaData,
+    /// Superseded blobs are an artifact of legacy import. In some rare occasions
+    /// the key blob needs to be upgraded during import. In that case two
+    /// blob are imported, the superseded one will have to be imported first,
+    /// so that the garbage collector can reap it.
+    superseded_blob: Option<(&'a [u8], &'a BlobMetaData)>,
+}
+
+impl<'a> BlobInfo<'a> {
+    /// Create a new instance of blob info with blob and corresponding metadata
+    /// and no superseded blob info.
+    pub fn new(blob: &'a [u8], metadata: &'a BlobMetaData) -> Self {
+        Self { blob, metadata, superseded_blob: None }
+    }
+
+    /// Create a new instance of blob info with blob and corresponding metadata
+    /// as well as superseded blob info.
+    pub fn new_with_superseded(
+        blob: &'a [u8],
+        metadata: &'a BlobMetaData,
+        superseded_blob: Option<(&'a [u8], &'a BlobMetaData)>,
+    ) -> Self {
+        Self { blob, metadata, superseded_blob }
+    }
+}
+
 impl CertificateInfo {
     /// Constructs a new CertificateInfo object from `cert` and `cert_chain`
     pub fn new(cert: Option<Vec<u8>>, cert_chain: Option<Vec<u8>>) -> Self {
@@ -2233,7 +2263,7 @@
         key: &KeyDescriptor,
         key_type: KeyType,
         params: &[KeyParameter],
-        blob_info: &(&[u8], &BlobMetaData),
+        blob_info: &BlobInfo,
         cert_info: &CertificateInfo,
         metadata: &KeyMetaData,
         km_uuid: &Uuid,
@@ -2253,7 +2283,27 @@
         self.with_transaction(TransactionBehavior::Immediate, |tx| {
             let key_id = Self::create_key_entry_internal(tx, &domain, namespace, key_type, km_uuid)
                 .context("Trying to create new key entry.")?;
-            let (blob, blob_metadata) = *blob_info;
+            let BlobInfo { blob, metadata: blob_metadata, superseded_blob } = *blob_info;
+
+            // In some occasions the key blob is already upgraded during the import.
+            // In order to make sure it gets properly deleted it is inserted into the
+            // database here and then immediately replaced by the superseding blob.
+            // The garbage collector will then subject the blob to deleteKey of the
+            // KM back end to permanently invalidate the key.
+            let need_gc = if let Some((blob, blob_metadata)) = superseded_blob {
+                Self::set_blob_internal(
+                    tx,
+                    key_id.id(),
+                    SubComponentType::KEY_BLOB,
+                    Some(blob),
+                    Some(blob_metadata),
+                )
+                .context("Trying to insert superseded key blob.")?;
+                true
+            } else {
+                false
+            };
+
             Self::set_blob_internal(
                 tx,
                 key_id.id(),
@@ -2280,7 +2330,8 @@
                 .context("Trying to insert key parameters.")?;
             metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?;
             let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type)
-                .context("Trying to rebind alias.")?;
+                .context("Trying to rebind alias.")?
+                || need_gc;
             Ok(key_id).do_gc(need_gc)
         })
         .context("In store_new_key.")
@@ -3234,6 +3285,7 @@
     use std::sync::{Arc, RwLock};
     use std::thread;
     use std::time::{Duration, SystemTime};
+    use crate::utils::AesGcm;
     #[cfg(disabled)]
     use std::time::Instant;
 
@@ -5557,8 +5609,7 @@
             None,
         )?;
 
-        let decrypted_secret_bytes =
-            loaded_super_key.aes_gcm_decrypt(&encrypted_secret, &iv, &tag)?;
+        let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?;
         assert_eq!(secret_bytes, &*decrypted_secret_bytes);
 
         Ok(())
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index b801ed3..cbc680d 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -17,8 +17,8 @@
 use crate::{
     error::{Error as KsError, ResponseCode},
     key_parameter::{KeyParameter, KeyParameterValue},
-    super_key::SuperKeyManager,
     utils::uid_to_android_user,
+    utils::AesGcm,
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     SecurityLevel::SecurityLevel, Tag::Tag, TagType::TagType,
@@ -26,6 +26,7 @@
 use anyhow::{Context, Result};
 use keystore2_crypto::{aes_gcm_decrypt, Password, ZVec};
 use std::collections::{HashMap, HashSet};
+use std::sync::Arc;
 use std::{convert::TryInto, fs::File, path::Path, path::PathBuf};
 use std::{
     fs,
@@ -87,6 +88,14 @@
     /// an invalid alias filename encoding.
     #[error("Invalid alias filename encoding.")]
     BadEncoding,
+    /// A component of the requested entry other than the KM key blob itself
+    /// was encrypted and no super key was provided.
+    #[error("Locked entry component.")]
+    LockedComponent,
+    /// The uids presented to move_keystore_entry belonged to different
+    /// Android users.
+    #[error("Cannot move keys across Android users.")]
+    AndroidUserMismatch,
 }
 
 /// The blob payload, optionally with all information required to decrypt it.
@@ -96,6 +105,16 @@
     Generic(Vec<u8>),
     /// A legacy key characteristics file. This has only a single list of Authorizations.
     Characteristics(Vec<u8>),
+    /// A legacy key characteristics file. This has only a single list of Authorizations.
+    /// Additionally, this characteristics file was encrypted with the user's super key.
+    EncryptedCharacteristics {
+        /// Initialization vector.
+        iv: Vec<u8>,
+        /// Aead tag for integrity verification.
+        tag: Vec<u8>,
+        /// Ciphertext.
+        data: Vec<u8>,
+    },
     /// A key characteristics cache has both a hardware enforced and a software enforced list
     /// of authorizations.
     CharacteristicsCache(Vec<u8>),
@@ -124,6 +143,17 @@
         /// Ciphertext.
         data: Vec<u8>,
     },
+    /// An encrypted blob. Includes the initialization vector, the aead tag, and the
+    /// ciphertext data. The key can be selected from context, i.e., the owner of the key
+    /// blob. This is a special case for generic encrypted blobs as opposed to key blobs.
+    EncryptedGeneric {
+        /// Initialization vector.
+        iv: Vec<u8>,
+        /// Aead tag for integrity verification.
+        tag: Vec<u8>,
+        /// Ciphertext.
+        data: Vec<u8>,
+    },
     /// Holds the plaintext key blob either after unwrapping an encrypted blob or when the
     /// blob was stored in "plaintext" on disk. The "plaintext" of a key blob is not actual
     /// plaintext because all KeyMint blobs are encrypted with a device bound key. The key
@@ -132,6 +162,19 @@
     Decrypted(ZVec),
 }
 
+/// Keystore used two different key characteristics file formats in the past.
+/// The key characteristics cache which superseded the characteristics file.
+/// The latter stored only one list of key parameters, while the former stored
+/// a hardware enforced and a software enforced list. This Enum indicates which
+/// type was read from the file system.
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum LegacyKeyCharacteristics {
+    /// A characteristics cache was read.
+    Cache(Vec<KeyParameter>),
+    /// A characteristics file was read.
+    File(Vec<KeyParameter>),
+}
+
 /// Represents a loaded legacy key blob file.
 #[derive(Debug, Eq, PartialEq)]
 pub struct Blob {
@@ -169,6 +212,16 @@
 }
 
 impl Blob {
+    /// Creates a new blob from flags and value.
+    pub fn new(flags: u8, value: BlobValue) -> Self {
+        Self { flags, value }
+    }
+
+    /// Return the raw flags of this Blob.
+    pub fn get_flags(&self) -> u8 {
+        self.flags
+    }
+
     /// This blob was generated with a fallback software KM device.
     pub fn is_fallback(&self) -> bool {
         self.flags & flags::FALLBACK != 0
@@ -212,10 +265,14 @@
     // version (1 Byte)
     // blob_type (1 Byte)
     // flags (1 Byte)
-    // info (1 Byte)
+    // info (1 Byte) Size of an info field appended to the blob.
     // initialization_vector (16 Bytes)
     // integrity (MD5 digest or gcm tag) (16 Bytes)
     // length (4 Bytes)
+    //
+    // The info field is used to store the salt for password encrypted blobs.
+    // The beginning of the info field can be computed from the file length
+    // and the info byte from the header: <file length> - <info> bytes.
     const COMMON_HEADER_SIZE: usize = 4 + Self::IV_SIZE + Self::GCM_TAG_LENGTH + 4;
 
     const VERSION_OFFSET: usize = 0;
@@ -341,12 +398,28 @@
         let tag = &buffer[Self::AEAD_TAG_OFFSET..Self::AEAD_TAG_OFFSET + Self::GCM_TAG_LENGTH];
 
         match (blob_type, is_encrypted, salt) {
-            (blob_types::GENERIC, _, _) => {
+            (blob_types::GENERIC, false, _) => {
                 Ok(Blob { flags, value: BlobValue::Generic(value.to_vec()) })
             }
-            (blob_types::KEY_CHARACTERISTICS, _, _) => {
+            (blob_types::GENERIC, true, _) => Ok(Blob {
+                flags,
+                value: BlobValue::EncryptedGeneric {
+                    iv: iv.to_vec(),
+                    tag: tag.to_vec(),
+                    data: value.to_vec(),
+                },
+            }),
+            (blob_types::KEY_CHARACTERISTICS, false, _) => {
                 Ok(Blob { flags, value: BlobValue::Characteristics(value.to_vec()) })
             }
+            (blob_types::KEY_CHARACTERISTICS, true, _) => Ok(Blob {
+                flags,
+                value: BlobValue::EncryptedCharacteristics {
+                    iv: iv.to_vec(),
+                    tag: tag.to_vec(),
+                    data: value.to_vec(),
+                },
+            }),
             (blob_types::KEY_CHARACTERISTICS_CACHE, _, _) => {
                 Ok(Blob { flags, value: BlobValue::CharacteristicsCache(value.to_vec()) })
             }
@@ -427,6 +500,15 @@
                         .context("In new_from_stream_decrypt_with.")?,
                 ),
             }),
+            BlobValue::EncryptedGeneric { iv, tag, data } => Ok(Blob {
+                flags: blob.flags,
+                value: BlobValue::Generic(
+                    decrypt(data, iv, tag, None, None)
+                        .context("In new_from_stream_decrypt_with.")?[..]
+                        .to_vec(),
+                ),
+            }),
+
             _ => Ok(blob),
         }
     }
@@ -546,24 +628,91 @@
         Ok(params)
     }
 
+    /// This function takes a Blob and an optional AesGcm. Plain text blob variants are
+    /// passed through as is. If a super key is given an attempt is made to decrypt the
+    /// blob thereby mapping BlobValue variants as follows:
+    /// BlobValue::Encrypted => BlobValue::Decrypted
+    /// BlobValue::EncryptedGeneric => BlobValue::Generic
+    /// BlobValue::EncryptedCharacteristics => BlobValue::Characteristics
+    /// If now super key is given or BlobValue::PwEncrypted is encountered,
+    /// Err(Error::LockedComponent) is returned.
+    fn decrypt_if_required(super_key: &Option<Arc<dyn AesGcm>>, blob: Blob) -> Result<Blob> {
+        match blob {
+            Blob { value: BlobValue::Generic(_), .. }
+            | Blob { value: BlobValue::Characteristics(_), .. }
+            | Blob { value: BlobValue::CharacteristicsCache(_), .. }
+            | Blob { value: BlobValue::Decrypted(_), .. } => Ok(blob),
+            Blob { value: BlobValue::EncryptedCharacteristics { iv, tag, data }, flags }
+                if super_key.is_some() =>
+            {
+                Ok(Blob {
+                    value: BlobValue::Characteristics(
+                        super_key.as_ref().unwrap().decrypt(&data, &iv, &tag).context(
+                            "In decrypt_if_required: Failed to decrypt EncryptedCharacteristics",
+                        )?[..]
+                            .to_vec(),
+                    ),
+                    flags,
+                })
+            }
+            Blob { value: BlobValue::Encrypted { iv, tag, data }, flags }
+                if super_key.is_some() =>
+            {
+                Ok(Blob {
+                    value: BlobValue::Decrypted(
+                        super_key
+                            .as_ref()
+                            .unwrap()
+                            .decrypt(&data, &iv, &tag)
+                            .context("In decrypt_if_required: Failed to decrypt Encrypted")?,
+                    ),
+                    flags,
+                })
+            }
+            Blob { value: BlobValue::EncryptedGeneric { iv, tag, data }, flags }
+                if super_key.is_some() =>
+            {
+                Ok(Blob {
+                    value: BlobValue::Generic(
+                        super_key
+                            .as_ref()
+                            .unwrap()
+                            .decrypt(&data, &iv, &tag)
+                            .context("In decrypt_if_required: Failed to decrypt Encrypted")?[..]
+                            .to_vec(),
+                    ),
+                    flags,
+                })
+            }
+            // This arm catches all encrypted cases where super key is not present or cannot
+            // decrypt the blob, the latter being BlobValue::PwEncrypted.
+            _ => Err(Error::LockedComponent)
+                .context("In decrypt_if_required: Encountered encrypted blob without super key."),
+        }
+    }
+
     fn read_characteristics_file(
         &self,
         uid: u32,
         prefix: &str,
         alias: &str,
         hw_sec_level: SecurityLevel,
-    ) -> Result<Vec<KeyParameter>> {
+        super_key: &Option<Arc<dyn AesGcm>>,
+    ) -> Result<LegacyKeyCharacteristics> {
         let blob = Self::read_generic_blob(&self.make_chr_filename(uid, alias, prefix))
             .context("In read_characteristics_file")?;
 
         let blob = match blob {
-            None => return Ok(Vec::new()),
+            None => return Ok(LegacyKeyCharacteristics::Cache(Vec::new())),
             Some(blob) => blob,
         };
 
-        let mut stream = match blob.value() {
-            BlobValue::Characteristics(data) => &data[..],
-            BlobValue::CharacteristicsCache(data) => &data[..],
+        let blob = Self::decrypt_if_required(super_key, blob)
+            .context("In read_characteristics_file: Trying to decrypt blob.")?;
+
+        let (mut stream, is_cache) = match blob.value() {
+            BlobValue::Characteristics(data) => (&data[..], false),
+            BlobValue::CharacteristicsCache(data) => (&data[..], true),
             _ => {
                 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(concat!(
                     "In read_characteristics_file: ",
@@ -589,7 +738,12 @@
             .into_iter()
             .map(|value| KeyParameter::new(value, SecurityLevel::KEYSTORE));
 
-        Ok(hw_list.into_iter().flatten().chain(sw_list).collect())
+        let params: Vec<KeyParameter> = hw_list.into_iter().flatten().chain(sw_list).collect();
+        if is_cache {
+            Ok(LegacyKeyCharacteristics::Cache(params))
+        } else {
+            Ok(LegacyKeyCharacteristics::File(params))
+        }
     }
 
     // This is a list of known prefixes that the Keystore 1.0 SPI used to use.
@@ -639,14 +793,40 @@
         Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?))
     }
 
+    fn read_generic_blob_decrypt_with<F>(path: &Path, decrypt: F) -> Result<Option<Blob>>
+    where
+        F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>,
+    {
+        let mut file = match Self::with_retry_interrupted(|| File::open(path)) {
+            Ok(file) => file,
+            Err(e) => match e.kind() {
+                ErrorKind::NotFound => return Ok(None),
+                _ => return Err(e).context("In read_generic_blob_decrypt_with."),
+            },
+        };
+
+        Ok(Some(
+            Self::new_from_stream_decrypt_with(&mut file, decrypt)
+                .context("In read_generic_blob_decrypt_with.")?,
+        ))
+    }
+
     /// Read a legacy keystore entry blob.
-    pub fn read_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
+    pub fn read_legacy_keystore_entry<F>(
+        &self,
+        uid: u32,
+        alias: &str,
+        decrypt: F,
+    ) -> Result<Option<Vec<u8>>>
+    where
+        F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>,
+    {
         let path = match self.make_legacy_keystore_entry_filename(uid, alias) {
             Some(path) => path,
             None => return Ok(None),
         };
 
-        let blob = Self::read_generic_blob(&path)
+        let blob = Self::read_generic_blob_decrypt_with(&path, decrypt)
             .context("In read_legacy_keystore_entry: Failed to read blob.")?;
 
         Ok(blob.and_then(|blob| match blob.value {
@@ -659,22 +839,23 @@
     }
 
     /// Remove a legacy keystore entry by the name alias with owner uid.
-    pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<()> {
+    pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<bool> {
         let path = match self.make_legacy_keystore_entry_filename(uid, alias) {
             Some(path) => path,
-            None => return Ok(()),
+            None => return Ok(false),
         };
 
         if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
             match e.kind() {
-                ErrorKind::NotFound => return Ok(()),
+                ErrorKind::NotFound => return Ok(false),
                 _ => return Err(e).context("In remove_legacy_keystore_entry."),
             }
         }
 
         let user_id = uid_to_android_user(uid);
         self.remove_user_dir_if_empty(user_id)
-            .context("In remove_legacy_keystore_entry: Trying to remove empty user dir.")
+            .context("In remove_legacy_keystore_entry: Trying to remove empty user dir.")?;
+        Ok(true)
     }
 
     /// List all entries belonging to the given uid.
@@ -988,6 +1169,88 @@
         Ok(something_was_deleted)
     }
 
+    /// This function moves a keystore file if it exists. It constructs the source and destination
+    /// file name using the make_filename function with the arguments uid, alias, and prefix.
+    /// The function overwrites existing destination files silently. If the source does not exist,
+    /// this function has no side effect and returns successfully.
+    fn move_keystore_file_if_exists<F>(
+        src_uid: u32,
+        dest_uid: u32,
+        src_alias: &str,
+        dest_alias: &str,
+        prefix: &str,
+        make_filename: F,
+    ) -> Result<()>
+    where
+        F: Fn(u32, &str, &str) -> PathBuf,
+    {
+        let src_path = make_filename(src_uid, src_alias, prefix);
+        let dest_path = make_filename(dest_uid, dest_alias, prefix);
+        match Self::with_retry_interrupted(|| fs::rename(&src_path, &dest_path)) {
+            Err(e) if e.kind() == ErrorKind::NotFound => Ok(()),
+            r => r.context("In move_keystore_file_if_exists: Trying to rename."),
+        }
+    }
+
+    /// Moves a keystore entry from one uid to another. The uids must have the same android user
+    /// component. Moves across android users are not permitted.
+    pub fn move_keystore_entry(
+        &self,
+        src_uid: u32,
+        dest_uid: u32,
+        src_alias: &str,
+        dest_alias: &str,
+    ) -> Result<()> {
+        if src_uid == dest_uid {
+            // Nothing to do in the trivial case.
+            return Ok(());
+        }
+
+        if uid_to_android_user(src_uid) != uid_to_android_user(dest_uid) {
+            return Err(Error::AndroidUserMismatch).context("In move_keystore_entry.");
+        }
+
+        let prefixes = ["USRPKEY", "USRSKEY", "USRCERT", "CACERT"];
+        for prefix in prefixes {
+            Self::move_keystore_file_if_exists(
+                src_uid,
+                dest_uid,
+                src_alias,
+                dest_alias,
+                prefix,
+                |uid, alias, prefix| self.make_blob_filename(uid, alias, prefix),
+            )
+            .with_context(|| {
+                format!(
+                    "In move_keystore_entry: Trying to move blob file with prefix: \"{}\"",
+                    prefix
+                )
+            })?;
+        }
+
+        let prefixes = ["USRPKEY", "USRSKEY"];
+
+        for prefix in prefixes {
+            Self::move_keystore_file_if_exists(
+                src_uid,
+                dest_uid,
+                src_alias,
+                dest_alias,
+                prefix,
+                |uid, alias, prefix| self.make_chr_filename(uid, alias, prefix),
+            )
+            .with_context(|| {
+                format!(
+                    "In move_keystore_entry: Trying to move characteristics file with \
+                     prefix: \"{}\"",
+                    prefix
+                )
+            })?;
+        }
+
+        Ok(())
+    }
+
     fn remove_user_dir_if_empty(&self, user_id: u32) -> Result<()> {
         if self
             .is_empty_user(user_id)
@@ -1004,79 +1267,66 @@
         &self,
         uid: u32,
         alias: &str,
-        key_manager: Option<&SuperKeyManager>,
-    ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
+        super_key: &Option<Arc<dyn AesGcm>>,
+    ) -> Result<(Option<(Blob, LegacyKeyCharacteristics)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
         let km_blob = self.read_km_blob_file(uid, alias).context("In load_by_uid_alias.")?;
 
         let km_blob = match km_blob {
             Some((km_blob, prefix)) => {
-                let km_blob = match km_blob {
-                    Blob { flags: _, value: BlobValue::Decrypted(_) } => km_blob,
-                    // Unwrap the key blob if required and if we have key_manager.
-                    Blob { flags, value: BlobValue::Encrypted { ref iv, ref tag, ref data } } => {
-                        if let Some(key_manager) = key_manager {
-                            let decrypted = match key_manager
-                                .get_per_boot_key_by_user_id(uid_to_android_user(uid))
-                            {
-                                Some(key) => key.aes_gcm_decrypt(data, iv, tag).context(
-                                    "In load_by_uid_alias: while trying to decrypt legacy blob.",
-                                )?,
-                                None => {
-                                    return Err(KsError::Rc(ResponseCode::LOCKED)).context(format!(
-                                        concat!(
-                                            "In load_by_uid_alias: ",
-                                            "User {} has not unlocked the keystore yet.",
-                                        ),
-                                        uid_to_android_user(uid)
-                                    ))
-                                }
-                            };
-                            Blob { flags, value: BlobValue::Decrypted(decrypted) }
-                        } else {
-                            km_blob
-                        }
-                    }
-                    _ => {
-                        return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
+                let km_blob =
+                    match km_blob {
+                        Blob { flags: _, value: BlobValue::Decrypted(_) }
+                        | Blob { flags: _, value: BlobValue::Encrypted { .. } } => km_blob,
+                        _ => return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
                             "In load_by_uid_alias: Found wrong blob type in legacy key blob file.",
-                        )
-                    }
-                };
+                        ),
+                    };
 
                 let hw_sec_level = match km_blob.is_strongbox() {
                     true => SecurityLevel::STRONGBOX,
                     false => SecurityLevel::TRUSTED_ENVIRONMENT,
                 };
                 let key_parameters = self
-                    .read_characteristics_file(uid, &prefix, alias, hw_sec_level)
+                    .read_characteristics_file(uid, &prefix, alias, hw_sec_level, super_key)
                     .context("In load_by_uid_alias.")?;
                 Some((km_blob, key_parameters))
             }
             None => None,
         };
 
-        let user_cert =
-            match Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT"))
-                .context("In load_by_uid_alias: While loading user cert.")?
-            {
-                Some(Blob { value: BlobValue::Generic(data), .. }) => Some(data),
-                None => None,
-                _ => {
-                    return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
-                        "In load_by_uid_alias: Found unexpected blob type in USRCERT file",
-                    )
-                }
-            };
+        let user_cert_blob =
+            Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT"))
+                .context("In load_by_uid_alias: While loading user cert.")?;
 
-        let ca_cert = match Self::read_generic_blob(&self.make_blob_filename(uid, alias, "CACERT"))
-            .context("In load_by_uid_alias: While loading ca cert.")?
-        {
-            Some(Blob { value: BlobValue::Generic(data), .. }) => Some(data),
-            None => None,
-            _ => {
+        let user_cert = if let Some(blob) = user_cert_blob {
+            let blob = Self::decrypt_if_required(super_key, blob)
+                .context("In load_by_uid_alias: While decrypting user cert.")?;
+
+            if let Blob { value: BlobValue::Generic(data), .. } = blob {
+                Some(data)
+            } else {
                 return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
-                    .context("In load_by_uid_alias: Found unexpected blob type in CACERT file")
+                    .context("In load_by_uid_alias: Found unexpected blob type in USRCERT file");
             }
+        } else {
+            None
+        };
+
+        let ca_cert_blob = Self::read_generic_blob(&self.make_blob_filename(uid, alias, "CACERT"))
+            .context("In load_by_uid_alias: While loading ca cert.")?;
+
+        let ca_cert = if let Some(blob) = ca_cert_blob {
+            let blob = Self::decrypt_if_required(super_key, blob)
+                .context("In load_by_uid_alias: While decrypting ca cert.")?;
+
+            if let Blob { value: BlobValue::Generic(data), .. } = blob {
+                Some(data)
+            } else {
+                return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
+                    .context("In load_by_uid_alias: Found unexpected blob type in CACERT file");
+            }
+        } else {
+            None
         };
 
         Ok((km_blob, user_cert, ca_cert))
@@ -1139,15 +1389,271 @@
 
 #[cfg(test)]
 mod test {
+    #![allow(dead_code)]
     use super::*;
-    use anyhow::anyhow;
-    use keystore2_crypto::aes_gcm_decrypt;
+    use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt};
     use rand::Rng;
     use std::string::FromUtf8Error;
     mod legacy_blob_test_vectors;
-    use crate::error;
+    use crate::legacy_blob::blob_types::{
+        GENERIC, KEY_CHARACTERISTICS, KEY_CHARACTERISTICS_CACHE, KM_BLOB, SUPER_KEY,
+        SUPER_KEY_AES256,
+    };
     use crate::legacy_blob::test::legacy_blob_test_vectors::*;
+    use anyhow::{anyhow, Result};
     use keystore2_test_utils::TempDir;
+    use std::convert::TryInto;
+    use std::fs::OpenOptions;
+    use std::io::Write;
+    use std::ops::Deref;
+
+    /// This function takes a blob and synchronizes the encrypted/super encrypted flags
+    /// with the blob type for the pairs Generic/EncryptedGeneric,
+    /// Characteristics/EncryptedCharacteristics and Encrypted/Decrypted.
+    /// E.g. if a non encrypted enum variant is encountered with flags::SUPER_ENCRYPTED
+    /// or flags::ENCRYPTED is set, the payload is encrypted and the corresponding
+    /// encrypted variant is returned, and vice versa. All other variants remain untouched
+    /// even if flags and BlobValue variant are inconsistent.
+    fn prepare_blob(blob: Blob, key: &[u8]) -> Result<Blob> {
+        match blob {
+            Blob { value: BlobValue::Generic(data), flags } if blob.is_encrypted() => {
+                let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap();
+                Ok(Blob { value: BlobValue::EncryptedGeneric { data: ciphertext, iv, tag }, flags })
+            }
+            Blob { value: BlobValue::Characteristics(data), flags } if blob.is_encrypted() => {
+                let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap();
+                Ok(Blob {
+                    value: BlobValue::EncryptedCharacteristics { data: ciphertext, iv, tag },
+                    flags,
+                })
+            }
+            Blob { value: BlobValue::Decrypted(data), flags } if blob.is_encrypted() => {
+                let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap();
+                Ok(Blob { value: BlobValue::Encrypted { data: ciphertext, iv, tag }, flags })
+            }
+            Blob { value: BlobValue::EncryptedGeneric { data, iv, tag }, flags }
+                if !blob.is_encrypted() =>
+            {
+                let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap();
+                Ok(Blob { value: BlobValue::Generic(plaintext[..].to_vec()), flags })
+            }
+            Blob { value: BlobValue::EncryptedCharacteristics { data, iv, tag }, flags }
+                if !blob.is_encrypted() =>
+            {
+                let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap();
+                Ok(Blob { value: BlobValue::Characteristics(plaintext[..].to_vec()), flags })
+            }
+            Blob { value: BlobValue::Encrypted { data, iv, tag }, flags }
+                if !blob.is_encrypted() =>
+            {
+                let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap();
+                Ok(Blob { value: BlobValue::Decrypted(plaintext), flags })
+            }
+            _ => Ok(blob),
+        }
+    }
+
+    struct LegacyBlobHeader {
+        version: u8,
+        blob_type: u8,
+        flags: u8,
+        info: u8,
+        iv: [u8; 12],
+        tag: [u8; 16],
+        blob_size: u32,
+    }
+
+    /// This function takes a Blob and writes it to out as a legacy blob file
+    /// version 3. Note that the flags field and the values field may be
+    /// inconsistent and could be sanitized by this function. It is intentionally
+    /// not done to enable tests to construct malformed blobs.
+    fn write_legacy_blob(out: &mut dyn Write, blob: Blob) -> Result<usize> {
+        let (header, data, salt) = match blob {
+            Blob { value: BlobValue::Generic(data), flags } => (
+                LegacyBlobHeader {
+                    version: 3,
+                    blob_type: GENERIC,
+                    flags,
+                    info: 0,
+                    iv: [0u8; 12],
+                    tag: [0u8; 16],
+                    blob_size: data.len() as u32,
+                },
+                data,
+                None,
+            ),
+            Blob { value: BlobValue::Characteristics(data), flags } => (
+                LegacyBlobHeader {
+                    version: 3,
+                    blob_type: KEY_CHARACTERISTICS,
+                    flags,
+                    info: 0,
+                    iv: [0u8; 12],
+                    tag: [0u8; 16],
+                    blob_size: data.len() as u32,
+                },
+                data,
+                None,
+            ),
+            Blob { value: BlobValue::CharacteristicsCache(data), flags } => (
+                LegacyBlobHeader {
+                    version: 3,
+                    blob_type: KEY_CHARACTERISTICS_CACHE,
+                    flags,
+                    info: 0,
+                    iv: [0u8; 12],
+                    tag: [0u8; 16],
+                    blob_size: data.len() as u32,
+                },
+                data,
+                None,
+            ),
+            Blob { value: BlobValue::PwEncrypted { iv, tag, data, salt, key_size }, flags } => (
+                LegacyBlobHeader {
+                    version: 3,
+                    blob_type: if key_size == keystore2_crypto::AES_128_KEY_LENGTH {
+                        SUPER_KEY
+                    } else {
+                        SUPER_KEY_AES256
+                    },
+                    flags,
+                    info: 0,
+                    iv: iv.try_into().unwrap(),
+                    tag: tag[..].try_into().unwrap(),
+                    blob_size: data.len() as u32,
+                },
+                data,
+                Some(salt),
+            ),
+            Blob { value: BlobValue::Encrypted { iv, tag, data }, flags } => (
+                LegacyBlobHeader {
+                    version: 3,
+                    blob_type: KM_BLOB,
+                    flags,
+                    info: 0,
+                    iv: iv.try_into().unwrap(),
+                    tag: tag[..].try_into().unwrap(),
+                    blob_size: data.len() as u32,
+                },
+                data,
+                None,
+            ),
+            Blob { value: BlobValue::EncryptedGeneric { iv, tag, data }, flags } => (
+                LegacyBlobHeader {
+                    version: 3,
+                    blob_type: GENERIC,
+                    flags,
+                    info: 0,
+                    iv: iv.try_into().unwrap(),
+                    tag: tag[..].try_into().unwrap(),
+                    blob_size: data.len() as u32,
+                },
+                data,
+                None,
+            ),
+            Blob { value: BlobValue::EncryptedCharacteristics { iv, tag, data }, flags } => (
+                LegacyBlobHeader {
+                    version: 3,
+                    blob_type: KEY_CHARACTERISTICS,
+                    flags,
+                    info: 0,
+                    iv: iv.try_into().unwrap(),
+                    tag: tag[..].try_into().unwrap(),
+                    blob_size: data.len() as u32,
+                },
+                data,
+                None,
+            ),
+            Blob { value: BlobValue::Decrypted(data), flags } => (
+                LegacyBlobHeader {
+                    version: 3,
+                    blob_type: KM_BLOB,
+                    flags,
+                    info: 0,
+                    iv: [0u8; 12],
+                    tag: [0u8; 16],
+                    blob_size: data.len() as u32,
+                },
+                data[..].to_vec(),
+                None,
+            ),
+        };
+        write_legacy_blob_helper(out, &header, &data, salt.as_deref())
+    }
+
+    fn write_legacy_blob_helper(
+        out: &mut dyn Write,
+        header: &LegacyBlobHeader,
+        data: &[u8],
+        info: Option<&[u8]>,
+    ) -> Result<usize> {
+        if 1 != out.write(&[header.version])? {
+            return Err(anyhow!("Unexpected size while writing version."));
+        }
+        if 1 != out.write(&[header.blob_type])? {
+            return Err(anyhow!("Unexpected size while writing blob_type."));
+        }
+        if 1 != out.write(&[header.flags])? {
+            return Err(anyhow!("Unexpected size while writing flags."));
+        }
+        if 1 != out.write(&[header.info])? {
+            return Err(anyhow!("Unexpected size while writing info."));
+        }
+        if 12 != out.write(&header.iv)? {
+            return Err(anyhow!("Unexpected size while writing iv."));
+        }
+        if 4 != out.write(&[0u8; 4])? {
+            return Err(anyhow!("Unexpected size while writing last 4 bytes of iv."));
+        }
+        if 16 != out.write(&header.tag)? {
+            return Err(anyhow!("Unexpected size while writing tag."));
+        }
+        if 4 != out.write(&header.blob_size.to_be_bytes())? {
+            return Err(anyhow!("Unexpected size while writing blob size."));
+        }
+        if data.len() != out.write(data)? {
+            return Err(anyhow!("Unexpected size while writing blob."));
+        }
+        if let Some(info) = info {
+            if info.len() != out.write(info)? {
+                return Err(anyhow!("Unexpected size while writing inof."));
+            }
+        }
+        Ok(40 + data.len() + info.map(|v| v.len()).unwrap_or(0))
+    }
+
+    fn make_encrypted_characteristics_file<P: AsRef<Path>>(path: P, key: &[u8]) -> Result<()> {
+        let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap();
+        let blob = Blob {
+            value: BlobValue::Characteristics(KEY_PARAMETERS.to_vec()),
+            flags: flags::ENCRYPTED,
+        };
+        let blob = prepare_blob(blob, key).unwrap();
+        write_legacy_blob(&mut file, blob).unwrap();
+        Ok(())
+    }
+
+    fn make_encrypted_usr_cert_file<P: AsRef<Path>>(path: P, key: &[u8]) -> Result<()> {
+        let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap();
+        let blob = Blob {
+            value: BlobValue::Generic(LOADED_CERT_AUTHBOUND.to_vec()),
+            flags: flags::ENCRYPTED,
+        };
+        let blob = prepare_blob(blob, key).unwrap();
+        write_legacy_blob(&mut file, blob).unwrap();
+        Ok(())
+    }
+
+    fn make_encrypted_ca_cert_file<P: AsRef<Path>>(path: P, key: &[u8]) -> Result<()> {
+        let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap();
+        let blob = Blob {
+            value: BlobValue::Generic(LOADED_CACERT_AUTHBOUND.to_vec()),
+            flags: flags::ENCRYPTED,
+        };
+        let blob = prepare_blob(blob, key).unwrap();
+        write_legacy_blob(&mut file, blob).unwrap();
+        Ok(())
+    }
 
     #[test]
     fn decode_encode_alias_test() {
@@ -1203,7 +1709,8 @@
     fn read_golden_key_blob_test() -> anyhow::Result<()> {
         let blob = LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*BLOB, |_, _, _, _, _| {
             Err(anyhow!("should not be called"))
-        })?;
+        })
+        .unwrap();
         assert!(!blob.is_encrypted());
         assert!(!blob.is_fallback());
         assert!(!blob.is_strongbox());
@@ -1213,7 +1720,8 @@
         let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
             &mut &*REAL_LEGACY_BLOB,
             |_, _, _, _, _| Err(anyhow!("should not be called")),
-        )?;
+        )
+        .unwrap();
         assert!(!blob.is_encrypted());
         assert!(!blob.is_fallback());
         assert!(!blob.is_strongbox());
@@ -1301,62 +1809,75 @@
 
     #[test]
     fn test_legacy_blobs() -> anyhow::Result<()> {
-        let temp_dir = TempDir::new("legacy_blob_test")?;
-        std::fs::create_dir(&*temp_dir.build().push("user_0"))?;
+        let temp_dir = TempDir::new("legacy_blob_test").unwrap();
+        std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
 
-        std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY)?;
+        std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
 
         std::fs::write(
             &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
             USRPKEY_AUTHBOUND,
-        )?;
+        )
+        .unwrap();
         std::fs::write(
             &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
             USRPKEY_AUTHBOUND_CHR,
-        )?;
+        )
+        .unwrap();
         std::fs::write(
             &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
             USRCERT_AUTHBOUND,
-        )?;
+        )
+        .unwrap();
         std::fs::write(
             &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
             CACERT_AUTHBOUND,
-        )?;
+        )
+        .unwrap();
 
         std::fs::write(
             &*temp_dir.build().push("user_0").push("10223_USRPKEY_non_authbound"),
             USRPKEY_NON_AUTHBOUND,
-        )?;
+        )
+        .unwrap();
         std::fs::write(
             &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_non_authbound"),
             USRPKEY_NON_AUTHBOUND_CHR,
-        )?;
+        )
+        .unwrap();
         std::fs::write(
             &*temp_dir.build().push("user_0").push("10223_USRCERT_non_authbound"),
             USRCERT_NON_AUTHBOUND,
-        )?;
+        )
+        .unwrap();
         std::fs::write(
             &*temp_dir.build().push("user_0").push("10223_CACERT_non_authbound"),
             CACERT_NON_AUTHBOUND,
-        )?;
+        )
+        .unwrap();
 
-        let mut key_manager: SuperKeyManager = Default::default();
-        let mut db = crate::database::KeystoreDB::new(temp_dir.path(), None)?;
         let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
 
-        assert_eq!(
-            legacy_blob_loader
-                .load_by_uid_alias(10223, "authbound", Some(&key_manager))
-                .unwrap_err()
-                .root_cause()
-                .downcast_ref::<error::Error>(),
-            Some(&error::Error::Rc(ResponseCode::LOCKED))
-        );
-
-        key_manager.unlock_user_key(&mut db, 0, &(PASSWORD.into()), &legacy_blob_loader)?;
+        if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
+            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
+        {
+            assert_eq!(flags, 4);
+            assert_eq!(
+                value,
+                BlobValue::Encrypted {
+                    data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+                    iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+                    tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+                }
+            );
+            assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
+            assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
+        } else {
+            panic!("");
+        }
 
         if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) =
-            legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
+            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
         {
             assert_eq!(flags, 4);
             //assert_eq!(value, BlobValue::Encrypted(..));
@@ -1366,7 +1887,7 @@
             panic!("");
         }
         if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
-            legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
+            legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
         {
             assert_eq!(flags, 0);
             assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
@@ -1383,11 +1904,11 @@
 
         assert_eq!(
             (None, None, None),
-            legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
+            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
         );
         assert_eq!(
             (None, None, None),
-            legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
+            legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
         );
 
         // The database should not be empty due to the super key.
@@ -1406,9 +1927,314 @@
         Ok(())
     }
 
+    struct TestKey(ZVec);
+
+    impl crate::utils::AesGcmKey for TestKey {
+        fn key(&self) -> &[u8] {
+            &self.0
+        }
+    }
+
+    impl Deref for TestKey {
+        type Target = [u8];
+        fn deref(&self) -> &Self::Target {
+            &self.0
+        }
+    }
+
+    #[test]
+    fn test_with_encrypted_characteristics() -> anyhow::Result<()> {
+        let temp_dir = TempDir::new("test_with_encrypted_characteristics").unwrap();
+        std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+        let pw: Password = PASSWORD.into();
+        let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap());
+        let super_key =
+            Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+        std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+        std::fs::write(
+            &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+            USRPKEY_AUTHBOUND,
+        )
+        .unwrap();
+        make_encrypted_characteristics_file(
+            &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+            &super_key,
+        )
+        .unwrap();
+        std::fs::write(
+            &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+            USRCERT_AUTHBOUND,
+        )
+        .unwrap();
+        std::fs::write(
+            &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+            CACERT_AUTHBOUND,
+        )
+        .unwrap();
+
+        let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+        assert_eq!(
+            legacy_blob_loader
+                .load_by_uid_alias(10223, "authbound", &None)
+                .unwrap_err()
+                .root_cause()
+                .downcast_ref::<Error>(),
+            Some(&Error::LockedComponent)
+        );
+
+        assert_eq!(
+            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(),
+            (
+                Some((
+                    Blob {
+                        flags: 4,
+                        value: BlobValue::Encrypted {
+                            data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+                            iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+                            tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+                        }
+                    },
+                    structured_test_params()
+                )),
+                Some(LOADED_CERT_AUTHBOUND.to_vec()),
+                Some(LOADED_CACERT_AUTHBOUND.to_vec())
+            )
+        );
+
+        legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+
+        assert_eq!(
+            (None, None, None),
+            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
+        );
+
+        // The database should not be empty due to the super key.
+        assert!(!legacy_blob_loader.is_empty().unwrap());
+        assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+        // The database should be considered empty for user 1.
+        assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+        legacy_blob_loader.remove_super_key(0);
+
+        // Now it should be empty.
+        assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+        assert!(legacy_blob_loader.is_empty().unwrap());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_encrypted_certificates() -> anyhow::Result<()> {
+        let temp_dir = TempDir::new("test_with_encrypted_certificates").unwrap();
+        std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+        let pw: Password = PASSWORD.into();
+        let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap());
+        let super_key =
+            Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+        std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+        std::fs::write(
+            &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+            USRPKEY_AUTHBOUND,
+        )
+        .unwrap();
+        std::fs::write(
+            &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+            USRPKEY_AUTHBOUND_CHR,
+        )
+        .unwrap();
+        make_encrypted_usr_cert_file(
+            &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+            &super_key,
+        )
+        .unwrap();
+        make_encrypted_ca_cert_file(
+            &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+            &super_key,
+        )
+        .unwrap();
+
+        let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+        assert_eq!(
+            legacy_blob_loader
+                .load_by_uid_alias(10223, "authbound", &None)
+                .unwrap_err()
+                .root_cause()
+                .downcast_ref::<Error>(),
+            Some(&Error::LockedComponent)
+        );
+
+        assert_eq!(
+            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(),
+            (
+                Some((
+                    Blob {
+                        flags: 4,
+                        value: BlobValue::Encrypted {
+                            data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+                            iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+                            tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+                        }
+                    },
+                    structured_test_params_cache()
+                )),
+                Some(LOADED_CERT_AUTHBOUND.to_vec()),
+                Some(LOADED_CACERT_AUTHBOUND.to_vec())
+            )
+        );
+
+        legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+
+        assert_eq!(
+            (None, None, None),
+            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
+        );
+
+        // The database should not be empty due to the super key.
+        assert!(!legacy_blob_loader.is_empty().unwrap());
+        assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+        // The database should be considered empty for user 1.
+        assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+        legacy_blob_loader.remove_super_key(0);
+
+        // Now it should be empty.
+        assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+        assert!(legacy_blob_loader.is_empty().unwrap());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_in_place_key_migration() -> anyhow::Result<()> {
+        let temp_dir = TempDir::new("test_in_place_key_migration").unwrap();
+        std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+        let pw: Password = PASSWORD.into();
+        let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap());
+        let super_key =
+            Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+        std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+        std::fs::write(
+            &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+            USRPKEY_AUTHBOUND,
+        )
+        .unwrap();
+        std::fs::write(
+            &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+            USRPKEY_AUTHBOUND_CHR,
+        )
+        .unwrap();
+        make_encrypted_usr_cert_file(
+            &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+            &super_key,
+        )
+        .unwrap();
+        make_encrypted_ca_cert_file(
+            &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+            &super_key,
+        )
+        .unwrap();
+
+        let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+        assert_eq!(
+            legacy_blob_loader
+                .load_by_uid_alias(10223, "authbound", &None)
+                .unwrap_err()
+                .root_cause()
+                .downcast_ref::<Error>(),
+            Some(&Error::LockedComponent)
+        );
+
+        let super_key: Option<Arc<dyn AesGcm>> = Some(super_key);
+
+        assert_eq!(
+            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(),
+            (
+                Some((
+                    Blob {
+                        flags: 4,
+                        value: BlobValue::Encrypted {
+                            data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+                            iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+                            tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+                        }
+                    },
+                    structured_test_params_cache()
+                )),
+                Some(LOADED_CERT_AUTHBOUND.to_vec()),
+                Some(LOADED_CACERT_AUTHBOUND.to_vec())
+            )
+        );
+
+        legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap();
+
+        assert_eq!(
+            legacy_blob_loader
+                .load_by_uid_alias(10224, "boundauth", &None)
+                .unwrap_err()
+                .root_cause()
+                .downcast_ref::<Error>(),
+            Some(&Error::LockedComponent)
+        );
+
+        assert_eq!(
+            legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(),
+            (
+                Some((
+                    Blob {
+                        flags: 4,
+                        value: BlobValue::Encrypted {
+                            data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+                            iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+                            tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+                        }
+                    },
+                    structured_test_params_cache()
+                )),
+                Some(LOADED_CERT_AUTHBOUND.to_vec()),
+                Some(LOADED_CACERT_AUTHBOUND.to_vec())
+            )
+        );
+
+        legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed.");
+
+        assert_eq!(
+            (None, None, None),
+            legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap()
+        );
+
+        // The database should not be empty due to the super key.
+        assert!(!legacy_blob_loader.is_empty().unwrap());
+        assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+        // The database should be considered empty for user 1.
+        assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+        legacy_blob_loader.remove_super_key(0);
+
+        // Now it should be empty.
+        assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+        assert!(legacy_blob_loader.is_empty().unwrap());
+
+        Ok(())
+    }
+
     #[test]
     fn list_non_existing_user() -> Result<()> {
-        let temp_dir = TempDir::new("list_non_existing_user")?;
+        let temp_dir = TempDir::new("list_non_existing_user").unwrap();
         let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
 
         assert!(legacy_blob_loader.list_user(20)?.is_empty());
@@ -1418,11 +2244,66 @@
 
     #[test]
     fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> {
-        let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user")?;
+        let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap();
         let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
 
         assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty());
 
         Ok(())
     }
+
+    #[test]
+    fn test_move_keystore_entry() {
+        let temp_dir = TempDir::new("test_move_keystore_entry").unwrap();
+        std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+        const SOME_CONTENT: &[u8] = b"some content";
+        const ANOTHER_CONTENT: &[u8] = b"another content";
+        const SOME_FILENAME: &str = "some_file";
+        const ANOTHER_FILENAME: &str = "another_file";
+
+        std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT)
+            .unwrap();
+
+        std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT)
+            .unwrap();
+
+        // Non existent source id silently ignored.
+        assert!(LegacyBlobLoader::move_keystore_file_if_exists(
+            1,
+            2,
+            "non_existent",
+            ANOTHER_FILENAME,
+            "ignored",
+            |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
+        )
+        .is_ok());
+
+        // Content of another_file has not changed.
+        let another_content =
+            std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
+        assert_eq!(&another_content, ANOTHER_CONTENT);
+
+        // Check that some_file still exists.
+        assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
+        // Existing target files are silently overwritten.
+
+        assert!(LegacyBlobLoader::move_keystore_file_if_exists(
+            1,
+            2,
+            SOME_FILENAME,
+            ANOTHER_FILENAME,
+            "ignored",
+            |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
+        )
+        .is_ok());
+
+        // Content of another_file is now "some content".
+        let another_content =
+            std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
+        assert_eq!(&another_content, SOME_CONTENT);
+
+        // Check that some_file no longer exists.
+        assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
+    }
 }
diff --git a/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs b/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs
index 14bd40c..2049ac2 100644
--- a/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs
+++ b/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs
@@ -12,6 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use crate::key_parameter::{KeyParameter, KeyParameterValue};
+use crate::legacy_blob::LegacyKeyCharacteristics;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+    Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve,
+    HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin,
+    KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+
 pub static BLOB: &[u8] = &[
     3, // version
     1, // type
@@ -22,6 +30,106 @@
     0, 0, 0, 4, // length in big endian
     0xde, 0xed, 0xbe, 0xef, // payload
 ];
+
+pub fn structured_test_params() -> LegacyKeyCharacteristics {
+    LegacyKeyCharacteristics::File(vec![
+        KeyParameter::new(KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), SecurityLevel::KEYSTORE),
+        KeyParameter::new(
+            KeyParameterValue::KeyPurpose(KeyPurpose::VERIFY),
+            SecurityLevel::KEYSTORE,
+        ),
+        KeyParameter::new(KeyParameterValue::Digest(Digest::SHA_2_256), SecurityLevel::KEYSTORE),
+        KeyParameter::new(
+            KeyParameterValue::UserSecureID(2100322049669824240),
+            SecurityLevel::KEYSTORE,
+        ),
+        KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::EC), SecurityLevel::KEYSTORE),
+        KeyParameter::new(KeyParameterValue::KeySize(256), SecurityLevel::KEYSTORE),
+        KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::KEYSTORE),
+        KeyParameter::new(
+            KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::FINGERPRINT),
+            SecurityLevel::KEYSTORE,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED),
+            SecurityLevel::KEYSTORE,
+        ),
+        KeyParameter::new(KeyParameterValue::OSVersion(110000), SecurityLevel::KEYSTORE),
+        KeyParameter::new(KeyParameterValue::OSPatchLevel(202101), SecurityLevel::KEYSTORE),
+        KeyParameter::new(KeyParameterValue::BootPatchLevel(20210105), SecurityLevel::KEYSTORE),
+        KeyParameter::new(KeyParameterValue::VendorPatchLevel(20210105), SecurityLevel::KEYSTORE),
+    ])
+}
+
+pub fn structured_test_params_cache() -> LegacyKeyCharacteristics {
+    LegacyKeyCharacteristics::Cache(vec![
+        KeyParameter::new(
+            KeyParameterValue::KeyPurpose(KeyPurpose::SIGN),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::KeyPurpose(KeyPurpose::VERIFY),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::Digest(Digest::SHA_2_256),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::UserSecureID(2100322049669824240),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::Algorithm(Algorithm::EC),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::KeySize(256), SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(
+            KeyParameterValue::EcCurve(EcCurve::P_256),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::FINGERPRINT),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::OSVersion(110000), SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(
+            KeyParameterValue::OSPatchLevel(202101),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::BootPatchLevel(20210105),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::VendorPatchLevel(20210105),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::CreationDateTime(1607149002000),
+            SecurityLevel::KEYSTORE,
+        ),
+        KeyParameter::new(KeyParameterValue::UserID(0), SecurityLevel::KEYSTORE),
+    ])
+}
+
+// One encoded list of key parameters.
+pub static KEY_PARAMETERS: &[u8] = &[
+    0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20,
+    0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x20,
+    0x04, 0x00, 0x00, 0x00, 0xf6, 0x01, 0x00, 0xa0, 0xf0, 0x7e, 0x7d, 0xb4, 0xc6, 0xd7, 0x25, 0x1d,
+    0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x30, 0x00, 0x01, 0x00, 0x00,
+    0x0a, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x2d, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+    0xf8, 0x01, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0xbe, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+    0xc1, 0x02, 0x00, 0x30, 0xb0, 0xad, 0x01, 0x00, 0xc2, 0x02, 0x00, 0x30, 0x75, 0x15, 0x03, 0x00,
+    0xcf, 0x02, 0x00, 0x30, 0xb9, 0x61, 0x34, 0x01, 0xce, 0x02, 0x00, 0x30, 0xb9, 0x61, 0x34, 0x01,
+    0x30, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+];
+
 pub static REAL_LEGACY_BLOB: &[u8] = &[
     0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -164,6 +272,24 @@
     0x76, 0x04, 0x2a, 0x48, 0xd1, 0xa7, 0x59, 0xd1, 0x04, 0x5b, 0xb4, 0x8a, 0x09, 0x22, 0x13, 0x0c,
     0x94, 0xb6, 0x67, 0x7b, 0x39, 0x85, 0x28, 0x11,
 ];
+
+pub static SUPERKEY_IV: &[u8] = &[
+    0x9a, 0x81, 0x56, 0x7d, 0xf5, 0x86, 0x7c, 0x62, 0xd7, 0xf9, 0x26, 0x06, 0x00, 0x00, 0x00, 0x00,
+];
+
+pub static SUPERKEY_TAG: &[u8] = &[
+    0xde, 0x2a, 0xcb, 0xac, 0x98, 0x57, 0x2b, 0xe5, 0x57, 0x18, 0x78, 0x57, 0x6e, 0x10, 0x09, 0x84,
+];
+
+pub static SUPERKEY_SALT: &[u8] = &[
+    0x04, 0x5b, 0xb4, 0x8a, 0x09, 0x22, 0x13, 0x0c, 0x94, 0xb6, 0x67, 0x7b, 0x39, 0x85, 0x28, 0x11,
+];
+
+pub static SUPERKEY_PAYLOAD: &[u8] = &[
+    0xac, 0x6d, 0x13, 0xe6, 0xad, 0x2c, 0x89, 0x53, 0x1a, 0x99, 0xa5, 0x6c, 0x88, 0xe9, 0xeb, 0x5c,
+    0xef, 0x68, 0x5e, 0x5b, 0x53, 0xa8, 0xe7, 0xa2, 0x76, 0x04, 0x2a, 0x48, 0xd1, 0xa7, 0x59, 0xd1,
+];
+
 pub static USRPKEY_AUTHBOUND: &[u8] = &[
     0x03, 0x04, 0x04, 0x00, 0x1c, 0x34, 0x87, 0x6f, 0xc8, 0x35, 0x0d, 0x34, 0x88, 0x59, 0xbc, 0xf5,
     0x00, 0x00, 0x00, 0x00, 0x62, 0xe3, 0x38, 0x2d, 0xd0, 0x58, 0x40, 0xc1, 0xb0, 0xf2, 0x4a, 0xdd,
@@ -203,6 +329,52 @@
     0xaf, 0x17, 0x2f, 0x21, 0x07, 0xea, 0x61, 0xff, 0x73, 0x08, 0x50, 0xb2, 0x19, 0xe8, 0x23, 0x1b,
     0x83, 0x42, 0xdd, 0x4e, 0x6d,
 ];
+
+pub static USRPKEY_AUTHBOUND_IV: &[u8] = &[
+    0x1c, 0x34, 0x87, 0x6f, 0xc8, 0x35, 0x0d, 0x34, 0x88, 0x59, 0xbc, 0xf5, 0x00, 0x00, 0x00, 0x00,
+];
+
+pub static USRPKEY_AUTHBOUND_TAG: &[u8] = &[
+    0x62, 0xe3, 0x38, 0x2d, 0xd0, 0x58, 0x40, 0xc1, 0xb0, 0xf2, 0x4a, 0xdd, 0xf7, 0x81, 0x67, 0x0b,
+];
+
+pub static USRPKEY_AUTHBOUND_ENC_PAYLOAD: &[u8] = &[
+    0x05, 0xb2, 0x5a, 0x1d, 0x1b, 0x25, 0x19, 0x48, 0xbf, 0x76, 0x0b, 0x37, 0x8c, 0x60, 0x52, 0xea,
+    0x30, 0x2a, 0x2c, 0x89, 0x99, 0x95, 0x57, 0x5c, 0xec, 0x62, 0x3c, 0x08, 0x1a, 0xc6, 0x65, 0xf9,
+    0xad, 0x24, 0x99, 0xf0, 0x5c, 0x44, 0xa0, 0xea, 0x9a, 0x60, 0xa2, 0xef, 0xf5, 0x27, 0x50, 0xba,
+    0x9c, 0xef, 0xa6, 0x08, 0x88, 0x4b, 0x0f, 0xfe, 0x5d, 0x41, 0xac, 0xba, 0xef, 0x9d, 0xa4, 0xb7,
+    0x72, 0xd3, 0xc8, 0x11, 0x92, 0x06, 0xf6, 0x26, 0xdf, 0x90, 0xe2, 0x66, 0x89, 0xf3, 0x85, 0x16,
+    0x4a, 0xdf, 0x7f, 0xac, 0x94, 0x4a, 0x1c, 0xce, 0x18, 0xee, 0xf4, 0x1f, 0x8e, 0xd6, 0xaf, 0xfd,
+    0x1d, 0xe5, 0x80, 0x4a, 0x6b, 0xbf, 0x91, 0xe2, 0x36, 0x1d, 0xb3, 0x53, 0x12, 0xfd, 0xc9, 0x0b,
+    0xa6, 0x69, 0x00, 0x45, 0xcb, 0x4c, 0x40, 0x6b, 0x70, 0xcb, 0xd2, 0xa0, 0x44, 0x0b, 0x4b, 0xec,
+    0xd6, 0x4f, 0x6f, 0x64, 0x37, 0xa7, 0xc7, 0x25, 0x54, 0xf4, 0xac, 0x6b, 0x34, 0x53, 0xea, 0x4e,
+    0x56, 0x49, 0xba, 0xf4, 0x1e, 0xc6, 0x52, 0x8f, 0xf4, 0x85, 0xe7, 0xb5, 0xaf, 0x49, 0x68, 0xb3,
+    0xb8, 0x7d, 0x63, 0xfc, 0x6e, 0x83, 0xa0, 0xf3, 0x91, 0x04, 0x80, 0xfd, 0xc5, 0x54, 0x7e, 0x92,
+    0x1a, 0x87, 0x2c, 0x6e, 0xa6, 0x29, 0xb9, 0x1e, 0x3f, 0xef, 0x30, 0x12, 0x7b, 0x2f, 0xa2, 0x16,
+    0x61, 0x8a, 0xcf, 0x14, 0x2d, 0x62, 0x98, 0x15, 0xae, 0x3b, 0xe6, 0x08, 0x1e, 0xb1, 0xf1, 0x21,
+    0xb0, 0x50, 0xc0, 0x4b, 0x81, 0x71, 0x29, 0xe7, 0x86, 0xbf, 0x29, 0xe1, 0xeb, 0xfe, 0xbc, 0x11,
+    0x3c, 0xc6, 0x15, 0x47, 0x9b, 0x41, 0x84, 0x61, 0x33, 0xbf, 0xca, 0xfe, 0x24, 0x92, 0x9e, 0x70,
+    0x26, 0x36, 0x46, 0xca, 0xfe, 0xd3, 0x5a, 0x1d, 0x9e, 0x30, 0x19, 0xbd, 0x26, 0x49, 0xb4, 0x90,
+    0x0c, 0x8d, 0xa2, 0x28, 0xa6, 0x24, 0x62, 0x6b, 0xe2, 0xfa, 0xe0, 0x53, 0xaa, 0x01, 0xeb, 0xaa,
+    0x41, 0x2b, 0xcb, 0xb1, 0x08, 0x66, 0x9d, 0x21, 0x2d, 0x2a, 0x47, 0x44, 0xee, 0xd5, 0x06, 0xe3,
+    0x4a, 0xb9, 0x3f, 0xcd, 0x78, 0x67, 0x89, 0x5b, 0xf7, 0x51, 0xc0, 0xc4, 0xa9, 0x68, 0xee, 0x44,
+    0x9c, 0x47, 0xa4, 0xbd, 0x6f, 0x7b, 0xdd, 0x64, 0xa8, 0xc7, 0x1e, 0x77, 0x1d, 0x68, 0x87, 0xaa,
+    0xae, 0x3c, 0xfc, 0x58, 0xb6, 0x3c, 0xcf, 0x58, 0xd0, 0x10, 0xaa, 0xef, 0xf0, 0x98, 0x67, 0x14,
+    0x29, 0x4d, 0x40, 0x8b, 0xe5, 0xb1, 0xdf, 0x7f, 0x40, 0xb1, 0xd8, 0xea, 0x6c, 0xa8, 0xf7, 0x64,
+    0xed, 0x02, 0x8d, 0xe7, 0x93, 0xfe, 0x79, 0x9a, 0x88, 0x62, 0x4f, 0xd0, 0x8a, 0x80, 0x36, 0x42,
+    0x0a, 0xf1, 0xa2, 0x0e, 0x30, 0x39, 0xbd, 0x26, 0x1d, 0xd4, 0xf1, 0xc8, 0x6e, 0xdd, 0xc5, 0x41,
+    0x29, 0xd8, 0xc1, 0x9e, 0x24, 0xf0, 0x25, 0x07, 0x05, 0x06, 0xc5, 0x08, 0xe3, 0x02, 0x2b, 0xe1,
+    0x40, 0xc5, 0x67, 0xd2, 0x82, 0x96, 0x20, 0x80, 0xcf, 0x87, 0x3a, 0xc6, 0xb0, 0xbe, 0xcc, 0xbb,
+    0x5a, 0x01, 0xab, 0xdd, 0x00, 0xc7, 0x0e, 0x7b, 0x02, 0x35, 0x27, 0xf4, 0x70, 0xfe, 0xd1, 0x19,
+    0x6a, 0x64, 0x23, 0x9d, 0xba, 0xe9, 0x1d, 0x76, 0x90, 0xfe, 0x7f, 0xd6, 0xb5, 0xa0, 0xe7, 0xb9,
+    0xf3, 0x56, 0x82, 0x8e, 0x57, 0x35, 0xf2, 0x69, 0xce, 0x52, 0xac, 0xc2, 0xf6, 0x5e, 0xb6, 0x54,
+    0x95, 0x83, 0x3b, 0x9f, 0x48, 0xbb, 0x04, 0x06, 0xac, 0x55, 0xa9, 0xb9, 0xa3, 0xe7, 0x89, 0x6e,
+    0x5c, 0x3a, 0x08, 0x67, 0x00, 0x8f, 0x1e, 0x26, 0x1b, 0x4d, 0x8a, 0xa6, 0x17, 0xa0, 0xa6, 0x18,
+    0xe6, 0x31, 0x43, 0x15, 0xb8, 0x7f, 0x9e, 0xf5, 0x78, 0x58, 0x98, 0xb1, 0x8c, 0xf5, 0x22, 0x42,
+    0x33, 0xc0, 0x42, 0x72, 0x4f, 0xce, 0x9f, 0x31, 0xaf, 0x17, 0x2f, 0x21, 0x07, 0xea, 0x61, 0xff,
+    0x73, 0x08, 0x50, 0xb2, 0x19, 0xe8, 0x23, 0x1b, 0x83, 0x42, 0xdd, 0x4e, 0x6d,
+];
+
 pub static USRPKEY_AUTHBOUND_CHR: &[u8] = &[
     0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
diff --git a/keystore2/src/legacy_importer.rs b/keystore2/src/legacy_importer.rs
index 3f37b14..81fb06c 100644
--- a/keystore2/src/legacy_importer.rs
+++ b/keystore2/src/legacy_importer.rs
@@ -14,18 +14,19 @@
 
 //! This module acts as a bridge between the legacy key database and the keystore2 database.
 
-use crate::key_parameter::KeyParameterValue;
-use crate::legacy_blob::BlobValue;
-use crate::utils::{uid_to_android_user, watchdog as wd};
-use crate::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
-use crate::{database::KeyType, error::Error};
-use crate::{
-    database::{
-        BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, EncryptedBy, KeyMetaData,
-        KeyMetaEntry, KeystoreDB, Uuid, KEYSTORE_UUID,
-    },
-    super_key::USER_SUPER_KEY,
+use crate::database::{
+    BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, EncryptedBy, KeyMetaData,
+    KeyMetaEntry, KeyType, KeystoreDB, Uuid, KEYSTORE_UUID,
 };
+use crate::error::{map_km_error, Error};
+use crate::key_parameter::{KeyParameter, KeyParameterValue};
+use crate::legacy_blob::{self, Blob, BlobValue, LegacyKeyCharacteristics};
+use crate::super_key::USER_SUPER_KEY;
+use crate::utils::{
+    key_characteristics_to_internal, uid_to_android_user, upgrade_keyblob_if_required_with,
+    watchdog as wd, AesGcm,
+};
+use crate::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
 use android_system_keystore2::aidl::android::system::keystore2::{
     Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
@@ -279,6 +280,116 @@
         result
     }
 
+    /// This function behaves exactly like with_try_import unless the src_key has an encrypted
+    /// component (other than the key blob itself [1]) and super_key is None.
+    /// In that case the files belonging to the src_key will be renamed to be moved to the
+    /// namespace indicated by dst_key. The destination domain must be in Domain::APP.
+    ///
+    /// [1] Components that cannot be encrypted with the super key in keystore2 include the
+    /// characteristics file, which was encrypted before Android Q, and certificate entries
+    /// added by KeyChain before Android Q.
+    pub fn with_try_import_or_migrate_namespaces<F, T>(
+        &self,
+        src: (u32, &KeyDescriptor),
+        dest: (u32, &KeyDescriptor),
+        super_key: Option<Arc<dyn AesGcm + Send + Sync>>,
+        has_migrate_any_permission: bool,
+        key_accessor: F,
+    ) -> Result<Option<T>>
+    where
+        F: Fn() -> Result<T>,
+    {
+        let _wp = wd::watch_millis("LegacyImporter::with_try_import_or_migrate_namespaces", 500);
+
+        let (src_uid, src_key) = src;
+        let (dest_uid, dest_key) = dest;
+
+        // Access the key and return on success.
+        match key_accessor() {
+            Ok(result) => return Ok(Some(result)),
+            Err(e) => {
+                if e.root_cause().downcast_ref::<Error>()
+                    != Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND))
+                {
+                    return Err(e);
+                }
+            }
+        }
+
+        // Filter inputs. We can only load legacy app domain keys as well
+        // as the SELINUX WIFI_NAMESPACE, which will be populated from AID_WIFI.
+        let src_uid = match src_key {
+            KeyDescriptor { domain: Domain::APP, alias: Some(_), .. } => src_uid,
+            KeyDescriptor { domain: Domain::SELINUX, nspace, alias: Some(_), .. } => {
+                match *nspace {
+                    Self::WIFI_NAMESPACE => Self::AID_WIFI,
+                    _ => {
+                        return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+                            .context(format!("No legacy keys for namespace {}", nspace))
+                    }
+                }
+            }
+            _ => {
+                return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+                    .context("No legacy keys for key descriptor.")
+            }
+        };
+
+        let dest_uid = match dest_key {
+            KeyDescriptor { domain: Domain::APP, alias: Some(_), .. } => Some(dest_uid),
+            KeyDescriptor { domain: Domain::SELINUX, alias: Some(_), .. } => {
+                // Domain::SELINUX cannot be migrated in place, but we cannot fail at this point
+                // because the import may succeed at which point the actual migration will
+                // be performed by the caller.
+                None
+            }
+            _ => {
+                return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+                    .context("No legacy keys for key descriptor.")
+            }
+        };
+
+        let src_key_clone = src_key.clone();
+        let dest_key_clone = dest_key.clone();
+        let result = self.do_serialized(move |importer_state| {
+            let super_key = super_key.map(|sk| -> Arc<dyn AesGcm> { sk });
+            match (
+                importer_state.check_and_import(src_uid, src_key_clone.clone(), super_key),
+                dest_uid,
+            ) {
+                // The import into the database was successful. Return Ok(true)
+                (Ok(()), _) => Ok(true),
+                // The import failed because a certificate and/or characteristics
+                // file was encrypted and no super_key was available. Migration within the
+                // legacy database is attempted and Ok(false) is returned on success.
+                (Err(e), Some(dest_uid))
+                    if has_migrate_any_permission
+                        && e.root_cause().downcast_ref::<Error>()
+                            == Some(&Error::Rc(ResponseCode::LOCKED)) =>
+                {
+                    importer_state
+                        .migrate_namespaces(src_uid, dest_uid, src_key_clone, dest_key_clone)
+                        .map(|_| false)
+                }
+                (Err(e), _) => Err(e),
+            }
+        });
+
+        match result {
+            None => {
+                Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context("Legacy database is empty.")
+            }
+
+            Some(Ok(true)) => {
+                // After successful import try again.
+                key_accessor().map(|v| Some(v))
+            }
+            // The entry was successfully migrated within the legacy database.
+            Some(Ok(false)) => Ok(None),
+            Some(Err(e)) => Err(e),
+        }
+    }
+
     /// Runs the key_accessor function and returns its result. If it returns an error and the
     /// root cause was KEY_NOT_FOUND, tries to import a key with the given parameters from
     /// the legacy database to the new database and runs the key_accessor function again if
@@ -287,6 +398,7 @@
         &self,
         key: &KeyDescriptor,
         caller_uid: u32,
+        super_key: Option<Arc<dyn AesGcm + Send + Sync>>,
         key_accessor: F,
     ) -> Result<T>
     where
@@ -323,8 +435,10 @@
         };
 
         let key_clone = key.clone();
-        let result = self
-            .do_serialized(move |importer_state| importer_state.check_and_import(uid, key_clone));
+        let result = self.do_serialized(move |importer_state| {
+            let super_key = super_key.map(|sk| -> Arc<dyn AesGcm> { sk });
+            importer_state.check_and_import(uid, key_clone, super_key)
+        });
 
         if let Some(result) = result {
             result?;
@@ -430,9 +544,199 @@
             .context("In list_uid: Trying to list legacy entries.")
     }
 
+    fn migrate_namespaces(
+        &mut self,
+        src_uid: u32,
+        dest_uid: u32,
+        src_key: KeyDescriptor,
+        dest_key: KeyDescriptor,
+    ) -> Result<()> {
+        let src_alias = src_key.alias.ok_or_else(|| {
+            anyhow::anyhow!(Error::sys()).context(
+                "In legacy_migrator::migrate_namespace: src_key.alias must be Some because \
+                 our caller must not have called us otherwise.",
+            )
+        })?;
+
+        if dest_key.domain != Domain::APP {
+            return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(
+                "In legacy_migrator::migrate_namespace: \
+                 Legacy in-place migration to SELinux namespace is not supported.",
+            );
+        }
+
+        let dest_alias = dest_key.alias.ok_or_else(|| {
+            anyhow::anyhow!(Error::sys()).context(concat!(
+                "In legacy_migrator::migrate_namespace: dest_key.alias must be Some because ",
+                "our caller must not have called us otherwise."
+            ))
+        })?;
+
+        self.legacy_loader
+            .move_keystore_entry(src_uid, dest_uid, &src_alias, &dest_alias)
+            .context("In legacy_migrator::migrate_namespace: Moving key entry files.")
+    }
+
+    /// Checks if the key can potentially be unlocked. And deletes the key entry otherwise.
+    /// If the super_key has already been imported, the super key database id is returned.
+    fn get_super_key_id_check_unlockable_or_delete(
+        &mut self,
+        uid: u32,
+        alias: &str,
+    ) -> Result<i64> {
+        let user_id = uid_to_android_user(uid);
+
+        match self
+            .db
+            .load_super_key(&USER_SUPER_KEY, user_id)
+            .context("In get_super_key_id_check_unlockable_or_delete: Failed to load super key")?
+        {
+            Some((_, entry)) => Ok(entry.id()),
+            None => {
+                // This might be the first time we access the super key,
+                // and it may not have been imported. We cannot import
+                // the legacy super_key key now, because we need to reencrypt
+                // it which we cannot do if we are not unlocked, which we are
+                // not because otherwise the key would have been imported.
+                // We can check though if the key exists. If it does,
+                // we can return Locked. Otherwise, we can delete the
+                // key and return NotFound, because the key will never
+                // be unlocked again.
+                if self.legacy_loader.has_super_key(user_id) {
+                    Err(Error::Rc(ResponseCode::LOCKED)).context(
+                        "In get_super_key_id_check_unlockable_or_delete: \
+                         Cannot import super key of this key while user is locked.",
+                    )
+                } else {
+                    self.legacy_loader.remove_keystore_entry(uid, alias).context(
+                        "In get_super_key_id_check_unlockable_or_delete: \
+                         Trying to remove obsolete key.",
+                    )?;
+                    Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+                        .context("In get_super_key_id_check_unlockable_or_delete: Obsolete key.")
+                }
+            }
+        }
+    }
+
+    fn characteristics_file_to_cache(
+        &mut self,
+        km_blob_params: Option<(Blob, LegacyKeyCharacteristics)>,
+        super_key: &Option<Arc<dyn AesGcm>>,
+        uid: u32,
+        alias: &str,
+    ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<(LegacyBlob<'static>, BlobMetaData)>)>
+    {
+        let (km_blob, params) = match km_blob_params {
+            Some((km_blob, LegacyKeyCharacteristics::File(params))) => (km_blob, params),
+            Some((km_blob, LegacyKeyCharacteristics::Cache(params))) => {
+                return Ok((Some((km_blob, params)), None))
+            }
+            None => return Ok((None, None)),
+        };
+
+        let km_uuid = self
+            .get_km_uuid(km_blob.is_strongbox())
+            .context("In characteristics_file_to_cache: Trying to get KM UUID")?;
+
+        let blob = match (&km_blob.value(), super_key.as_ref()) {
+            (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => {
+                let blob = super_key
+                    .decrypt(data, iv, tag)
+                    .context("In characteristics_file_to_cache: Decryption failed.")?;
+                LegacyBlob::ZVec(blob)
+            }
+            (BlobValue::Encrypted { .. }, None) => {
+                return Err(Error::Rc(ResponseCode::LOCKED)).context(
+                    "In characteristics_file_to_cache: Oh uh, so close. \
+                     This ancient key cannot be imported unless the user is unlocked.",
+                );
+            }
+            (BlobValue::Decrypted(data), _) => LegacyBlob::Ref(data),
+            _ => {
+                return Err(Error::sys())
+                    .context("In characteristics_file_to_cache: Unexpected blob type.")
+            }
+        };
+
+        let (km_params, upgraded_blob) = get_key_characteristics_without_app_data(&km_uuid, &*blob)
+            .context(
+                "In characteristics_file_to_cache: Failed to get key characteristics from device.",
+            )?;
+
+        let flags = km_blob.get_flags();
+
+        let (current_blob, superseded_blob) = if let Some(upgraded_blob) = upgraded_blob {
+            match (km_blob.take_value(), super_key.as_ref()) {
+                (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => {
+                    let super_key_id =
+                        self.get_super_key_id_check_unlockable_or_delete(uid, alias).context(
+                            "In characteristics_file_to_cache: \
+                             How is there a super key but no super key id?",
+                        )?;
+
+                    let mut superseded_metadata = BlobMetaData::new();
+                    superseded_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
+                    superseded_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
+                    superseded_metadata
+                        .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+                    superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+                    let superseded_blob = (LegacyBlob::Vec(data), superseded_metadata);
+
+                    let (data, iv, tag) = super_key.encrypt(&upgraded_blob).context(
+                        "In characteristics_file_to_cache: \
+                         Failed to encrypt upgraded key blob.",
+                    )?;
+                    (
+                        Blob::new(flags, BlobValue::Encrypted { data, iv, tag }),
+                        Some(superseded_blob),
+                    )
+                }
+                (BlobValue::Encrypted { .. }, None) => {
+                    return Err(Error::sys()).context(
+                        "In characteristics_file_to_cache: This should not be reachable. \
+                         The blob could not have been decrypted above.",
+                    );
+                }
+                (BlobValue::Decrypted(data), _) => {
+                    let mut superseded_metadata = BlobMetaData::new();
+                    superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+                    let superseded_blob = (LegacyBlob::ZVec(data), superseded_metadata);
+                    (
+                        Blob::new(
+                            flags,
+                            BlobValue::Decrypted(upgraded_blob.try_into().context(
+                                "In characteristics_file_to_cache: \
+                             Failed to convert upgraded blob to ZVec.",
+                            )?),
+                        ),
+                        Some(superseded_blob),
+                    )
+                }
+                _ => {
+                    return Err(Error::sys()).context(
+                        "In characteristics_file_to_cache: This should not be reachable. \
+                         Any other variant should have resulted in a different error.",
+                    )
+                }
+            }
+        } else {
+            (km_blob, None)
+        };
+
+        let params =
+            augment_legacy_characteristics_file_with_key_characteristics(km_params, params);
+        Ok((Some((current_blob, params)), superseded_blob))
+    }
+
     /// This is a key import request that must run in the importer thread. This must
     /// be passed to do_serialized.
-    fn check_and_import(&mut self, uid: u32, mut key: KeyDescriptor) -> Result<()> {
+    fn check_and_import(
+        &mut self,
+        uid: u32,
+        mut key: KeyDescriptor,
+        super_key: Option<Arc<dyn AesGcm>>,
+    ) -> Result<()> {
         let alias = key.alias.clone().ok_or_else(|| {
             anyhow::anyhow!(Error::sys()).context(
                 "In check_and_import: Must be Some because \
@@ -451,49 +755,42 @@
         // If the key is not found in the cache, try to load from the legacy database.
         let (km_blob_params, user_cert, ca_cert) = self
             .legacy_loader
-            .load_by_uid_alias(uid, &alias, None)
+            .load_by_uid_alias(uid, &alias, &super_key)
+            .map_err(|e| {
+                if e.root_cause().downcast_ref::<legacy_blob::Error>()
+                    == Some(&legacy_blob::Error::LockedComponent)
+                {
+                    // There is no chance to succeed at this point. We just check if there is
+                    // a super key so that this entry might be unlockable in the future.
+                    // If not the entry will be deleted and KEY_NOT_FOUND is returned.
+                    // If a super key id was returned we still have to return LOCKED but the key
+                    // may be imported when the user unlocks the device.
+                    self.get_super_key_id_check_unlockable_or_delete(uid, &alias)
+                        .and_then::<i64, _>(|_| {
+                            Err(Error::Rc(ResponseCode::LOCKED))
+                                .context("Super key present but locked.")
+                        })
+                        .unwrap_err()
+                } else {
+                    e
+                }
+            })
             .context("In check_and_import: Trying to load legacy blob.")?;
+
+        let (km_blob_params, superseded_blob) = self
+            .characteristics_file_to_cache(km_blob_params, &super_key, uid, &alias)
+            .context("In check_and_import: Trying to update legacy charateristics.")?;
+
         let result = match km_blob_params {
             Some((km_blob, params)) => {
                 let is_strongbox = km_blob.is_strongbox();
+
                 let (blob, mut blob_metadata) = match km_blob.take_value() {
                     BlobValue::Encrypted { iv, tag, data } => {
                         // Get super key id for user id.
-                        let user_id = uid_to_android_user(uid as u32);
-
-                        let super_key_id = match self
-                            .db
-                            .load_super_key(&USER_SUPER_KEY, user_id)
-                            .context("In check_and_import: Failed to load super key")?
-                        {
-                            Some((_, entry)) => entry.id(),
-                            None => {
-                                // This might be the first time we access the super key,
-                                // and it may not have been imported. We cannot import
-                                // the legacy super_key key now, because we need to reencrypt
-                                // it which we cannot do if we are not unlocked, which we are
-                                // not because otherwise the key would have been imported.
-                                // We can check though if the key exists. If it does,
-                                // we can return Locked. Otherwise, we can delete the
-                                // key and return NotFound, because the key will never
-                                // be unlocked again.
-                                if self.legacy_loader.has_super_key(user_id) {
-                                    return Err(Error::Rc(ResponseCode::LOCKED)).context(concat!(
-                                        "In check_and_import: Cannot import super key of this ",
-                                        "key while user is locked."
-                                    ));
-                                } else {
-                                    self.legacy_loader.remove_keystore_entry(uid, &alias).context(
-                                        concat!(
-                                            "In check_and_import: ",
-                                            "Trying to remove obsolete key."
-                                        ),
-                                    )?;
-                                    return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
-                                        .context("In check_and_import: Obsolete key.");
-                                }
-                            }
-                        };
+                        let super_key_id = self
+                            .get_super_key_id_check_unlockable_or_delete(uid, &alias)
+                            .context("In check_and_import: Failed to get super key id.")?;
 
                         let mut blob_metadata = BlobMetaData::new();
                         blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
@@ -519,13 +816,18 @@
                     .context("In check_and_import: Trying to make creation time.")?;
                 metadata.add(KeyMetaEntry::CreationDate(creation_date));
 
+                let blob_info = BlobInfo::new_with_superseded(
+                    &blob,
+                    &blob_metadata,
+                    superseded_blob.as_ref().map(|(b, m)| (&**b, m)),
+                );
                 // Store legacy key in the database.
                 self.db
                     .store_new_key(
                         &key,
                         KeyType::Client,
                         &params,
-                        &(&blob, &blob_metadata),
+                        &blob_info,
                         &CertificateInfo::new(user_cert, ca_cert),
                         &metadata,
                         &km_uuid,
@@ -635,13 +937,17 @@
         {
             let (km_blob_params, _, _) = self
                 .legacy_loader
-                .load_by_uid_alias(uid, &alias, None)
+                .load_by_uid_alias(uid, &alias, &None)
                 .context("In bulk_delete: Trying to load legacy blob.")?;
 
             // Determine if the key needs special handling to be deleted.
             let (need_gc, is_super_encrypted) = km_blob_params
                 .as_ref()
                 .map(|(blob, params)| {
+                    let params = match params {
+                        LegacyKeyCharacteristics::Cache(params)
+                        | LegacyKeyCharacteristics::File(params) => params,
+                    };
                     (
                         params.iter().any(|kp| {
                             KeyParameterValue::RollbackResistance == *kp.key_parameter_value()
@@ -714,18 +1020,68 @@
     }
 }
 
-enum LegacyBlob {
+enum LegacyBlob<'a> {
     Vec(Vec<u8>),
     ZVec(ZVec),
+    Ref(&'a [u8]),
 }
 
-impl Deref for LegacyBlob {
+impl Deref for LegacyBlob<'_> {
     type Target = [u8];
 
     fn deref(&self) -> &Self::Target {
         match self {
             Self::Vec(v) => v,
             Self::ZVec(v) => v,
+            Self::Ref(v) => v,
         }
     }
 }
+
+/// This function takes two KeyParameter lists. The first is assumed to have been retrieved from the
+/// KM back end using km_dev.getKeyCharacteristics. The second is assumed to have been retrieved
+/// from a legacy key characteristics file (not cache) as used in Android P and older. The function
+/// augments the former with entries from the latter only if no equivalent entry is present ignoring.
+/// the security level of enforcement. All entries in the latter are assumed to have security level
+/// KEYSTORE.
+fn augment_legacy_characteristics_file_with_key_characteristics<T>(
+    mut from_km: Vec<KeyParameter>,
+    legacy: T,
+) -> Vec<KeyParameter>
+where
+    T: IntoIterator<Item = KeyParameter>,
+{
+    for legacy_kp in legacy.into_iter() {
+        if !from_km
+            .iter()
+            .any(|km_kp| km_kp.key_parameter_value() == legacy_kp.key_parameter_value())
+        {
+            from_km.push(legacy_kp);
+        }
+    }
+    from_km
+}
+
+/// Attempts to retrieve the key characteristics for the given blob from the KM back end with the
+/// given UUID. It may upgrade the key blob in the process. In that case the upgraded blob is
+/// returned as the second tuple member.
+fn get_key_characteristics_without_app_data(
+    uuid: &Uuid,
+    blob: &[u8],
+) -> Result<(Vec<KeyParameter>, Option<Vec<u8>>)> {
+    let (km_dev, _) = crate::globals::get_keymint_dev_by_uuid(uuid)
+        .with_context(|| format!("In foo: Trying to get km device for id {:?}", uuid))?;
+
+    let (characteristics, upgraded_blob) = upgrade_keyblob_if_required_with(
+        &*km_dev,
+        blob,
+        &[],
+        |blob| {
+            let _wd = wd::watch_millis("In foo: Calling GetKeyCharacteristics.", 500);
+            map_km_error(km_dev.getKeyCharacteristics(blob, &[], &[]))
+        },
+        |_| Ok(()),
+    )
+    .context("In foo.")?;
+    Ok((key_characteristics_to_internal(characteristics), upgraded_blob))
+}
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index 71f43d6..0d637d8 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -23,7 +23,8 @@
 use crate::permission::{KeyPerm, KeystorePerm};
 use crate::super_key::{SuperKeyManager, UserState};
 use crate::utils::{
-    check_key_permission, check_keystore_permission, list_key_entries, watchdog as wd,
+    check_key_permission, check_keystore_permission, list_key_entries, uid_to_android_user,
+    watchdog as wd,
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel,
@@ -255,34 +256,55 @@
             }
         };
 
-        DB.with(|db| {
-            let (key_id_guard, _) = LEGACY_IMPORTER
-                .with_try_import(source, src_uid, || {
-                    db.borrow_mut().load_key_entry(
-                        source,
-                        KeyType::Client,
-                        KeyEntryLoadBits::NONE,
-                        src_uid,
-                        |k, av| {
-                            if migrate_any_key_permission {
-                                Ok(())
-                            } else {
-                                check_key_permission(KeyPerm::Use, k, &av)?;
-                                check_key_permission(KeyPerm::Delete, k, &av)?;
-                                check_key_permission(KeyPerm::Grant, k, &av)
-                            }
-                        },
-                    )
-                })
-                .context("In migrate_key_namespace: Failed to load key blob.")?;
+        let user_id = uid_to_android_user(dest_uid);
 
-            db.borrow_mut().migrate_key_namespace(key_id_guard, destination, dest_uid, |k| {
-                if migrate_any_key_permission {
-                    Ok(())
-                } else {
-                    check_key_permission(KeyPerm::Rebind, k, &None)
-                }
-            })
+        if user_id != uid_to_android_user(src_uid)
+            && (source.domain == Domain::APP || destination.domain == Domain::APP)
+        {
+            return Err(Error::sys()).context(
+                "In migrate_key_namespace: Keys cannot be migrated across android users.",
+            );
+        }
+
+        let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id);
+
+        DB.with(|db| {
+            if let Some((key_id_guard, _)) = LEGACY_IMPORTER
+                .with_try_import_or_migrate_namespaces(
+                    (src_uid, source),
+                    (dest_uid, destination),
+                    super_key,
+                    migrate_any_key_permission,
+                    || {
+                        db.borrow_mut().load_key_entry(
+                            source,
+                            KeyType::Client,
+                            KeyEntryLoadBits::NONE,
+                            src_uid,
+                            |k, av| {
+                                if migrate_any_key_permission {
+                                    Ok(())
+                                } else {
+                                    check_key_permission(KeyPerm::Use, k, &av)?;
+                                    check_key_permission(KeyPerm::Delete, k, &av)?;
+                                    check_key_permission(KeyPerm::Grant, k, &av)
+                                }
+                            },
+                        )
+                    },
+                )
+                .context("In migrate_key_namespace: Failed to load key blob.")?
+            {
+                db.borrow_mut().migrate_key_namespace(key_id_guard, destination, dest_uid, |k| {
+                    if migrate_any_key_permission {
+                        Ok(())
+                    } else {
+                        check_key_permission(KeyPerm::Rebind, k, &None)
+                    }
+                })
+            } else {
+                Ok(())
+            }
         })
     }
 
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
index 0ee3db0..4ce9dce 100644
--- a/keystore2/src/raw_device.rs
+++ b/keystore2/src/raw_device.rs
@@ -16,8 +16,9 @@
 
 use crate::{
     database::{
-        BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits,
-        KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB, SubComponentType, Uuid,
+        BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry,
+        KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB,
+        SubComponentType, Uuid,
     },
     error::{map_km_error, Error, ErrorCode},
     globals::get_keymint_device,
@@ -125,7 +126,7 @@
             key_desc,
             key_type,
             &key_parameters,
-            &(&creation_result.keyBlob, &blob_metadata),
+            &BlobInfo::new(&creation_result.keyBlob, &blob_metadata),
             &CertificateInfo::new(None, None),
             &key_metadata,
             &self.km_uuid,
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index eefbc20..4cf41c5 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -18,7 +18,7 @@
 use crate::audit_log::{
     log_key_deleted, log_key_generated, log_key_imported, log_key_integrity_violation,
 };
-use crate::database::{CertificateInfo, KeyIdGuard};
+use crate::database::{BlobInfo, CertificateInfo, KeyIdGuard};
 use crate::error::{self, map_km_error, map_or_log_err, Error, ErrorCode};
 use crate::globals::{DB, ENFORCEMENTS, LEGACY_IMPORTER, SUPER_KEY};
 use crate::key_parameter::KeyParameter as KsKeyParam;
@@ -182,7 +182,7 @@
                             &key,
                             KeyType::Client,
                             &key_parameters,
-                            &(&key_blob, &blob_metadata),
+                            &BlobInfo::new(&key_blob, &blob_metadata),
                             &cert_info,
                             &key_metadata,
                             &self.km_uuid,
@@ -243,9 +243,13 @@
                 )
             }
             _ => {
+                let super_key = SUPER_KEY
+                    .read()
+                    .unwrap()
+                    .get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
                 let (key_id_guard, mut key_entry) = DB
                     .with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| {
-                        LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+                        LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
                             db.borrow_mut().load_key_entry(
                                 key,
                                 KeyType::Client,
@@ -721,9 +725,11 @@
         // Import_wrapped_key requires the rebind permission for the new key.
         check_key_permission(KeyPerm::Rebind, &key, &None).context("In import_wrapped_key.")?;
 
+        let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id);
+
         let (wrapping_key_id_guard, mut wrapping_key_entry) = DB
             .with(|db| {
-                LEGACY_IMPORTER.with_try_import(&key, caller_uid, || {
+                LEGACY_IMPORTER.with_try_import(&key, caller_uid, super_key, || {
                     db.borrow_mut().load_key_entry(
                         wrapping_key,
                         KeyType::Client,
@@ -829,7 +835,7 @@
     fn upgrade_keyblob_if_required_with<T, F>(
         &self,
         km_dev: &dyn IKeyMintDevice,
-        key_id_guard: Option<KeyIdGuard>,
+        mut key_id_guard: Option<KeyIdGuard>,
         key_blob: &KeyBlob,
         blob_metadata: &BlobMetaData,
         params: &[KeyParameter],
@@ -838,60 +844,42 @@
     where
         F: Fn(&[u8]) -> Result<T, Error>,
     {
-        match f(key_blob) {
-            Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
-                let upgraded_blob = {
-                    let _wp = self.watch_millis(
-                        concat!(
-                            "In KeystoreSecurityLevel::upgrade_keyblob_if_required_with: ",
-                            "calling upgradeKey."
-                        ),
-                        500,
-                    );
-                    map_km_error(km_dev.upgradeKey(key_blob, params))
-                }
-                .context("In upgrade_keyblob_if_required_with: Upgrade failed.")?;
-
-                if let Some(kid) = key_id_guard {
+        let (v, upgraded_blob) = crate::utils::upgrade_keyblob_if_required_with(
+            km_dev,
+            key_blob,
+            params,
+            f,
+            |upgraded_blob| {
+                if key_id_guard.is_some() {
+                    // Unwrap cannot panic, because the is_some was true.
+                    let kid = key_id_guard.take().unwrap();
                     Self::store_upgraded_keyblob(
                         kid,
                         blob_metadata.km_uuid(),
                         key_blob,
-                        &upgraded_blob,
+                        upgraded_blob,
                     )
-                    .context(
-                        "In upgrade_keyblob_if_required_with: store_upgraded_keyblob failed",
-                    )?;
+                    .context("In upgrade_keyblob_if_required_with: store_upgraded_keyblob failed")
+                } else {
+                    Ok(())
                 }
+            },
+        )
+        .context("In KeystoreSecurityLevel::upgrade_keyblob_if_required_with.")?;
 
-                match f(&upgraded_blob) {
-                    Ok(v) => Ok((v, Some(upgraded_blob))),
-                    Err(e) => Err(e).context(concat!(
+        // If no upgrade was needed, use the opportunity to reencrypt the blob if required
+        // and if the a key_id_guard is held. Note: key_id_guard can only be Some if no
+        // upgrade was performed above and if one was given in the first place.
+        if key_blob.force_reencrypt() {
+            if let Some(kid) = key_id_guard {
+                Self::store_upgraded_keyblob(kid, blob_metadata.km_uuid(), key_blob, key_blob)
+                    .context(concat!(
                         "In upgrade_keyblob_if_required_with: ",
-                        "Failed to perform operation on second try."
-                    )),
-                }
-            }
-            result => {
-                if let Some(kid) = key_id_guard {
-                    if key_blob.force_reencrypt() {
-                        Self::store_upgraded_keyblob(
-                            kid,
-                            blob_metadata.km_uuid(),
-                            key_blob,
-                            key_blob,
-                        )
-                        .context(concat!(
-                            "In upgrade_keyblob_if_required_with: ",
-                            "store_upgraded_keyblob failed in forced reencrypt"
-                        ))?;
-                    }
-                }
-                result
-                    .map(|v| (v, None))
-                    .context("In upgrade_keyblob_if_required_with: Called closure failed.")
+                        "store_upgraded_keyblob failed in forced reencrypt"
+                    ))?;
             }
         }
+        Ok((v, upgraded_blob))
     }
 
     fn convert_storage_key_to_ephemeral(
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index 46bc8b0..79e7692 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -22,11 +22,11 @@
 use crate::security_level::KeystoreSecurityLevel;
 use crate::utils::{
     check_grant_permission, check_key_permission, check_keystore_permission,
-    key_parameters_to_authorizations, list_key_entries, watchdog as wd,
+    key_parameters_to_authorizations, list_key_entries, uid_to_android_user, watchdog as wd,
 };
 use crate::{
     database::Uuid,
-    globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_IMPORTER},
+    globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_IMPORTER, SUPER_KEY},
 };
 use crate::{database::KEYSTORE_UUID, permission};
 use crate::{
@@ -130,9 +130,13 @@
 
     fn get_key_entry(&self, key: &KeyDescriptor) -> Result<KeyEntryResponse> {
         let caller_uid = ThreadState::get_calling_uid();
+
+        let super_key =
+            SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
         let (key_id_guard, mut key_entry) = DB
             .with(|db| {
-                LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+                LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
                     db.borrow_mut().load_key_entry(
                         key,
                         KeyType::Client,
@@ -182,8 +186,11 @@
         certificate_chain: Option<&[u8]>,
     ) -> Result<()> {
         let caller_uid = ThreadState::get_calling_uid();
+        let super_key =
+            SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
         DB.with::<_, Result<()>>(|db| {
-            let entry = match LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+            let entry = match LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
                 db.borrow_mut().load_key_entry(
                     key,
                     KeyType::Client,
@@ -291,8 +298,11 @@
 
     fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {
         let caller_uid = ThreadState::get_calling_uid();
+        let super_key =
+            SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
         DB.with(|db| {
-            LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+            LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
                 db.borrow_mut().unbind_key(key, KeyType::Client, caller_uid, |k, av| {
                     check_key_permission(KeyPerm::Delete, k, &av).context("During delete_key.")
                 })
@@ -309,8 +319,11 @@
         access_vector: permission::KeyPermSet,
     ) -> Result<KeyDescriptor> {
         let caller_uid = ThreadState::get_calling_uid();
+        let super_key =
+            SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
         DB.with(|db| {
-            LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+            LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
                 db.borrow_mut().grant(
                     key,
                     caller_uid,
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 2fb4991..376d44a 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -28,8 +28,7 @@
     legacy_blob::LegacyBlobLoader,
     legacy_importer::LegacyImporter,
     raw_device::KeyMintDevice,
-    utils::watchdog as wd,
-    utils::AID_KEYSTORE,
+    utils::{watchdog as wd, AesGcm, AID_KEYSTORE},
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     Algorithm::Algorithm, BlockMode::BlockMode, HardwareAuthToken::HardwareAuthToken,
@@ -154,15 +153,22 @@
     reencrypt_with: Option<Arc<SuperKey>>,
 }
 
-impl SuperKey {
-    /// For most purposes `unwrap_key` handles decryption,
-    /// but legacy handling and some tests need to assume AES and decrypt directly.
-    pub fn aes_gcm_decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> {
+impl AesGcm for SuperKey {
+    fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> {
         if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm {
             aes_gcm_decrypt(data, iv, tag, &self.key)
-                .context("In aes_gcm_decrypt: decryption failed")
+                .context("In SuperKey::decrypt: Decryption failed.")
         } else {
-            Err(Error::sys()).context("In aes_gcm_decrypt: Key is not an AES key")
+            Err(Error::sys()).context("In SuperKey::decrypt: Key is not an AES key.")
+        }
+    }
+
+    fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
+        if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm {
+            aes_gcm_encrypt(plaintext, &self.key)
+                .context("In SuperKey::encrypt: Encryption failed.")
+        } else {
+            Err(Error::sys()).context("In SuperKey::encrypt: Key is not an AES key.")
         }
     }
 }
@@ -386,7 +392,15 @@
         })
     }
 
-    pub fn get_per_boot_key_by_user_id(&self, user_id: UserId) -> Option<Arc<SuperKey>> {
+    pub fn get_per_boot_key_by_user_id(
+        &self,
+        user_id: UserId,
+    ) -> Option<Arc<dyn AesGcm + Send + Sync>> {
+        self.get_per_boot_key_by_user_id_internal(user_id)
+            .map(|sk| -> Arc<dyn AesGcm + Send + Sync> { sk })
+    }
+
+    fn get_per_boot_key_by_user_id_internal(&self, user_id: UserId) -> Option<Arc<SuperKey>> {
         self.data.user_keys.get(&user_id).and_then(|e| e.per_boot.as_ref().cloned())
     }
 
@@ -464,7 +478,7 @@
         match key.algorithm {
             SuperEncryptionAlgorithm::Aes256Gcm => match (metadata.iv(), metadata.aead_tag()) {
                 (Some(iv), Some(tag)) => key
-                    .aes_gcm_decrypt(blob, iv, tag)
+                    .decrypt(blob, iv, tag)
                     .context("In unwrap_key_with_key: Failed to decrypt the key blob."),
                 (iv, tag) => Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
                     concat!(
@@ -1093,7 +1107,7 @@
         legacy_importer: &LegacyImporter,
         user_id: UserId,
     ) -> Result<UserState> {
-        match self.get_per_boot_key_by_user_id(user_id) {
+        match self.get_per_boot_key_by_user_id_internal(user_id) {
             Some(super_key) => Ok(UserState::LskfUnlocked(super_key)),
             None => {
                 // Check if a super key exists in the database or legacy database.
@@ -1127,7 +1141,7 @@
         user_id: UserId,
         password: Option<&Password>,
     ) -> Result<UserState> {
-        match self.get_per_boot_key_by_user_id(user_id) {
+        match self.get_per_boot_key_by_user_id_internal(user_id) {
             Some(_) if password.is_none() => {
                 // Transitioning to swiping, delete only the super key in database and cache,
                 // and super-encrypted keys in database (and in KM).
@@ -1162,7 +1176,7 @@
         user_id: UserId,
         password: &Password,
     ) -> Result<UserState> {
-        match self.get_per_boot_key_by_user_id(user_id) {
+        match self.get_per_boot_key_by_user_id_internal(user_id) {
             Some(super_key) => {
                 log::info!("In unlock_and_get_user_state. Trying to unlock when already unlocked.");
                 Ok(UserState::LskfUnlocked(super_key))
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index c924bef..a312c4b 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -15,7 +15,8 @@
 //! This module implements utility functions used by the Keystore 2.0 service
 //! implementation.
 
-use crate::error::{map_binder_status, Error, ErrorCode};
+use crate::error::{map_binder_status, map_km_error, Error, ErrorCode};
+use crate::key_parameter::KeyParameter;
 use crate::permission;
 use crate::permission::{KeyPerm, KeyPermSet, KeystorePerm};
 use crate::{
@@ -23,7 +24,8 @@
     globals::LEGACY_IMPORTER,
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-    KeyCharacteristics::KeyCharacteristics, Tag::Tag,
+    IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics,
+    KeyParameter::KeyParameter as KmKeyParameter, Tag::Tag,
 };
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
 use android_security_apc::aidl::android::security::apc::{
@@ -40,6 +42,8 @@
     APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
     APC_COMPAT_ERROR_SYSTEM_ERROR,
 };
+use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt, ZVec};
+use std::iter::IntoIterator;
 
 /// This function uses its namesake in the permission module and in
 /// combination with with_calling_sid from the binder crate to check
@@ -135,18 +139,60 @@
 /// representation of the keystore service.
 pub fn key_characteristics_to_internal(
     key_characteristics: Vec<KeyCharacteristics>,
-) -> Vec<crate::key_parameter::KeyParameter> {
+) -> Vec<KeyParameter> {
     key_characteristics
         .into_iter()
         .flat_map(|aidl_key_char| {
             let sec_level = aidl_key_char.securityLevel;
-            aidl_key_char.authorizations.into_iter().map(move |aidl_kp| {
-                crate::key_parameter::KeyParameter::new(aidl_kp.into(), sec_level)
-            })
+            aidl_key_char
+                .authorizations
+                .into_iter()
+                .map(move |aidl_kp| KeyParameter::new(aidl_kp.into(), sec_level))
         })
         .collect()
 }
 
+/// This function can be used to upgrade key blobs on demand. The return value of
+/// `km_op` is inspected and if ErrorCode::KEY_REQUIRES_UPGRADE is encountered,
+/// an attempt is made to upgrade the key blob. On success `new_blob_handler` is called
+/// with the upgraded blob as argument. Then `km_op` is called a second time with the
+/// upgraded blob as argument. On success a tuple of the `km_op`s result and the
+/// optional upgraded blob is returned.
+pub fn upgrade_keyblob_if_required_with<T, KmOp, NewBlobHandler>(
+    km_dev: &dyn IKeyMintDevice,
+    key_blob: &[u8],
+    upgrade_params: &[KmKeyParameter],
+    km_op: KmOp,
+    new_blob_handler: NewBlobHandler,
+) -> Result<(T, Option<Vec<u8>>)>
+where
+    KmOp: Fn(&[u8]) -> Result<T, Error>,
+    NewBlobHandler: FnOnce(&[u8]) -> Result<()>,
+{
+    match km_op(key_blob) {
+        Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
+            let upgraded_blob = {
+                let _wp = watchdog::watch_millis(
+                    "In utils::upgrade_keyblob_if_required_with: calling upgradeKey.",
+                    500,
+                );
+                map_km_error(km_dev.upgradeKey(key_blob, upgrade_params))
+            }
+            .context("In utils::upgrade_keyblob_if_required_with: Upgrade failed.")?;
+
+            new_blob_handler(&upgraded_blob)
+                .context("In utils::upgrade_keyblob_if_required_with: calling new_blob_handler.")?;
+
+            km_op(&upgraded_blob)
+                .map(|v| (v, Some(upgraded_blob)))
+                .context("In utils::upgrade_keyblob_if_required_with: Calling km_op after upgrade.")
+        }
+        r => r
+            .map(|v| (v, None))
+            .context("In utils::upgrade_keyblob_if_required_with: Calling km_op."),
+    }
+}
+
 /// Converts a set of key characteristics from the internal representation into a set of
 /// Authorizations as they are used to convey key characteristics to the clients of keystore.
 pub fn key_parameters_to_authorizations(
@@ -255,6 +301,36 @@
     }
 }
 
+/// Trait implemented by objects that can be used to decrypt cipher text using AES-GCM.
+pub trait AesGcm {
+    /// Deciphers `data` using the initialization vector `iv` and AEAD tag `tag`
+    /// and AES-GCM. The implementation provides the key material and selects
+    /// the implementation variant, e.g., AES128 or AES265.
+    fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec>;
+
+    /// Encrypts `data` and returns the ciphertext, the initialization vector `iv`
+    /// and AEAD tag `tag`. The implementation provides the key material and selects
+    /// the implementation variant, e.g., AES128 or AES265.
+    fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)>;
+}
+
+/// Marks an object as AES-GCM key.
+pub trait AesGcmKey {
+    /// Provides access to the raw key material.
+    fn key(&self) -> &[u8];
+}
+
+impl<T: AesGcmKey> AesGcm for T {
+    fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> {
+        aes_gcm_decrypt(data, iv, tag, self.key())
+            .context("In AesGcm<T>::decrypt: Decryption failed")
+    }
+
+    fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
+        aes_gcm_encrypt(plaintext, self.key()).context("In AesGcm<T>::encrypt: Encryption failed.")
+    }
+}
+
 /// This module provides empty/noop implementations of the watch dog utility functions.
 #[cfg(not(feature = "watchdog"))]
 pub mod watchdog {