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.

Ignore-AOSP-First: Cherry-picked.

Bug: 213173772
Bug: 213172664
Bug: 203101472
Test: keystore2_test
Merged-In: Id8561d3f98d53182709d9f4feeeecda3b1535077
Change-Id: Id8561d3f98d53182709d9f4feeeecda3b1535077
diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs
index efa0870..c23c29c 100644
--- a/keystore2/legacykeystore/lib.rs
+++ b/keystore2/legacykeystore/lib.rs
@@ -25,8 +25,9 @@
 };
 use anyhow::{Context, Result};
 use keystore2::{
-    async_task::AsyncTask, legacy_blob::LegacyBlobLoader, maintenance::DeleteListener,
-    maintenance::Domain, utils::watchdog as wd,
+    async_task::AsyncTask, globals::SUPER_KEY, legacy_blob::LegacyBlobLoader,
+    maintenance::DeleteListener, maintenance::Domain, utils::uid_to_android_user,
+    utils::watchdog as wd,
 };
 use rusqlite::{
     params, Connection, OptionalExtension, Transaction, TransactionBehavior, NO_PARAMS,
@@ -312,8 +313,8 @@
         if let Some(entry) = db.get(uid, alias).context("In get: Trying to load entry from DB.")? {
             return Ok(entry);
         }
-        if self.get_legacy(uid, alias).context("In get: Trying to migrate legacy blob.")? {
-            // If we were able to migrate a legacy blob try again.
+        if self.get_legacy(uid, alias).context("In get: Trying to import legacy blob.")? {
+            // If we were able to import a legacy blob try again.
             if let Some(entry) =
                 db.get(uid, alias).context("In get: Trying to load entry from DB.")?
             {
@@ -325,19 +326,20 @@
 
     fn put(&self, alias: &str, uid: i32, entry: &[u8]) -> Result<()> {
         let uid = Self::get_effective_uid(uid).context("In put.")?;
-        // In order to make sure that we don't have stale legacy entries, make sure they are
-        // migrated before replacing them.
-        let _ = self.get_legacy(uid, alias);
         let mut db = self.open_db().context("In put.")?;
-        db.put(uid, alias, entry).context("In put: Trying to insert entry into DB.")
+        db.put(uid, alias, entry).context("In put: Trying to insert entry into DB.")?;
+        // When replacing an entry, make sure that there is no stale legacy file entry.
+        let _ = self.remove_legacy(uid, alias);
+        Ok(())
     }
 
     fn remove(&self, alias: &str, uid: i32) -> Result<()> {
         let uid = Self::get_effective_uid(uid).context("In remove.")?;
         let mut db = self.open_db().context("In remove.")?;
-        // In order to make sure that we don't have stale legacy entries, make sure they are
-        // migrated before removing them.
-        let _ = self.get_legacy(uid, alias);
+
+        if self.remove_legacy(uid, alias).context("In remove: trying to remove legacy entry")? {
+            return Ok(());
+        }
         let removed =
             db.remove(uid, alias).context("In remove: Trying to remove entry from DB.")?;
         if removed {
@@ -427,17 +429,30 @@
                 return Ok(true);
             }
             let mut db = DB::new(&state.db_path).context("In open_db: Failed to open db.")?;
-            let migrated =
-                Self::migrate_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db)
-                    .context("Trying to migrate legacy keystore entries.")?;
-            if migrated {
+            let imported =
+                Self::import_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db)
+                    .context("Trying to import legacy keystore entries.")?;
+            if imported {
                 state.recently_imported.insert((uid, alias));
             }
-            Ok(migrated)
+            Ok(imported)
         })
         .context("In get_legacy.")
     }
 
+    fn remove_legacy(&self, uid: u32, alias: &str) -> Result<bool> {
+        let alias = alias.to_string();
+        self.do_serialized(move |state| {
+            if state.recently_imported.contains(&(uid, alias.clone())) {
+                return Ok(false);
+            }
+            state
+                .legacy_loader
+                .remove_legacy_keystore_entry(uid, &alias)
+                .context("Trying to remove legacy entry.")
+        })
+    }
+
     fn bulk_delete_uid(&self, uid: u32) -> Result<()> {
         self.do_serialized(move |state| {
             let entries = state
@@ -470,21 +485,29 @@
         })
     }
 
-    fn migrate_one_legacy_entry(
+    fn import_one_legacy_entry(
         uid: u32,
         alias: &str,
         legacy_loader: &LegacyBlobLoader,
         db: &mut DB,
     ) -> Result<bool> {
         let blob = legacy_loader
-            .read_legacy_keystore_entry(uid, alias)
-            .context("In migrate_one_legacy_entry: Trying to read legacy keystore entry.")?;
+            .read_legacy_keystore_entry(uid, alias, |ciphertext, iv, tag, _salt, _key_size| {
+                if let Some(key) =
+                    SUPER_KEY.get_per_boot_key_by_user_id(uid_to_android_user(uid as u32))
+                {
+                    key.decrypt(ciphertext, iv, tag)
+                } else {
+                    Err(Error::sys()).context("No key found for user. Device may be locked.")
+                }
+            })
+            .context("In import_one_legacy_entry: Trying to read legacy keystore entry.")?;
         if let Some(entry) = blob {
             db.put(uid, alias, &entry)
-                .context("In migrate_one_legacy_entry: Trying to insert entry into DB.")?;
+                .context("In import_one_legacy_entry: Trying to insert entry into DB.")?;
             legacy_loader
                 .remove_legacy_keystore_entry(uid, alias)
-                .context("In migrate_one_legacy_entry: Trying to delete legacy keystore entry.")?;
+                .context("In import_one_legacy_entry: Trying to delete legacy keystore entry.")?;
             Ok(true)
         } else {
             Ok(false)
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index de23328..8acd775 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.")
@@ -3235,6 +3286,7 @@
     use std::sync::Arc;
     use std::thread;
     use std::time::{Duration, SystemTime};
+    use crate::utils::AesGcm;
     #[cfg(disabled)]
     use std::time::Instant;
 
@@ -5561,8 +5613,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 6b16d2e..8cbcda1 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.
@@ -1004,79 +1185,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 +1307,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 +1627,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 +1638,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 +1727,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 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 +1805,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 +1822,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 +1845,196 @@
         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 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,7 +2044,7 @@
 
     #[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());
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 107b9b0..ee5a13f 100644
--- a/keystore2/src/legacy_importer.rs
+++ b/keystore2/src/legacy_importer.rs
@@ -14,19 +14,23 @@
 
 //! 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+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::{
+    IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel,
+};
+use android_hardware_security_keymint::binder::Strong;
 use android_system_keystore2::aidl::android::system::keystore2::{
     Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
 };
@@ -34,6 +38,7 @@
 use core::ops::Deref;
 use keystore2_crypto::{Password, ZVec};
 use std::collections::{HashMap, HashSet};
+use std::convert::TryInto;
 use std::sync::atomic::{AtomicU8, Ordering};
 use std::sync::mpsc::channel;
 use std::sync::{Arc, Mutex};
@@ -287,6 +292,7 @@
         &self,
         key: &KeyDescriptor,
         caller_uid: u32,
+        super_key: Option<Arc<dyn AesGcm + Send + Sync>>,
         key_accessor: F,
     ) -> Result<T>
     where
@@ -323,8 +329,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 +438,166 @@
             .context("In list_uid: Trying to list legacy entries.")
     }
 
+    /// 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 +616,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 characteristics.")?;
+
         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 +677,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 +798,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 +881,79 @@
     }
 }
 
-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 get_key_characteristics_without_app_data: Trying to get km device for id {:?}",
+            uuid
+        )
+    })?;
+
+    let km_dev: Strong<dyn IKeyMintDevice> = km_dev
+        .get_interface()
+        .context("In get_key_characteristics_without_app_data: Failed to get keymint device.")?;
+
+    let (characteristics, upgraded_blob) = upgrade_keyblob_if_required_with(
+        &*km_dev,
+        blob,
+        &[],
+        |blob| {
+            let _wd = wd::watch_millis(
+                "In get_key_characteristics_without_app_data: 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 468a37d..ced8986 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -22,7 +22,9 @@
 use crate::globals::{DB, LEGACY_IMPORTER, SUPER_KEY};
 use crate::permission::{KeyPerm, KeystorePerm};
 use crate::super_key::UserState;
-use crate::utils::{check_key_permission, check_keystore_permission, watchdog as wd};
+use crate::utils::{
+    check_key_permission, check_keystore_permission, uid_to_android_user, watchdog as wd,
+};
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::IKeyMintDevice::IKeyMintDevice;
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
 use android_security_maintenance::aidl::android::security::maintenance::{
@@ -219,11 +221,13 @@
     fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
         let caller_uid = ThreadState::get_calling_uid();
 
+        let super_key = SUPER_KEY.get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
         DB.with(|db| {
             let key_id_guard = match source.domain {
                 Domain::APP | Domain::SELINUX | Domain::KEY_ID => {
                     let (key_id_guard, _) = LEGACY_IMPORTER
-                        .with_try_import(&source, caller_uid, || {
+                        .with_try_import(&source, caller_uid, super_key, || {
                             db.borrow_mut().load_key_entry(
                                 &source,
                                 KeyType::Client,
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
index cd54915..a883987 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,
@@ -123,7 +124,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 9ba38ec..70264d3 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;
@@ -180,7 +180,7 @@
                             &key,
                             KeyType::Client,
                             &key_parameters,
-                            &(&key_blob, &blob_metadata),
+                            &BlobInfo::new(&key_blob, &blob_metadata),
                             &cert_info,
                             &key_metadata,
                             &self.km_uuid,
@@ -241,9 +241,11 @@
                 )
             }
             _ => {
+                let super_key =
+                    SUPER_KEY.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,
@@ -717,9 +719,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.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,
@@ -823,7 +827,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],
@@ -832,60 +836,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 b7c90f7..646e7b1 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, watchdog as wd, Asp,
+    key_parameters_to_authorizations, uid_to_android_user, watchdog as wd, Asp,
 };
 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::{
@@ -132,9 +132,12 @@
 
     fn get_key_entry(&self, key: &KeyDescriptor) -> Result<KeyEntryResponse> {
         let caller_uid = ThreadState::get_calling_uid();
+
+        let super_key = SUPER_KEY.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,
@@ -184,8 +187,10 @@
         certificate_chain: Option<&[u8]>,
     ) -> Result<()> {
         let caller_uid = ThreadState::get_calling_uid();
+        let super_key = SUPER_KEY.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,
@@ -308,8 +313,10 @@
 
     fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {
         let caller_uid = ThreadState::get_calling_uid();
+        let super_key = SUPER_KEY.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.")
                 })
@@ -326,8 +333,10 @@
         access_vector: permission::KeyPermSet,
     ) -> Result<KeyDescriptor> {
         let caller_uid = ThreadState::get_calling_uid();
+        let super_key = SUPER_KEY.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 cfcab80..d261321 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -29,8 +29,7 @@
     legacy_importer::LegacyImporter,
     raw_device::KeyMintDevice,
     try_insert::TryInsert,
-    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,
@@ -157,15 +156,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.")
         }
     }
 }
@@ -378,7 +384,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>> {
         let data = self.data.lock().unwrap();
         data.user_keys.get(&user_id).and_then(|e| e.per_boot.as_ref().cloned())
     }
@@ -457,7 +471,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!(
@@ -1081,7 +1095,7 @@
         skm: &SuperKeyManager,
         user_id: UserId,
     ) -> Result<UserState> {
-        match skm.get_per_boot_key_by_user_id(user_id) {
+        match skm.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.
@@ -1105,7 +1119,7 @@
         user_id: UserId,
         password: Option<&Password>,
     ) -> Result<UserState> {
-        match skm.get_per_boot_key_by_user_id(user_id) {
+        match skm.get_per_boot_key_by_user_id_internal(user_id) {
             Some(super_key) => {
                 if password.is_none() {
                     //transitioning to swiping, delete only the super key in database and cache, and
@@ -1139,7 +1153,7 @@
         user_id: UserId,
         password: &Password,
     ) -> Result<UserState> {
-        match skm.get_per_boot_key_by_user_id(user_id) {
+        match skm.get_per_boot_key_by_user_id_internal(user_id) {
             Some(super_key) => {
                 log::info!("In get_with_password_unlock. Trying to unlock when already unlocked.");
                 Ok(UserState::LskfUnlocked(super_key))
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index a110c64..5a42b25 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -15,11 +15,13 @@
 //! 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 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::{
@@ -29,13 +31,15 @@
 use android_system_keystore2::aidl::android::system::keystore2::{
     Authorization::Authorization, KeyDescriptor::KeyDescriptor,
 };
-use anyhow::{anyhow, Context};
+use anyhow::{anyhow, Context, Result};
 use binder::{FromIBinder, SpIBinder, ThreadState};
 use keystore2_apc_compat::{
     ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED,
     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;
 use std::sync::Mutex;
 
 /// This function uses its namesake in the permission module and in
@@ -165,18 +169,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(
@@ -264,6 +310,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 {