Merge "Remove alanstokes@google.com from fsverity/OWNERS" into main
diff --git a/OWNERS b/OWNERS
index 6fdb550..b1c1fea 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,11 +1,7 @@
-alanstokes@google.com
 drysdale@google.com
-eranm@google.com
 hasinitg@google.com
 jbires@google.com
 jeffv@google.com
-kroot@google.com
 sethmo@google.com
 swillden@google.com
-trong@google.com
 zeuthen@google.com
diff --git a/fsverity/fsverity_manifest_generator.py b/fsverity/fsverity_manifest_generator.py
index ca7ac5c..d232450 100644
--- a/fsverity/fsverity_manifest_generator.py
+++ b/fsverity/fsverity_manifest_generator.py
@@ -31,7 +31,7 @@
   cmd = [fsverity_path, 'digest', input_file]
   cmd.extend(['--compact'])
   cmd.extend(['--hash-alg', HASH_ALGORITHM])
-  out = subprocess.check_output(cmd, universal_newlines=True).strip()
+  out = subprocess.check_output(cmd, text=True).strip()
   return bytes(bytearray.fromhex(out))
 
 if __name__ == '__main__':
@@ -46,22 +46,50 @@
       required=True)
   p.add_argument(
       '--base-dir',
-      help='directory to use as a relative root for the inputs',
-      required=True)
+      help='directory to use as a relative root for the inputs. Also see the documentation of '
+      'inputs')
   p.add_argument(
       'inputs',
       nargs='*',
-      help='input file for the build manifest')
+      help='input file for the build manifest. It can be in either of two forms: <file> or '
+      '<file>,<path_on_device>. If the first form is used, --base-dir must be provided, and the '
+      'path on device will be the filepath relative to the base dir')
   args = p.parse_args()
 
+  links = {}
   digests = FSVerityDigests()
   for f in sorted(args.inputs):
-    # f is a full path for now; make it relative so it starts with {mount_point}/
-    digest = digests.digests[os.path.relpath(f, args.base_dir)]
-    digest.digest = _digest(args.fsverity_path, f)
-    digest.hash_alg = HASH_ALGORITHM
+    if args.base_dir:
+      # f is a full path for now; make it relative so it starts with {mount_point}/
+      rel = os.path.relpath(f, args.base_dir)
+    else:
+      parts = f.split(',')
+      if len(parts) != 2 or not parts[0] or not parts[1]:
+        sys.exit("Since --base-path wasn't provided, all inputs must be pairs separated by commas "
+          "but this input wasn't: " + f)
+      f, rel = parts
 
-  manifest = digests.SerializeToString()
+    # Some fsv_meta files are links to other ones. Don't read through the link, because the
+    # layout of files in the build system may not match the layout of files on the device.
+    # Instead, read its target and use it to copy the digest from the real file after all files
+    # are processed.
+    if os.path.islink(f):
+      links[rel] = os.path.normpath(os.path.join(os.path.dirname(rel), os.readlink(f)))
+    else:
+      digest = digests.digests[rel]
+      digest.digest = _digest(args.fsverity_path, f)
+      digest.hash_alg = HASH_ALGORITHM
+
+  for link_rel, real_rel in links.items():
+    if real_rel not in digests.digests:
+      sys.exit(f'There was a fsv_meta symlink to {real_rel}, but that file was not a fsv_meta file')
+    link_digest = digests.digests[link_rel]
+    real_digest = digests.digests[real_rel]
+    link_digest.CopyFrom(real_digest)
+
+  # Serialize with deterministic=True for reproducible builds and build caching.
+  # The serialized contents will still change across different versions of protobuf.
+  manifest = digests.SerializeToString(deterministic=True)
 
   with open(args.output, "wb") as f:
     f.write(manifest)
diff --git a/fsverity_init/Android.bp b/fsverity_init/Android.bp
deleted file mode 100644
index 212aac4..0000000
--- a/fsverity_init/Android.bp
+++ /dev/null
@@ -1,41 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "system_security_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["system_security_license"],
-}
-
-cc_binary {
-    name: "fsverity_init",
-    srcs: [
-        "fsverity_init.cpp",
-    ],
-    static_libs: [
-        "aconfig_fsverity_init_c_lib",
-        "libmini_keyctl_static",
-    ],
-    shared_libs: [
-        "libbase",
-        "libkeyutils",
-        "liblog",
-    ],
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-Wextra",
-    ],
-}
-
-aconfig_declarations {
-    name: "aconfig_fsverity_init",
-    package: "android.security.flag",
-    container: "system",
-    srcs: ["flags.aconfig"],
-}
-
-cc_aconfig_library {
-    name: "aconfig_fsverity_init_c_lib",
-    aconfig_declarations: "aconfig_fsverity_init",
-}
diff --git a/fsverity_init/OWNERS b/fsverity_init/OWNERS
deleted file mode 100644
index f9e7b25..0000000
--- a/fsverity_init/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-alanstokes@google.com
-ebiggers@google.com
-jeffv@google.com
-jiyong@google.com
-victorhsieh@google.com
diff --git a/fsverity_init/flags.aconfig b/fsverity_init/flags.aconfig
deleted file mode 100644
index 495c71c..0000000
--- a/fsverity_init/flags.aconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-package: "android.security.flag"
-container: "system"
-
-flag {
-    name: "deprecate_fsverity_init"
-    namespace: "hardware_backed_security"
-    description: "Feature flag for deprecate fsverity_init"
-    bug: "290064770"
-    is_fixed_read_only: true
-}
diff --git a/fsverity_init/fsverity_init.cpp b/fsverity_init/fsverity_init.cpp
deleted file mode 100644
index 717beeb..0000000
--- a/fsverity_init/fsverity_init.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// fsverity_init is a tool for loading X.509 certificates into the kernel keyring used by the
-// fsverity builtin signature verification kernel feature
-// (https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#built-in-signature-verification).
-// Starting in Android 14, Android has actually stopped using this feature, as it was too inflexible
-// and caused problems.  It has been replaced by userspace signature verification.  Also, some uses
-// of fsverity in Android are now for integrity-only use cases.
-//
-// Regardless, there may exist fsverity files on-disk that were created by Android 13 or earlier.
-// These files still have builtin signatures.  If the kernel is an older kernel that still has
-// CONFIG_FS_VERITY_BUILTIN_SIGNATURES enabled, these files cannot be opened unless the
-// corresponding key is in the ".fs-verity" keyring.  Therefore, this tool still has to exist and be
-// used to load keys into the kernel, even though this has no security purpose anymore.
-//
-// This tool can be removed as soon as all supported kernels are guaranteed to have
-// CONFIG_FS_VERITY_BUILTIN_SIGNATURES disabled, or alternatively as soon as support for upgrades
-// from Android 13 or earlier is no longer required.
-//
-
-#define LOG_TAG "fsverity_init"
-
-#include <sys/types.h>
-
-#include <filesystem>
-#include <string>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <android_security_flag.h>
-#include <log/log.h>
-#include <mini_keyctl_utils.h>
-
-void LoadKeyFromFile(key_serial_t keyring_id, const char* keyname, const std::string& path) {
-    LOG(INFO) << "LoadKeyFromFile path=" << path << " keyname=" << keyname;
-    std::string content;
-    if (!android::base::ReadFileToString(path, &content)) {
-        LOG(ERROR) << "Failed to read key from " << path;
-        return;
-    }
-    if (add_key("asymmetric", keyname, content.c_str(), content.size(), keyring_id) < 0) {
-        PLOG(ERROR) << "Failed to add key from " << path;
-    }
-}
-
-void LoadKeyFromDirectory(key_serial_t keyring_id, const char* keyname_prefix, const char* dir) {
-    if (!std::filesystem::exists(dir)) {
-        return;
-    }
-    int counter = 0;
-    for (const auto& entry : std::filesystem::directory_iterator(dir)) {
-        if (!android::base::EndsWithIgnoreCase(entry.path().c_str(), ".der")) continue;
-        std::string keyname = keyname_prefix + std::to_string(counter);
-        counter++;
-        LoadKeyFromFile(keyring_id, keyname.c_str(), entry.path());
-    }
-}
-
-void LoadKeyFromVerifiedPartitions(key_serial_t keyring_id) {
-    // NB: Directories need to be synced with FileIntegrityService.java in
-    // frameworks/base.
-    LoadKeyFromDirectory(keyring_id, "fsv_system_", "/system/etc/security/fsverity");
-    LoadKeyFromDirectory(keyring_id, "fsv_product_", "/product/etc/security/fsverity");
-}
-
-int main(int argc, const char** argv) {
-    if (android::security::flag::deprecate_fsverity_init()) {
-        // Don't load keys to the built-in fs-verity keyring in kernel. This will make existing
-        // files not readable. We expect to only enable the flag when there are no such files or
-        // when failure is ok (e.g. with a fallback).
-        return 0;
-    }
-
-    if (argc < 2) {
-        LOG(ERROR) << "Not enough arguments";
-        return -1;
-    }
-
-    key_serial_t keyring_id = android::GetKeyringId(".fs-verity");
-    if (keyring_id < 0) {
-        // This is expected on newer kernels.  See comment at the beginning of this file.
-        LOG(DEBUG) << "no initialization required";
-        return 0;
-    }
-
-    const std::string_view command = argv[1];
-
-    if (command == "--load-verified-keys") {
-        LoadKeyFromVerifiedPartitions(keyring_id);
-    } else {
-        LOG(ERROR) << "Unknown argument(s).";
-        return -1;
-    }
-
-    return 0;
-}
diff --git a/keystore-engine/Android.bp b/keystore-engine/Android.bp
index 7fbfe53..d763445 100644
--- a/keystore-engine/Android.bp
+++ b/keystore-engine/Android.bp
@@ -27,7 +27,8 @@
     name: "libkeystore-engine",
 
     defaults: [
-        "keystore2_use_latest_aidl_ndk_shared",
+        "keymint_use_latest_hal_aidl_ndk_static",
+        "keystore2_use_latest_aidl_ndk_static",
     ],
     srcs: [
         "android_engine.cpp",
@@ -41,7 +42,6 @@
     ],
 
     shared_libs: [
-        "android.system.keystore2-V4-ndk",
         "libbinder_ndk",
         "libcrypto",
         "libcutils",
diff --git a/keystore/keystore_attestation_id.cpp b/keystore/keystore_attestation_id.cpp
index bcd3318..c91f86f 100644
--- a/keystore/keystore_attestation_id.cpp
+++ b/keystore/keystore_attestation_id.cpp
@@ -21,6 +21,7 @@
 #include <log/log.h>
 
 #include <memory>
+#include <mutex>
 #include <string>
 #include <vector>
 
@@ -51,6 +52,7 @@
 constexpr const char* kAttestationSystemPackageName = "AndroidSystem";
 constexpr const size_t kMaxAttempts = 3;
 constexpr const unsigned long kRetryIntervalUsecs = 500000;  // sleep for 500 ms
+constexpr const char* kProviderServiceName = "sec_key_att_app_id_provider";
 
 std::vector<uint8_t> signature2SHA256(const security::keystore::Signature& sig) {
     std::vector<uint8_t> digest_buffer(SHA256_DIGEST_LENGTH);
@@ -61,24 +63,24 @@
 using ::aidl::android::system::keystore2::ResponseCode;
 using ::android::security::keystore::BpKeyAttestationApplicationIdProvider;
 
-class KeyAttestationApplicationIdProvider : public BpKeyAttestationApplicationIdProvider {
-  public:
-    KeyAttestationApplicationIdProvider();
+[[clang::no_destroy]] std::mutex gServiceMu;
+[[clang::no_destroy]] std::shared_ptr<BpKeyAttestationApplicationIdProvider>
+    gService;  // GUARDED_BY gServiceMu
 
-    static KeyAttestationApplicationIdProvider& get();
-
-  private:
-    android::sp<android::IServiceManager> service_manager_;
-};
-
-KeyAttestationApplicationIdProvider& KeyAttestationApplicationIdProvider::get() {
-    static KeyAttestationApplicationIdProvider mpm;
-    return mpm;
+std::shared_ptr<BpKeyAttestationApplicationIdProvider> get_service() {
+    std::lock_guard<std::mutex> guard(gServiceMu);
+    if (gService.get() == nullptr) {
+        gService = std::make_shared<BpKeyAttestationApplicationIdProvider>(
+            android::defaultServiceManager()->waitForService(String16(kProviderServiceName)));
+    }
+    return gService;
 }
 
-KeyAttestationApplicationIdProvider::KeyAttestationApplicationIdProvider()
-    : BpKeyAttestationApplicationIdProvider(android::defaultServiceManager()->waitForService(
-          String16("sec_key_att_app_id_provider"))) {}
+void reset_service() {
+    std::lock_guard<std::mutex> guard(gServiceMu);
+    // Drop the global reference; any thread that already has a reference can keep using it.
+    gService.reset();
+}
 
 DECLARE_STACK_OF(ASN1_OCTET_STRING);
 
@@ -281,23 +283,31 @@
         key_attestation_id.packageInfos.push_back(std::move(pinfo));
     } else {
         /* Get the attestation application ID from package manager */
-        auto& pm = KeyAttestationApplicationIdProvider::get();
         ::android::binder::Status status;
 
-        // Retry on failure if a service specific error code.
+        // Retry on failure.
         for (size_t attempt{0}; attempt < kMaxAttempts; ++attempt) {
-            status = pm.getKeyAttestationApplicationId(uid, &key_attestation_id);
+            auto pm = get_service();
+            status = pm->getKeyAttestationApplicationId(uid, &key_attestation_id);
             if (status.isOk()) {
                 break;
-            } else if (status.exceptionCode() != binder::Status::EX_SERVICE_SPECIFIC) {
-                ALOGW("Retry: key attestation ID failed with service specific error: %s %d",
-                      status.exceptionMessage().c_str(), status.serviceSpecificErrorCode());
-                usleep(kRetryIntervalUsecs);
-            } else {
-                ALOGW("Retry: key attestation ID failed with error: %s %d",
-                      status.exceptionMessage().c_str(), status.exceptionCode());
-                usleep(kRetryIntervalUsecs);
             }
+
+            if (status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) {
+                ALOGW("Retry: get attestation ID for %d failed with service specific error: %s %d",
+                      uid, status.exceptionMessage().c_str(), status.serviceSpecificErrorCode());
+            } else if (status.exceptionCode() == binder::Status::EX_TRANSACTION_FAILED) {
+                // If the transaction failed, drop the package manager connection so that the next
+                // attempt will try again.
+                ALOGW(
+                    "Retry: get attestation ID for %d transaction failed, reset connection: %s %d",
+                    uid, status.exceptionMessage().c_str(), status.exceptionCode());
+                reset_service();
+            } else {
+                ALOGW("Retry: get attestation ID for %d failed with error: %s %d", uid,
+                      status.exceptionMessage().c_str(), status.exceptionCode());
+            }
+            usleep(kRetryIntervalUsecs);
         }
 
         if (!status.isOk()) {
diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp
index ab3e22c..d442e48 100644
--- a/keystore/keystore_cli_v2.cpp
+++ b/keystore/keystore_cli_v2.cpp
@@ -22,11 +22,12 @@
 #include <variant>
 #include <vector>
 
+#include <android-base/strings.h>
+
 #include <base/command_line.h>
 #include <base/files/file_util.h>
 #include <base/strings/string_number_conversions.h>
 #include <base/strings/string_split.h>
-#include <base/strings/string_util.h>
 
 #include <aidl/android/security/apc/BnConfirmationCallback.h>
 #include <aidl/android/security/apc/IProtectedConfirmation.h>
@@ -705,12 +706,12 @@
     std::vector<TestCase> test_cases = GetTestCases();
     for (const auto& test_case : test_cases) {
         if (!prefix.empty() &&
-            !base::StartsWith(test_case.name, prefix, base::CompareCase::SENSITIVE)) {
+            !android::base::StartsWith(test_case.name, prefix)) {
             continue;
         }
         if (test_for_0_3 &&
-            (base::StartsWith(test_case.name, "AES", base::CompareCase::SENSITIVE) ||
-             base::StartsWith(test_case.name, "HMAC", base::CompareCase::SENSITIVE))) {
+            (android::base::StartsWith(test_case.name, "AES") ||
+             android::base::StartsWith(test_case.name, "HMAC"))) {
             continue;
         }
         ++test_count;
@@ -1016,8 +1017,7 @@
         return 1;
     }
 
-    std::vector<std::string> pieces =
-        base::SplitString(uiOptionsStr, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    std::vector<std::string> pieces = android::base::Tokenize(uiOptionsStr, ",");
     int uiOptionsAsFlags = 0;
     for (auto& p : pieces) {
         int value;
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index ed9cd88..4878eb3 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_hardware_backed_security",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "system_security_license"
@@ -30,7 +31,10 @@
         "keystore2_use_latest_aidl_rust",
         "structured_log_rust_defaults",
     ],
-
+    cfgs: select(release_flag("RELEASE_AVF_ENABLE_EARLY_VM"), {
+        true: ["early_vm"],
+        default: [],
+    }),
     rustlibs: [
         "android.hardware.security.rkp-V3-rust",
         "android.hardware.security.secureclock-V1-rust",
@@ -42,10 +46,14 @@
         "android.security.maintenance-rust",
         "android.security.metrics-rust",
         "android.security.rkp_aidl-rust",
+        "apex_aidl_interface-rust",
         "libaconfig_android_hardware_biometrics_rust",
         "libandroid_security_flags_rust",
         "libanyhow",
         "libbinder_rs",
+        "libbssl_crypto",
+        "libder",
+        "libhex",
         "libkeystore2_aaid-rust",
         "libkeystore2_apc_compat-rust",
         "libkeystore2_crypto_rust",
@@ -53,10 +61,10 @@
         "libkeystore2_hal_names_rust",
         "libkeystore2_km_compat",
         "libkeystore2_selinux",
-        "liblazy_static",
         "liblibc",
         "liblog_rust",
         "libmessage_macro",
+        "libpostprocessor_client",
         "librand",
         "librkpd_client",
         "librustutils",
@@ -105,12 +113,12 @@
     defaults: ["libkeystore2_defaults"],
     rustlibs: [
         "libandroid_logger",
-        "libhex",
         "libkeystore2_test_utils",
         "libkeystore2_with_test_utils",
         "liblibsqlite3_sys",
         "libnix",
         "librusqlite",
+        "libtempfile",
     ],
     // The test should always include watchdog.
     features: [
@@ -120,6 +128,11 @@
     require_root: true,
 }
 
+vintf_fragment {
+    name: "android.system.keystore2-service.xml",
+    src: "android.system.keystore2-service.xml",
+}
+
 rust_defaults {
     name: "keystore2_defaults",
     srcs: ["src/keystore2_main.rs"],
@@ -138,7 +151,7 @@
     // selection available in the build system.
     prefer_rlib: true,
 
-    vintf_fragments: ["android.system.keystore2-service.xml"],
+    vintf_fragment_modules: ["android.system.keystore2-service.xml"],
 
     required: ["keystore_cli_v2"],
 }
@@ -162,6 +175,23 @@
     srcs: ["aconfig/flags.aconfig"],
 }
 
+java_aconfig_library {
+    name: "keystore2_flags_java",
+    aconfig_declarations: "keystore2_flags",
+}
+
+java_aconfig_library {
+    name: "keystore2_flags_java-host",
+    aconfig_declarations: "keystore2_flags",
+    host_supported: true,
+}
+
+java_aconfig_library {
+    name: "keystore2_flags_java-framework",
+    aconfig_declarations: "keystore2_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 rust_aconfig_library {
     name: "libkeystore2_flags_rust",
     crate_name: "keystore2_flags",
diff --git a/keystore2/OWNERS b/keystore2/OWNERS
index bf9d61b..1fcc785 100644
--- a/keystore2/OWNERS
+++ b/keystore2/OWNERS
@@ -1,8 +1,9 @@
 set noparent
 # Bug component: 1084732
-eranm@google.com
+cvlasov@google.com
 drysdale@google.com
 hasinitg@google.com
 jbires@google.com
+kwadhera@google.com
 sethmo@google.com
 swillden@google.com
diff --git a/keystore2/TEST_MAPPING b/keystore2/TEST_MAPPING
index 57ce78c..f12a301 100644
--- a/keystore2/TEST_MAPPING
+++ b/keystore2/TEST_MAPPING
@@ -1,6 +1,9 @@
 {
   "presubmit": [
     {
+      "name": "keystore2_client_tests"
+    },
+    {
       "name": "keystore2_crypto_test"
     },
     {
@@ -32,9 +35,6 @@
       "name": "CtsKeystorePerformanceTestCases"
     },
     {
-      "name": "keystore2_client_tests"
-    },
-    {
       "name": "librkpd_client.test"
     },
     {
diff --git a/keystore2/aconfig/flags.aconfig b/keystore2/aconfig/flags.aconfig
index 856b42e..b15230e 100644
--- a/keystore2/aconfig/flags.aconfig
+++ b/keystore2/aconfig/flags.aconfig
@@ -18,6 +18,22 @@
 }
 
 flag {
+  name: "disable_legacy_keystore_get"
+  namespace: "hardware_backed_security"
+  description: "This flag disables legacy keystore get and makes it so that get returns an error"
+  bug: "307460850"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "enable_dump"
+  namespace: "hardware_backed_security"
+  description: "Include support for dump() on the IKeystoreMaintenance service"
+  bug: "344987718"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "import_previously_emulated_keys"
   namespace: "hardware_backed_security"
   description: "Include support for importing keys that were previously software-emulated into KeyMint"
@@ -26,9 +42,17 @@
 }
 
 flag {
-  name: "database_loop_timeout"
+  name: "use_blob_state_column"
   namespace: "hardware_backed_security"
-  description: "Abandon Keystore database retry loop after an interval"
+  description: "Use state database column to track superseded blobentry rows"
   bug: "319563050"
   is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+  name: "attest_modules"
+  namespace: "hardware_backed_security"
+  description: "Support attestation of modules"
+  bug: "369375199"
+  is_fixed_read_only: true
+}
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index c297a15..13bf455 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -22,29 +22,10 @@
 }
 
 aidl_interface {
-    name: "android.security.attestationmanager",
-    srcs: ["android/security/attestationmanager/*.aidl"],
-    imports: ["android.hardware.security.keymint-V3"],
-    unstable: true,
-    backend: {
-        java: {
-            platform_apis: true,
-        },
-        rust: {
-            enabled: true,
-        },
-        ndk: {
-            enabled: true,
-            apps_enabled: false,
-        },
-    },
-}
-
-aidl_interface {
     name: "android.security.authorization",
     srcs: ["android/security/authorization/*.aidl"],
+    defaults: ["android.hardware.security.keymint-latest-defaults"],
     imports: [
-        "android.hardware.security.keymint-V3",
         "android.hardware.security.secureclock-V1",
     ],
     unstable: true,
@@ -82,8 +63,8 @@
 aidl_interface {
     name: "android.security.compat",
     srcs: ["android/security/compat/*.aidl"],
+    defaults: ["android.hardware.security.keymint-latest-defaults"],
     imports: [
-        "android.hardware.security.keymint-V3",
         "android.hardware.security.secureclock-V1",
         "android.hardware.security.sharedsecret-V1",
     ],
@@ -105,8 +86,8 @@
 aidl_interface {
     name: "android.security.maintenance",
     srcs: ["android/security/maintenance/*.aidl"],
-    imports: [
-        "android.system.keystore2-V4",
+    defaults: [
+        "android.system.keystore2-latest-defaults",
     ],
     unstable: true,
     backend: {
@@ -142,10 +123,30 @@
 }
 
 aidl_interface {
+    name: "android.security.postprocessor",
+    srcs: ["android/security/postprocessor/*.aidl"],
+    unstable: true,
+    backend: {
+        java: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+        ndk: {
+            enabled: false,
+        },
+        rust: {
+            enabled: true,
+        },
+    },
+}
+
+aidl_interface {
     name: "android.security.metrics",
     srcs: ["android/security/metrics/*.aidl"],
-    imports: [
-        "android.system.keystore2-V4",
+    defaults: [
+        "android.system.keystore2-latest-defaults",
     ],
     unstable: true,
     backend: {
@@ -168,21 +169,21 @@
 java_defaults {
     name: "keystore2_use_latest_aidl_java_static",
     static_libs: [
-        "android.system.keystore2-V4-java-source",
+        "android.system.keystore2-V5-java-source",
     ],
 }
 
 java_defaults {
     name: "keystore2_use_latest_aidl_java_shared",
     libs: [
-        "android.system.keystore2-V4-java-source",
+        "android.system.keystore2-V5-java-source",
     ],
 }
 
 java_defaults {
     name: "keystore2_use_latest_aidl_java",
     libs: [
-        "android.system.keystore2-V4-java",
+        "android.system.keystore2-V5-java",
     ],
 }
 
@@ -192,28 +193,28 @@
 cc_defaults {
     name: "keystore2_use_latest_aidl_ndk_static",
     static_libs: [
-        "android.system.keystore2-V4-ndk",
+        "android.system.keystore2-V5-ndk",
     ],
 }
 
 cc_defaults {
     name: "keystore2_use_latest_aidl_ndk_shared",
     shared_libs: [
-        "android.system.keystore2-V4-ndk",
+        "android.system.keystore2-V5-ndk",
     ],
 }
 
 cc_defaults {
     name: "keystore2_use_latest_aidl_cpp_shared",
     shared_libs: [
-        "android.system.keystore2-V4-cpp",
+        "android.system.keystore2-V5-cpp",
     ],
 }
 
 cc_defaults {
     name: "keystore2_use_latest_aidl_cpp_static",
     static_libs: [
-        "android.system.keystore2-V4-cpp",
+        "android.system.keystore2-V5-cpp",
     ],
 }
 
@@ -223,6 +224,6 @@
 rust_defaults {
     name: "keystore2_use_latest_aidl_rust",
     rustlibs: [
-        "android.system.keystore2-V4-rust",
+        "android.system.keystore2-V5-rust",
     ],
 }
diff --git a/keystore2/aidl/android/security/attestationmanager/ByteArray.aidl b/keystore2/aidl/android/security/attestationmanager/ByteArray.aidl
deleted file mode 100644
index dc37b1b..0000000
--- a/keystore2/aidl/android/security/attestationmanager/ByteArray.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.attestationmanager;
-
-/**
- * Simple data holder for a byte array, allowing for multidimensional arrays in AIDL.
- * @hide
- */
-parcelable ByteArray {
-    byte[] data;
-}
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/attestationmanager/IAttestationManager.aidl b/keystore2/aidl/android/security/attestationmanager/IAttestationManager.aidl
deleted file mode 100644
index e77a21e..0000000
--- a/keystore2/aidl/android/security/attestationmanager/IAttestationManager.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.attestationmanager;
-
-import android.security.attestationmanager.ByteArray;
-import android.hardware.security.keymint.KeyParameter;
-
-/**
- * Internal interface for performing device attestation.
- * @hide
- */
-interface IAttestationManager {
-    /**
-     * Attest a provided list of device identifiers.
-     *
-     * @return The signed certificate chain, with each individual certificate encoded as a byte
-     *         array.
-     */
-    ByteArray[] attestDevice(
-            in KeyParameter[] deviceIdentifiers, boolean useIndividualAttestation,
-            in byte[] attestationChallenge, int securityLevel);
-}
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/metrics/HardwareAuthenticatorType.aidl b/keystore2/aidl/android/security/metrics/HardwareAuthenticatorType.aidl
index b13f6ea..d5cacfd 100644
--- a/keystore2/aidl/android/security/metrics/HardwareAuthenticatorType.aidl
+++ b/keystore2/aidl/android/security/metrics/HardwareAuthenticatorType.aidl
@@ -17,16 +17,41 @@
 package android.security.metrics;
 
 /**
- * HardwareAuthenticatorType enum as defined in Keystore2KeyCreationWithAuthInfo of
- * frameworks/proto_logging/stats/atoms.proto.
+ * AIDL enum representing the
+ * android.os.statsd.Keystore2KeyCreationWithAuthInfo.HardwareAuthenticatorType protocol buffer enum
+ * defined in frameworks/proto_logging/stats/atoms.proto.
+ *
+ * This enum is a mirror of
+ * hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/HardwareAuthenticatorType.aidl
+ * except that:
+ *   - The enum tag number for the ANY value is set to 5,
+ *   - The enum tag numbers of all other values are incremented by 1, and
+ *   - Two new values are added: AUTH_TYPE_UNSPECIFIED and NO_AUTH_TYPE.
+ * The KeyMint AIDL enum is a bitmask, but since the enum tag numbers in this metrics-specific
+ * mirror were shifted, this enum can't behave as a bitmask. As a result, we have to explicitly add
+ * values to represent the bitwise OR of pairs of values that we expect to see in the wild.
  * @hide
  */
 @Backing(type="int")
 enum HardwareAuthenticatorType {
-    /** Unspecified takes 0. Other values are incremented by 1 compared to keymint spec. */
+    // Sentinel value to represent undefined enum tag numbers (which would represent combinations of
+    // values from the KeyMint enum that aren't explicitly represented here). We don't expect to see
+    // this value in the metrics, but if we do it means that an unexpected (bitwise OR) combination
+    // of KeyMint HardwareAuthenticatorType values is being used as the HardwareAuthenticatorType
+    // key parameter.
     AUTH_TYPE_UNSPECIFIED = 0,
+    // Corresponds to KeyMint's HardwareAuthenticatorType::NONE value (enum tag number 0).
     NONE = 1,
+    // Corresponds to KeyMint's HardwareAuthenticatorType::PASSWORD value (enum tag number 1 << 0).
     PASSWORD = 2,
+    // Corresponds to KeyMint's HardwareAuthenticatorType::FINGERPRINT value (enum tag number
+    // 1 << 1).
     FINGERPRINT = 3,
+    // Corresponds to the (bitwise OR) combination of KeyMint's HardwareAuthenticatorType::PASSWORD
+    // and HardwareAuthenticatorType::FINGERPRINT values.
+    PASSWORD_OR_FINGERPRINT = 4,
+    // Corresponds to KeyMint's HardwareAuthenticatorType::ANY value (enum tag number 0xFFFFFFFF).
     ANY = 5,
+    // No HardwareAuthenticatorType was specified in the key parameters.
+    NO_AUTH_TYPE = 6,
 }
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl b/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl
new file mode 100644
index 0000000..8d9daad
--- /dev/null
+++ b/keystore2/aidl/android/security/postprocessor/CertificateChain.aidl
@@ -0,0 +1,34 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android.security.postprocessor;
+
+/**
+ * General parcelable for holding the encoded certificates to be used in Keystore. This parcelable
+ * is returned by `IKeystoreCertificatePostProcessor::processKeystoreCertificates`.
+ * @hide
+ */
+@RustDerive(Clone=true)
+parcelable CertificateChain {
+    /**
+     * Holds the DER-encoded representation of the leaf certificate.
+     */
+    byte[] leafCertificate;
+    /**
+     * Holds a byte array containing the concatenation of all the remaining elements of the
+     * certificate chain with root certificate as the last with each certificate represented in
+     * DER-encoded format.
+     */
+    byte[] remainingChain;
+}
diff --git a/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl b/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl
new file mode 100644
index 0000000..0ceaacb
--- /dev/null
+++ b/keystore2/aidl/android/security/postprocessor/IKeystoreCertificatePostProcessor.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.postprocessor;
+
+import android.security.postprocessor.CertificateChain;
+
+interface IKeystoreCertificatePostProcessor {
+    /**
+     * Allows implementing services to process the keystore certificates after the certificate
+     * chain has been generated.
+     *
+     * certificateChain holds the chain associated with a newly generated Keystore asymmetric
+     * keypair, where the leafCertificate is the certificate for the public key of generated key.
+     * The remaining attestation certificates are stored as a concatenated byte array of the
+     * encoded certificates with root certificate as the last element.
+     *
+     * Successful calls would get the processed certificate chain which then replaces the original
+     * certificate chain. In case of any failures/exceptions, keystore would fallback to the
+     * original certificate chain.
+     *
+     * @hide
+     */
+    CertificateChain processKeystoreCertificates(in CertificateChain certificateChain);
+}
diff --git a/keystore2/android.system.keystore2-service.xml b/keystore2/android.system.keystore2-service.xml
index 4d8a756..35b9cc8 100644
--- a/keystore2/android.system.keystore2-service.xml
+++ b/keystore2/android.system.keystore2-service.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="framework">
     <hal format="aidl">
         <name>android.system.keystore2</name>
-        <version>4</version>
+        <version>5</version>
         <interface>
             <name>IKeystoreService</name>
             <instance>default</instance>
diff --git a/keystore2/apc_compat/apc_compat.cpp b/keystore2/apc_compat/apc_compat.cpp
index ffe7595..0ff2fc5 100644
--- a/keystore2/apc_compat/apc_compat.cpp
+++ b/keystore2/apc_compat/apc_compat.cpp
@@ -26,6 +26,7 @@
 #include <android/binder_manager.h>
 
 #include <memory>
+#include <set>
 #include <string>
 #include <thread>
 #include <vector>
@@ -214,6 +215,14 @@
         return nullptr;
     }
 
+    class DeathRecipientCookie {
+      public:
+        DeathRecipientCookie(std::weak_ptr<ConfuiAidlCompatSession> session)
+            : mAidlSession(session) {}
+        DeathRecipientCookie() = delete;
+        std::weak_ptr<ConfuiAidlCompatSession> mAidlSession;
+    };
+
     uint32_t promptUserConfirmation(ApcCompatCallback callback, const char* prompt_text,
                                     const uint8_t* extra_data, size_t extra_data_size,
                                     const char* locale, ApcCompatUiOptions ui_options) {
@@ -234,12 +243,19 @@
         if (!aidlService_) {
             return APC_COMPAT_ERROR_SYSTEM_ERROR;
         }
-        auto linkRet =
-            AIBinder_linkToDeath(aidlService_->asBinder().get(), death_recipient_.get(), this);
-        if (linkRet != STATUS_OK) {
-            LOG(ERROR) << "Communication error: promptUserConfirmation: "
-                          "Trying to register death recipient: ";
-            return APC_COMPAT_ERROR_SYSTEM_ERROR;
+
+        {
+            auto cookieLock = std::lock_guard(deathRecipientCookie_lock_);
+            void* cookie = new DeathRecipientCookie(this->ref<ConfuiAidlCompatSession>());
+            auto linkRet = AIBinder_linkToDeath(aidlService_->asBinder().get(),
+                                                death_recipient_.get(), cookie);
+            if (linkRet != STATUS_OK) {
+                LOG(ERROR) << "Communication error: promptUserConfirmation: "
+                              "Trying to register death recipient: ";
+                delete static_cast<DeathRecipientCookie*>(cookie);
+                return APC_COMPAT_ERROR_SYSTEM_ERROR;
+            }
+            deathRecipientCookie_.insert(cookie);
         }
 
         auto rc = aidlService_->promptUserConfirmation(ref<ConfuiAidlCompatSession>(), aidl_prompt,
@@ -275,8 +291,21 @@
 
         if (callback.result != nullptr) {
             if (aidlService_) {
-                AIBinder_unlinkToDeath(aidlService_->asBinder().get(), death_recipient_.get(),
-                                       this);
+                // unlink all of the registered death recipients in case there
+                // were multiple calls to promptUserConfirmation before a call
+                // to finalize
+                std::set<void*> cookiesToUnlink;
+                {
+                    auto cookieLock = std::lock_guard(deathRecipientCookie_lock_);
+                    cookiesToUnlink = deathRecipientCookie_;
+                    deathRecipientCookie_.clear();
+                }
+
+                // Unlink these outside of the lock
+                for (void* cookie : cookiesToUnlink) {
+                    AIBinder_unlinkToDeath(aidlService_->asBinder().get(), death_recipient_.get(),
+                                           cookie);
+                }
             }
             CompatSessionCB::finalize(responseCode2Compat(responseCode), callback, dataConfirmed,
                                       confirmationToken);
@@ -293,17 +322,46 @@
     void serviceDied() {
         aidlService_.reset();
         aidlService_ = nullptr;
+        {
+            std::lock_guard lock(deathRecipientCookie_lock_);
+            deathRecipientCookie_.clear();
+        }
         finalize(AidlConfirmationUI::SYSTEM_ERROR, {}, {});
     }
 
-    static void binderDiedCallbackAidl(void* ptr) {
-        LOG(ERROR) << __func__ << " : ConfuiAidlCompatSession Service died.";
-        auto aidlSession = static_cast<ConfuiAidlCompatSession*>(ptr);
-        if (aidlSession == nullptr) {
-            LOG(ERROR) << __func__ << ": Null ConfuiAidlCompatSession HAL died.";
-            return;
+    void serviceUnlinked(void* cookie) {
+        {
+            std::lock_guard lock(deathRecipientCookie_lock_);
+            deathRecipientCookie_.erase(cookie);
         }
-        aidlSession->serviceDied();
+    }
+
+    static void binderDiedCallbackAidl(void* ptr) {
+        auto aidlSessionCookie = static_cast<ConfuiAidlCompatSession::DeathRecipientCookie*>(ptr);
+        if (aidlSessionCookie == nullptr) {
+            LOG(ERROR) << __func__ << ": Null cookie for binderDiedCallbackAidl when HAL died!";
+            return;
+        } else if (auto aidlSession = aidlSessionCookie->mAidlSession.lock();
+                   aidlSession != nullptr) {
+            LOG(WARNING) << __func__ << " : Notififying ConfuiAidlCompatSession Service died.";
+            aidlSession->serviceDied();
+        } else {
+            LOG(ERROR) << __func__
+                       << " : ConfuiAidlCompatSession Service died but object is no longer around "
+                          "to be able to notify.";
+        }
+    }
+
+    static void binderUnlinkedCallbackAidl(void* ptr) {
+        auto aidlSessionCookie = static_cast<ConfuiAidlCompatSession::DeathRecipientCookie*>(ptr);
+        if (aidlSessionCookie == nullptr) {
+            LOG(ERROR) << __func__ << ": Null cookie!";
+            return;
+        } else if (auto aidlSession = aidlSessionCookie->mAidlSession.lock();
+                   aidlSession != nullptr) {
+            aidlSession->serviceUnlinked(ptr);
+        }
+        delete aidlSessionCookie;
     }
 
     int getReturnCode(const ::ndk::ScopedAStatus& result) {
@@ -343,6 +401,7 @@
         : aidlService_(service), callback_{nullptr, nullptr} {
         death_recipient_ = ::ndk::ScopedAIBinder_DeathRecipient(
             AIBinder_DeathRecipient_new(binderDiedCallbackAidl));
+        AIBinder_DeathRecipient_setOnUnlinked(death_recipient_.get(), binderUnlinkedCallbackAidl);
     }
 
     virtual ~ConfuiAidlCompatSession() = default;
@@ -351,6 +410,8 @@
 
   private:
     std::shared_ptr<AidlConfirmationUI> aidlService_;
+    std::mutex deathRecipientCookie_lock_;
+    std::set<void*> deathRecipientCookie_;
 
     // The callback_lock_ protects the callback_ field against concurrent modification.
     // IMPORTANT: It must never be held while calling the call back.
diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs
index 8e6040b..b173da8 100644
--- a/keystore2/legacykeystore/lib.rs
+++ b/keystore2/legacykeystore/lib.rs
@@ -134,6 +134,7 @@
     }
 
     fn get(&mut self, caller_uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
+        ensure_keystore_get_is_enabled()?;
         self.with_transaction(TransactionBehavior::Deferred, |tx| {
             tx.query_row(
                 "SELECT profile FROM profiles WHERE owner = ? AND alias = ?;",
@@ -239,6 +240,17 @@
     }
 }
 
+fn ensure_keystore_get_is_enabled() -> Result<()> {
+    if keystore2_flags::disable_legacy_keystore_get() {
+        Err(Error::deprecated()).context(concat!(
+            "Retrieving from Keystore's legacy database is ",
+            "no longer supported, store in an app-specific database instead"
+        ))
+    } else {
+        Ok(())
+    }
+}
+
 struct LegacyKeystoreDeleteListener {
     legacy_keystore: Arc<LegacyKeystore>,
 }
@@ -313,6 +325,7 @@
     }
 
     fn get(&self, alias: &str, uid: i32) -> Result<Vec<u8>> {
+        ensure_keystore_get_is_enabled()?;
         let mut db = self.open_db().context("In get.")?;
         let uid = Self::get_effective_uid(uid).context("In get.")?;
 
diff --git a/keystore2/postprocessor_client/Android.bp b/keystore2/postprocessor_client/Android.bp
new file mode 100644
index 0000000..7f0194a
--- /dev/null
+++ b/keystore2/postprocessor_client/Android.bp
@@ -0,0 +1,47 @@
+//
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_security_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_security_license"],
+}
+
+rust_defaults {
+    name: "libpostprocessor_client_defaults",
+    crate_name: "postprocessor_client",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "android.security.postprocessor-rust",
+        "libanyhow",
+        "libbinder_rs",
+        "liblog_rust",
+        "libmessage_macro",
+        "libthiserror",
+    ],
+    defaults: [
+        "keymint_use_latest_hal_aidl_rust",
+    ],
+}
+
+rust_library {
+    name: "libpostprocessor_client",
+    defaults: [
+        "libpostprocessor_client_defaults",
+    ],
+}
diff --git a/keystore2/postprocessor_client/src/lib.rs b/keystore2/postprocessor_client/src/lib.rs
new file mode 100644
index 0000000..beeb5f5
--- /dev/null
+++ b/keystore2/postprocessor_client/src/lib.rs
@@ -0,0 +1,109 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Helper wrapper around PostProcessor interface.
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::Certificate::Certificate;
+use android_security_postprocessor::aidl::android::security::postprocessor::{
+    CertificateChain::CertificateChain,
+    IKeystoreCertificatePostProcessor::IKeystoreCertificatePostProcessor,
+};
+use anyhow::{Context, Result};
+use binder::{StatusCode, Strong};
+use log::{error, info, warn};
+use message_macro::source_location_msg;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc;
+use std::thread;
+use std::time::Duration;
+
+/// Errors occurred during the interaction with Certificate Processor
+#[derive(Debug, Clone, Copy, thiserror::Error, PartialEq, Eq)]
+#[error("Binder transaction error {0:?}")]
+pub struct Error(pub StatusCode);
+
+static CERT_PROCESSOR_FAILURE: AtomicBool = AtomicBool::new(false);
+
+fn send_certificate_chain_to_processor(
+    attestation_chain: CertificateChain,
+) -> Result<CertificateChain> {
+    let cert_processing_server: Strong<dyn IKeystoreCertificatePostProcessor> = wait_for_interface(
+        "rkp_cert_processor.service".to_string(),
+    )
+    .context(source_location_msg!("While trying to connect to the post processor service."))?;
+    cert_processing_server
+        .processKeystoreCertificates(&attestation_chain)
+        .context(source_location_msg!("While trying to post process certificates."))
+}
+
+/// Processes the keystore certificates after the certificate chain has been generated by Keystore.
+/// More details about this function provided in IKeystoreCertificatePostProcessor.aidl
+pub fn process_certificate_chain(
+    mut certificates: Vec<Certificate>,
+    attestation_certs: Vec<u8>,
+) -> Vec<Certificate> {
+    // If no certificates are provided from keymint, return the original chain.
+    if certificates.is_empty() {
+        error!("No leaf certificate provided.");
+        return vec![Certificate { encodedCertificate: attestation_certs }];
+    }
+
+    if certificates.len() > 1 {
+        warn!("dropping {} unexpected extra certificates after the leaf", certificates.len() - 1);
+    }
+
+    let attestation_chain = CertificateChain {
+        leafCertificate: certificates[0].encodedCertificate.clone(),
+        remainingChain: attestation_certs.clone(),
+    };
+    let result = send_certificate_chain_to_processor(attestation_chain);
+    match result {
+        Ok(certificate_chain) => {
+            info!("Post processing successful. Replacing certificates.");
+            vec![
+                Certificate { encodedCertificate: certificate_chain.leafCertificate },
+                Certificate { encodedCertificate: certificate_chain.remainingChain },
+            ]
+        }
+        Err(err) => {
+            warn!("Failed to replace certificates ({err:#?}), falling back to original chain.");
+            certificates.push(Certificate { encodedCertificate: attestation_certs });
+            certificates
+        }
+    }
+}
+
+fn wait_for_interface(
+    service_name: String,
+) -> Result<Strong<dyn IKeystoreCertificatePostProcessor>> {
+    if CERT_PROCESSOR_FAILURE.load(Ordering::Relaxed) {
+        return Err(Error(StatusCode::INVALID_OPERATION).into());
+    }
+    let (sender, receiver) = mpsc::channel();
+    let _t = thread::spawn(move || {
+        if let Err(e) = sender.send(binder::wait_for_interface(&service_name)) {
+            error!("failed to send result of wait_for_interface({service_name}), likely due to timeout: {e:?}");
+        }
+    });
+
+    match receiver.recv_timeout(Duration::from_secs(5)) {
+        Ok(service_binder) => Ok(service_binder?),
+        Err(e) => {
+            error!("Timed out while connecting to post processor service: {e:#?}");
+            // Cert processor has failed. Retry only after reboot.
+            CERT_PROCESSOR_FAILURE.store(true, Ordering::Relaxed);
+            Err(e.into())
+        }
+    }
+}
diff --git a/keystore2/rkpd_client/src/lib.rs b/keystore2/rkpd_client/src/lib.rs
index d8a5276..936fe3d 100644
--- a/keystore2/rkpd_client/src/lib.rs
+++ b/keystore2/rkpd_client/src/lib.rs
@@ -310,331 +310,4 @@
 }
 
 #[cfg(test)]
-mod tests {
-    use super::*;
-    use android_security_rkp_aidl::aidl::android::security::rkp::IRegistration::BnRegistration;
-    use std::sync::atomic::{AtomicU32, Ordering};
-    use std::sync::{Arc, Mutex};
-
-    const DEFAULT_RPC_SERVICE_NAME: &str =
-        "android.hardware.security.keymint.IRemotelyProvisionedComponent/default";
-
-    struct MockRegistrationValues {
-        key: RemotelyProvisionedKey,
-        latency: Option<Duration>,
-        thread_join_handles: Vec<Option<std::thread::JoinHandle<()>>>,
-    }
-
-    struct MockRegistration(Arc<Mutex<MockRegistrationValues>>);
-
-    impl MockRegistration {
-        pub fn new_native_binder(
-            key: &RemotelyProvisionedKey,
-            latency: Option<Duration>,
-        ) -> Strong<dyn IRegistration> {
-            let result = Self(Arc::new(Mutex::new(MockRegistrationValues {
-                key: RemotelyProvisionedKey {
-                    keyBlob: key.keyBlob.clone(),
-                    encodedCertChain: key.encodedCertChain.clone(),
-                },
-                latency,
-                thread_join_handles: Vec::new(),
-            })));
-            BnRegistration::new_binder(result, BinderFeatures::default())
-        }
-    }
-
-    impl Drop for MockRegistration {
-        fn drop(&mut self) {
-            let mut values = self.0.lock().unwrap();
-            for handle in values.thread_join_handles.iter_mut() {
-                // These are test threads. So, no need to worry too much about error handling.
-                handle.take().unwrap().join().unwrap();
-            }
-        }
-    }
-
-    impl Interface for MockRegistration {}
-
-    impl IRegistration for MockRegistration {
-        fn getKey(&self, _: i32, cb: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> {
-            let mut values = self.0.lock().unwrap();
-            let key = RemotelyProvisionedKey {
-                keyBlob: values.key.keyBlob.clone(),
-                encodedCertChain: values.key.encodedCertChain.clone(),
-            };
-            let latency = values.latency;
-            let get_key_cb = cb.clone();
-
-            // Need a separate thread to trigger timeout in the caller.
-            let join_handle = std::thread::spawn(move || {
-                if let Some(duration) = latency {
-                    std::thread::sleep(duration);
-                }
-                get_key_cb.onSuccess(&key).unwrap();
-            });
-            values.thread_join_handles.push(Some(join_handle));
-            Ok(())
-        }
-
-        fn cancelGetKey(&self, _: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> {
-            Ok(())
-        }
-
-        fn storeUpgradedKeyAsync(
-            &self,
-            _: &[u8],
-            _: &[u8],
-            cb: &Strong<dyn IStoreUpgradedKeyCallback>,
-        ) -> binder::Result<()> {
-            // We are primarily concerned with timing out correctly. Storing the key in this mock
-            // registration isn't particularly interesting, so skip that part.
-            let values = self.0.lock().unwrap();
-            let store_cb = cb.clone();
-            let latency = values.latency;
-
-            std::thread::spawn(move || {
-                if let Some(duration) = latency {
-                    std::thread::sleep(duration);
-                }
-                store_cb.onSuccess().unwrap();
-            });
-            Ok(())
-        }
-    }
-
-    fn get_mock_registration(
-        key: &RemotelyProvisionedKey,
-        latency: Option<Duration>,
-    ) -> Result<binder::Strong<dyn IRegistration>> {
-        let (tx, rx) = oneshot::channel();
-        let cb = GetRegistrationCallback::new_native_binder(tx);
-        let mock_registration = MockRegistration::new_native_binder(key, latency);
-
-        assert!(cb.onSuccess(&mock_registration).is_ok());
-        tokio_rt().block_on(rx).unwrap()
-    }
-
-    // Using the same key ID makes test cases race with each other. So, we use separate key IDs for
-    // different test cases.
-    fn get_next_key_id() -> u32 {
-        static ID: AtomicU32 = AtomicU32::new(0);
-        ID.fetch_add(1, Ordering::Relaxed)
-    }
-
-    #[test]
-    fn test_get_registration_cb_success() {
-        let key: RemotelyProvisionedKey = Default::default();
-        let registration = get_mock_registration(&key, /*latency=*/ None);
-        assert!(registration.is_ok());
-    }
-
-    #[test]
-    fn test_get_registration_cb_cancel() {
-        let (tx, rx) = oneshot::channel();
-        let cb = GetRegistrationCallback::new_native_binder(tx);
-        assert!(cb.onCancel().is_ok());
-
-        let result = tokio_rt().block_on(rx).unwrap();
-        assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled);
-    }
-
-    #[test]
-    fn test_get_registration_cb_error() {
-        let (tx, rx) = oneshot::channel();
-        let cb = GetRegistrationCallback::new_native_binder(tx);
-        assert!(cb.onError("error").is_ok());
-
-        let result = tokio_rt().block_on(rx).unwrap();
-        assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::GetRegistrationFailed);
-    }
-
-    #[test]
-    fn test_get_key_cb_success() {
-        let mock_key =
-            RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
-        let (tx, rx) = oneshot::channel();
-        let cb = GetKeyCallback::new_native_binder(tx);
-        assert!(cb.onSuccess(&mock_key).is_ok());
-
-        let key = tokio_rt().block_on(rx).unwrap().unwrap();
-        assert_eq!(key, mock_key);
-    }
-
-    #[test]
-    fn test_get_key_cb_cancel() {
-        let (tx, rx) = oneshot::channel();
-        let cb = GetKeyCallback::new_native_binder(tx);
-        assert!(cb.onCancel().is_ok());
-
-        let result = tokio_rt().block_on(rx).unwrap();
-        assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled);
-    }
-
-    #[test]
-    fn test_get_key_cb_error() {
-        for get_key_error in GetKeyErrorCode::enum_values() {
-            let (tx, rx) = oneshot::channel();
-            let cb = GetKeyCallback::new_native_binder(tx);
-            assert!(cb.onError(get_key_error, "error").is_ok());
-
-            let result = tokio_rt().block_on(rx).unwrap();
-            assert_eq!(
-                result.unwrap_err().downcast::<Error>().unwrap(),
-                Error::GetKeyFailed(get_key_error),
-            );
-        }
-    }
-
-    #[test]
-    fn test_store_upgraded_cb_success() {
-        let (tx, rx) = oneshot::channel();
-        let cb = StoreUpgradedKeyCallback::new_native_binder(tx);
-        assert!(cb.onSuccess().is_ok());
-
-        tokio_rt().block_on(rx).unwrap().unwrap();
-    }
-
-    #[test]
-    fn test_store_upgraded_key_cb_error() {
-        let (tx, rx) = oneshot::channel();
-        let cb = StoreUpgradedKeyCallback::new_native_binder(tx);
-        assert!(cb.onError("oh no! it failed").is_ok());
-
-        let result = tokio_rt().block_on(rx).unwrap();
-        assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::StoreUpgradedKeyFailed);
-    }
-
-    #[test]
-    fn test_get_mock_key_success() {
-        let mock_key =
-            RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
-        let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap();
-
-        let key = tokio_rt()
-            .block_on(get_rkpd_attestation_key_from_registration_async(&registration, 0))
-            .unwrap();
-        assert_eq!(key, mock_key);
-    }
-
-    #[test]
-    fn test_get_mock_key_timeout() {
-        let mock_key =
-            RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
-        let latency = RKPD_TIMEOUT + Duration::from_secs(1);
-        let registration = get_mock_registration(&mock_key, Some(latency)).unwrap();
-
-        let result =
-            tokio_rt().block_on(get_rkpd_attestation_key_from_registration_async(&registration, 0));
-        assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RetryableTimeout);
-    }
-
-    #[test]
-    fn test_store_mock_key_success() {
-        let mock_key =
-            RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
-        let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap();
-        tokio_rt()
-            .block_on(store_rkpd_attestation_key_with_registration_async(&registration, &[], &[]))
-            .unwrap();
-    }
-
-    #[test]
-    fn test_store_mock_key_timeout() {
-        let mock_key =
-            RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
-        let latency = RKPD_TIMEOUT + Duration::from_secs(1);
-        let registration = get_mock_registration(&mock_key, Some(latency)).unwrap();
-
-        let result = tokio_rt().block_on(store_rkpd_attestation_key_with_registration_async(
-            &registration,
-            &[],
-            &[],
-        ));
-        assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::Timeout);
-    }
-
-    #[test]
-    fn test_get_rkpd_attestation_key() {
-        binder::ProcessState::start_thread_pool();
-        let key_id = get_next_key_id();
-        let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
-        assert!(!key.keyBlob.is_empty());
-        assert!(!key.encodedCertChain.is_empty());
-    }
-
-    #[test]
-    fn test_get_rkpd_attestation_key_same_caller() {
-        binder::ProcessState::start_thread_pool();
-        let key_id = get_next_key_id();
-
-        // Multiple calls should return the same key.
-        let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
-        let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
-
-        assert_eq!(first_key.keyBlob, second_key.keyBlob);
-        assert_eq!(first_key.encodedCertChain, second_key.encodedCertChain);
-    }
-
-    #[test]
-    fn test_get_rkpd_attestation_key_different_caller() {
-        binder::ProcessState::start_thread_pool();
-        let first_key_id = get_next_key_id();
-        let second_key_id = get_next_key_id();
-
-        // Different callers should be getting different keys.
-        let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, first_key_id).unwrap();
-        let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, second_key_id).unwrap();
-
-        assert_ne!(first_key.keyBlob, second_key.keyBlob);
-        assert_ne!(first_key.encodedCertChain, second_key.encodedCertChain);
-    }
-
-    #[test]
-    // Couple of things to note:
-    // 1. This test must never run with UID of keystore. Otherwise, it can mess up keys stored by
-    //    keystore.
-    // 2. Storing and reading the stored key is prone to race condition. So, we only do this in one
-    //    test case.
-    fn test_store_rkpd_attestation_key() {
-        binder::ProcessState::start_thread_pool();
-        let key_id = get_next_key_id();
-        let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
-        let new_blob: [u8; 8] = rand::random();
-
-        assert!(
-            store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &key.keyBlob, &new_blob).is_ok()
-        );
-
-        let new_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
-
-        // Restore original key so that we don't leave RKPD with invalid blobs.
-        assert!(
-            store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &new_blob, &key.keyBlob).is_ok()
-        );
-        assert_eq!(new_key.keyBlob, new_blob);
-    }
-
-    #[test]
-    fn test_stress_get_rkpd_attestation_key() {
-        binder::ProcessState::start_thread_pool();
-        let key_id = get_next_key_id();
-        let mut threads = vec![];
-        const NTHREADS: u32 = 10;
-        const NCALLS: u32 = 1000;
-
-        for _ in 0..NTHREADS {
-            threads.push(std::thread::spawn(move || {
-                for _ in 0..NCALLS {
-                    let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
-                    assert!(!key.keyBlob.is_empty());
-                    assert!(!key.encodedCertChain.is_empty());
-                }
-            }));
-        }
-
-        for t in threads {
-            assert!(t.join().is_ok());
-        }
-    }
-}
+mod tests;
diff --git a/keystore2/rkpd_client/src/tests.rs b/keystore2/rkpd_client/src/tests.rs
new file mode 100644
index 0000000..fd0468f
--- /dev/null
+++ b/keystore2/rkpd_client/src/tests.rs
@@ -0,0 +1,338 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! RKPD tests.
+
+use super::*;
+use android_security_rkp_aidl::aidl::android::security::rkp::IRegistration::BnRegistration;
+use std::sync::atomic::{AtomicU32, Ordering};
+use std::sync::{Arc, Mutex};
+
+const DEFAULT_RPC_SERVICE_NAME: &str =
+    "android.hardware.security.keymint.IRemotelyProvisionedComponent/default";
+
+struct MockRegistrationValues {
+    key: RemotelyProvisionedKey,
+    latency: Option<Duration>,
+    thread_join_handles: Vec<Option<std::thread::JoinHandle<()>>>,
+}
+
+struct MockRegistration(Arc<Mutex<MockRegistrationValues>>);
+
+impl MockRegistration {
+    pub fn new_native_binder(
+        key: &RemotelyProvisionedKey,
+        latency: Option<Duration>,
+    ) -> Strong<dyn IRegistration> {
+        let result = Self(Arc::new(Mutex::new(MockRegistrationValues {
+            key: RemotelyProvisionedKey {
+                keyBlob: key.keyBlob.clone(),
+                encodedCertChain: key.encodedCertChain.clone(),
+            },
+            latency,
+            thread_join_handles: Vec::new(),
+        })));
+        BnRegistration::new_binder(result, BinderFeatures::default())
+    }
+}
+
+impl Drop for MockRegistration {
+    fn drop(&mut self) {
+        let mut values = self.0.lock().unwrap();
+        for handle in values.thread_join_handles.iter_mut() {
+            // These are test threads. So, no need to worry too much about error handling.
+            handle.take().unwrap().join().unwrap();
+        }
+    }
+}
+
+impl Interface for MockRegistration {}
+
+impl IRegistration for MockRegistration {
+    fn getKey(&self, _: i32, cb: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> {
+        let mut values = self.0.lock().unwrap();
+        let key = RemotelyProvisionedKey {
+            keyBlob: values.key.keyBlob.clone(),
+            encodedCertChain: values.key.encodedCertChain.clone(),
+        };
+        let latency = values.latency;
+        let get_key_cb = cb.clone();
+
+        // Need a separate thread to trigger timeout in the caller.
+        let join_handle = std::thread::spawn(move || {
+            if let Some(duration) = latency {
+                std::thread::sleep(duration);
+            }
+            get_key_cb.onSuccess(&key).unwrap();
+        });
+        values.thread_join_handles.push(Some(join_handle));
+        Ok(())
+    }
+
+    fn cancelGetKey(&self, _: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> {
+        Ok(())
+    }
+
+    fn storeUpgradedKeyAsync(
+        &self,
+        _: &[u8],
+        _: &[u8],
+        cb: &Strong<dyn IStoreUpgradedKeyCallback>,
+    ) -> binder::Result<()> {
+        // We are primarily concerned with timing out correctly. Storing the key in this mock
+        // registration isn't particularly interesting, so skip that part.
+        let values = self.0.lock().unwrap();
+        let store_cb = cb.clone();
+        let latency = values.latency;
+
+        std::thread::spawn(move || {
+            if let Some(duration) = latency {
+                std::thread::sleep(duration);
+            }
+            store_cb.onSuccess().unwrap();
+        });
+        Ok(())
+    }
+}
+
+fn get_mock_registration(
+    key: &RemotelyProvisionedKey,
+    latency: Option<Duration>,
+) -> Result<binder::Strong<dyn IRegistration>> {
+    let (tx, rx) = oneshot::channel();
+    let cb = GetRegistrationCallback::new_native_binder(tx);
+    let mock_registration = MockRegistration::new_native_binder(key, latency);
+
+    assert!(cb.onSuccess(&mock_registration).is_ok());
+    tokio_rt().block_on(rx).unwrap()
+}
+
+// Using the same key ID makes test cases race with each other. So, we use separate key IDs for
+// different test cases.
+fn get_next_key_id() -> u32 {
+    static ID: AtomicU32 = AtomicU32::new(0);
+    ID.fetch_add(1, Ordering::Relaxed)
+}
+
+#[test]
+fn test_get_registration_cb_success() {
+    let key: RemotelyProvisionedKey = Default::default();
+    let registration = get_mock_registration(&key, /*latency=*/ None);
+    assert!(registration.is_ok());
+}
+
+#[test]
+fn test_get_registration_cb_cancel() {
+    let (tx, rx) = oneshot::channel();
+    let cb = GetRegistrationCallback::new_native_binder(tx);
+    assert!(cb.onCancel().is_ok());
+
+    let result = tokio_rt().block_on(rx).unwrap();
+    assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled);
+}
+
+#[test]
+fn test_get_registration_cb_error() {
+    let (tx, rx) = oneshot::channel();
+    let cb = GetRegistrationCallback::new_native_binder(tx);
+    assert!(cb.onError("error").is_ok());
+
+    let result = tokio_rt().block_on(rx).unwrap();
+    assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::GetRegistrationFailed);
+}
+
+#[test]
+fn test_get_key_cb_success() {
+    let mock_key =
+        RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+    let (tx, rx) = oneshot::channel();
+    let cb = GetKeyCallback::new_native_binder(tx);
+    assert!(cb.onSuccess(&mock_key).is_ok());
+
+    let key = tokio_rt().block_on(rx).unwrap().unwrap();
+    assert_eq!(key, mock_key);
+}
+
+#[test]
+fn test_get_key_cb_cancel() {
+    let (tx, rx) = oneshot::channel();
+    let cb = GetKeyCallback::new_native_binder(tx);
+    assert!(cb.onCancel().is_ok());
+
+    let result = tokio_rt().block_on(rx).unwrap();
+    assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled);
+}
+
+#[test]
+fn test_get_key_cb_error() {
+    for get_key_error in GetKeyErrorCode::enum_values() {
+        let (tx, rx) = oneshot::channel();
+        let cb = GetKeyCallback::new_native_binder(tx);
+        assert!(cb.onError(get_key_error, "error").is_ok());
+
+        let result = tokio_rt().block_on(rx).unwrap();
+        assert_eq!(
+            result.unwrap_err().downcast::<Error>().unwrap(),
+            Error::GetKeyFailed(get_key_error),
+        );
+    }
+}
+
+#[test]
+fn test_store_upgraded_cb_success() {
+    let (tx, rx) = oneshot::channel();
+    let cb = StoreUpgradedKeyCallback::new_native_binder(tx);
+    assert!(cb.onSuccess().is_ok());
+
+    tokio_rt().block_on(rx).unwrap().unwrap();
+}
+
+#[test]
+fn test_store_upgraded_key_cb_error() {
+    let (tx, rx) = oneshot::channel();
+    let cb = StoreUpgradedKeyCallback::new_native_binder(tx);
+    assert!(cb.onError("oh no! it failed").is_ok());
+
+    let result = tokio_rt().block_on(rx).unwrap();
+    assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::StoreUpgradedKeyFailed);
+}
+
+#[test]
+fn test_get_mock_key_success() {
+    let mock_key =
+        RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+    let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap();
+
+    let key = tokio_rt()
+        .block_on(get_rkpd_attestation_key_from_registration_async(&registration, 0))
+        .unwrap();
+    assert_eq!(key, mock_key);
+}
+
+#[test]
+fn test_get_mock_key_timeout() {
+    let mock_key =
+        RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+    let latency = RKPD_TIMEOUT + Duration::from_secs(1);
+    let registration = get_mock_registration(&mock_key, Some(latency)).unwrap();
+
+    let result =
+        tokio_rt().block_on(get_rkpd_attestation_key_from_registration_async(&registration, 0));
+    assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RetryableTimeout);
+}
+
+#[test]
+fn test_store_mock_key_success() {
+    let mock_key =
+        RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+    let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap();
+    tokio_rt()
+        .block_on(store_rkpd_attestation_key_with_registration_async(&registration, &[], &[]))
+        .unwrap();
+}
+
+#[test]
+fn test_store_mock_key_timeout() {
+    let mock_key =
+        RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] };
+    let latency = RKPD_TIMEOUT + Duration::from_secs(1);
+    let registration = get_mock_registration(&mock_key, Some(latency)).unwrap();
+
+    let result = tokio_rt().block_on(store_rkpd_attestation_key_with_registration_async(
+        &registration,
+        &[],
+        &[],
+    ));
+    assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::Timeout);
+}
+
+#[test]
+fn test_get_rkpd_attestation_key() {
+    binder::ProcessState::start_thread_pool();
+    let key_id = get_next_key_id();
+    let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+    assert!(!key.keyBlob.is_empty());
+    assert!(!key.encodedCertChain.is_empty());
+}
+
+#[test]
+fn test_get_rkpd_attestation_key_same_caller() {
+    binder::ProcessState::start_thread_pool();
+    let key_id = get_next_key_id();
+
+    // Multiple calls should return the same key.
+    let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+    let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+
+    assert_eq!(first_key.keyBlob, second_key.keyBlob);
+    assert_eq!(first_key.encodedCertChain, second_key.encodedCertChain);
+}
+
+#[test]
+fn test_get_rkpd_attestation_key_different_caller() {
+    binder::ProcessState::start_thread_pool();
+    let first_key_id = get_next_key_id();
+    let second_key_id = get_next_key_id();
+
+    // Different callers should be getting different keys.
+    let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, first_key_id).unwrap();
+    let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, second_key_id).unwrap();
+
+    assert_ne!(first_key.keyBlob, second_key.keyBlob);
+    assert_ne!(first_key.encodedCertChain, second_key.encodedCertChain);
+}
+
+#[test]
+// Couple of things to note:
+// 1. This test must never run with UID of keystore. Otherwise, it can mess up keys stored by
+//    keystore.
+// 2. Storing and reading the stored key is prone to race condition. So, we only do this in one
+//    test case.
+fn test_store_rkpd_attestation_key() {
+    binder::ProcessState::start_thread_pool();
+    let key_id = get_next_key_id();
+    let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+    let new_blob: [u8; 8] = rand::random();
+
+    assert!(store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &key.keyBlob, &new_blob).is_ok());
+
+    let new_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+
+    // Restore original key so that we don't leave RKPD with invalid blobs.
+    assert!(store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &new_blob, &key.keyBlob).is_ok());
+    assert_eq!(new_key.keyBlob, new_blob);
+}
+
+#[test]
+fn test_stress_get_rkpd_attestation_key() {
+    binder::ProcessState::start_thread_pool();
+    let key_id = get_next_key_id();
+    let mut threads = vec![];
+    const NTHREADS: u32 = 10;
+    const NCALLS: u32 = 1000;
+
+    for _ in 0..NTHREADS {
+        threads.push(std::thread::spawn(move || {
+            for _ in 0..NCALLS {
+                let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap();
+                assert!(!key.keyBlob.is_empty());
+                assert!(!key.encodedCertChain.is_empty());
+            }
+        }));
+    }
+
+    for t in threads {
+        assert!(t.join().is_ok());
+    }
+}
diff --git a/keystore2/selinux/Android.bp b/keystore2/selinux/Android.bp
index 254f95e..8e644e6 100644
--- a/keystore2/selinux/Android.bp
+++ b/keystore2/selinux/Android.bp
@@ -34,7 +34,6 @@
 
     rustlibs: [
         "libanyhow",
-        "liblazy_static",
         "liblog_rust",
         "libselinux_bindgen",
         "libthiserror",
@@ -57,7 +56,6 @@
     rustlibs: [
         "libandroid_logger",
         "libanyhow",
-        "liblazy_static",
         "liblog_rust",
         "libselinux_bindgen",
         "libthiserror",
@@ -77,7 +75,6 @@
         "libandroid_logger",
         "libanyhow",
         "libkeystore2_selinux",
-        "liblazy_static",
         "liblog_rust",
         "libnix",
         "libnum_cpus",
diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs
index 695e029..1f1e692 100644
--- a/keystore2/selinux/src/lib.rs
+++ b/keystore2/selinux/src/lib.rs
@@ -18,6 +18,7 @@
 //!  * getcon
 //!  * selinux_check_access
 //!  * selabel_lookup for the keystore2_key backend.
+//!
 //! And it provides an owning wrapper around context strings `Context`.
 
 // TODO(b/290018030): Remove this and add proper safety comments.
@@ -25,7 +26,6 @@
 
 use anyhow::Context as AnyhowContext;
 use anyhow::{anyhow, Result};
-use lazy_static::lazy_static;
 pub use selinux::pid_t;
 use selinux::SELABEL_CTX_ANDROID_KEYSTORE2_KEY;
 use selinux::SELINUX_CB_LOG;
@@ -41,15 +41,13 @@
 
 static SELINUX_LOG_INIT: sync::Once = sync::Once::new();
 
-lazy_static! {
-    /// `selinux_check_access` is only thread safe if avc_init was called with lock callbacks.
-    /// However, avc_init is deprecated and not exported by androids version of libselinux.
-    /// `selinux_set_callbacks` does not allow setting lock callbacks. So the only option
-    /// that remains right now is to put a big lock around calls into libselinux.
-    /// TODO b/188079221 It should suffice to protect `selinux_check_access` but until we are
-    /// certain of that, we leave the extra locks in place
-    static ref LIB_SELINUX_LOCK: sync::Mutex<()> = Default::default();
-}
+/// `selinux_check_access` is only thread safe if avc_init was called with lock callbacks.
+/// However, avc_init is deprecated and not exported by androids version of libselinux.
+/// `selinux_set_callbacks` does not allow setting lock callbacks. So the only option
+/// that remains right now is to put a big lock around calls into libselinux.
+/// TODO b/188079221 It should suffice to protect `selinux_check_access` but until we are
+/// certain of that, we leave the extra locks in place
+static LIB_SELINUX_LOCK: sync::Mutex<()> = sync::Mutex::new(());
 
 fn redirect_selinux_logs_to_logcat() {
     // `selinux_set_callback` assigns the static lifetime function pointer
@@ -249,34 +247,6 @@
     }
 }
 
-/// Safe wrapper around libselinux `getpidcon`. It initializes the `Context::Raw` variant of the
-/// returned `Context`.
-///
-/// ## Return
-///  * Ok(Context::Raw()) if successful.
-///  * Err(Error::sys()) if getpidcon succeeded but returned a NULL pointer.
-///  * Err(io::Error::last_os_error()) if getpidcon failed.
-pub fn getpidcon(pid: selinux::pid_t) -> Result<Context> {
-    init_logger_once();
-    let _lock = LIB_SELINUX_LOCK.lock().unwrap();
-
-    let mut con: *mut c_char = ptr::null_mut();
-    match unsafe { selinux::getpidcon(pid, &mut con) } {
-        0 => {
-            if !con.is_null() {
-                Ok(Context::Raw(con))
-            } else {
-                Err(anyhow!(Error::sys(format!(
-                    "getpidcon returned a NULL context for pid {}",
-                    pid
-                ))))
-            }
-        }
-        _ => Err(anyhow!(io::Error::last_os_error()))
-            .context(format!("getpidcon failed for pid {}", pid)),
-    }
-}
-
 /// Safe wrapper around selinux_check_access.
 ///
 /// ## Return
@@ -798,12 +768,4 @@
         check_keystore_perm!(reset);
         check_keystore_perm!(unlock);
     }
-
-    #[test]
-    fn test_getpidcon() {
-        // Check that `getpidcon` of our pid is equal to what `getcon` returns.
-        // And by using `unwrap` we make sure that both also have to return successfully
-        // fully to pass the test.
-        assert_eq!(getpidcon(std::process::id() as i32).unwrap(), getcon().unwrap());
-    }
 }
diff --git a/keystore2/src/async_task.rs b/keystore2/src/async_task.rs
index 6548445..16401a4 100644
--- a/keystore2/src/async_task.rs
+++ b/keystore2/src/async_task.rs
@@ -27,6 +27,9 @@
     thread,
 };
 
+#[cfg(test)]
+mod tests;
+
 #[derive(Debug, PartialEq, Eq)]
 enum State {
     Exiting,
@@ -256,279 +259,3 @@
         state.state = State::Running;
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::{AsyncTask, Shelf};
-    use std::sync::{
-        mpsc::{channel, sync_channel, RecvTimeoutError},
-        Arc,
-    };
-    use std::time::Duration;
-
-    #[test]
-    fn test_shelf() {
-        let mut shelf = Shelf::default();
-
-        let s = "A string".to_string();
-        assert_eq!(shelf.put(s), None);
-
-        let s2 = "Another string".to_string();
-        assert_eq!(shelf.put(s2), Some("A string".to_string()));
-
-        // Put something of a different type on the shelf.
-        #[derive(Debug, PartialEq, Eq)]
-        struct Elf {
-            pub name: String,
-        }
-        let e1 = Elf { name: "Glorfindel".to_string() };
-        assert_eq!(shelf.put(e1), None);
-
-        // The String value is still on the shelf.
-        let s3 = shelf.get_downcast_ref::<String>().unwrap();
-        assert_eq!(s3, "Another string");
-
-        // As is the Elf.
-        {
-            let e2 = shelf.get_downcast_mut::<Elf>().unwrap();
-            assert_eq!(e2.name, "Glorfindel");
-            e2.name = "Celeborn".to_string();
-        }
-
-        // Take the Elf off the shelf.
-        let e3 = shelf.remove_downcast_ref::<Elf>().unwrap();
-        assert_eq!(e3.name, "Celeborn");
-
-        assert_eq!(shelf.remove_downcast_ref::<Elf>(), None);
-
-        // No u64 value has been put on the shelf, so getting one gives the default value.
-        {
-            let i = shelf.get_mut::<u64>();
-            assert_eq!(*i, 0);
-            *i = 42;
-        }
-        let i2 = shelf.get_downcast_ref::<u64>().unwrap();
-        assert_eq!(*i2, 42);
-
-        // No i32 value has ever been seen near the shelf.
-        assert_eq!(shelf.get_downcast_ref::<i32>(), None);
-        assert_eq!(shelf.get_downcast_mut::<i32>(), None);
-        assert_eq!(shelf.remove_downcast_ref::<i32>(), None);
-    }
-
-    #[test]
-    fn test_async_task() {
-        let at = AsyncTask::default();
-
-        // First queue up a job that blocks until we release it, to avoid
-        // unpredictable synchronization.
-        let (start_sender, start_receiver) = channel();
-        at.queue_hi(move |shelf| {
-            start_receiver.recv().unwrap();
-            // Put a trace vector on the shelf
-            shelf.put(Vec::<String>::new());
-        });
-
-        // Queue up some high-priority and low-priority jobs.
-        for i in 0..3 {
-            let j = i;
-            at.queue_lo(move |shelf| {
-                let trace = shelf.get_mut::<Vec<String>>();
-                trace.push(format!("L{}", j));
-            });
-            let j = i;
-            at.queue_hi(move |shelf| {
-                let trace = shelf.get_mut::<Vec<String>>();
-                trace.push(format!("H{}", j));
-            });
-        }
-
-        // Finally queue up a low priority job that emits the trace.
-        let (trace_sender, trace_receiver) = channel();
-        at.queue_lo(move |shelf| {
-            let trace = shelf.get_downcast_ref::<Vec<String>>().unwrap();
-            trace_sender.send(trace.clone()).unwrap();
-        });
-
-        // Ready, set, go.
-        start_sender.send(()).unwrap();
-        let trace = trace_receiver.recv().unwrap();
-
-        assert_eq!(trace, vec!["H0", "H1", "H2", "L0", "L1", "L2"]);
-    }
-
-    #[test]
-    fn test_async_task_chain() {
-        let at = Arc::new(AsyncTask::default());
-        let (sender, receiver) = channel();
-        // Queue up a job that will queue up another job. This confirms
-        // that the job is not invoked with any internal AsyncTask locks held.
-        let at_clone = at.clone();
-        at.queue_hi(move |_shelf| {
-            at_clone.queue_lo(move |_shelf| {
-                sender.send(()).unwrap();
-            });
-        });
-        receiver.recv().unwrap();
-    }
-
-    #[test]
-    #[should_panic]
-    fn test_async_task_panic() {
-        let at = AsyncTask::default();
-        at.queue_hi(|_shelf| {
-            panic!("Panic from queued job");
-        });
-        // Queue another job afterwards to ensure that the async thread gets joined.
-        let (done_sender, done_receiver) = channel();
-        at.queue_hi(move |_shelf| {
-            done_sender.send(()).unwrap();
-        });
-        done_receiver.recv().unwrap();
-    }
-
-    #[test]
-    fn test_async_task_idle() {
-        let at = AsyncTask::new(Duration::from_secs(3));
-        // Need a SyncSender as it is Send+Sync.
-        let (idle_done_sender, idle_done_receiver) = sync_channel::<()>(3);
-        at.add_idle(move |_shelf| {
-            idle_done_sender.send(()).unwrap();
-        });
-
-        // Queue up some high-priority and low-priority jobs that take time.
-        for _i in 0..3 {
-            at.queue_lo(|_shelf| {
-                std::thread::sleep(Duration::from_millis(500));
-            });
-            at.queue_hi(|_shelf| {
-                std::thread::sleep(Duration::from_millis(500));
-            });
-        }
-        // Final low-priority job.
-        let (done_sender, done_receiver) = channel();
-        at.queue_lo(move |_shelf| {
-            done_sender.send(()).unwrap();
-        });
-
-        // Nothing happens until the last job completes.
-        assert_eq!(
-            idle_done_receiver.recv_timeout(Duration::from_secs(1)),
-            Err(RecvTimeoutError::Timeout)
-        );
-        done_receiver.recv().unwrap();
-        // Now that the last low-priority job has completed, the idle task should
-        // fire pretty much immediately.
-        idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
-
-        // Idle callback not executed again even if we wait for a while.
-        assert_eq!(
-            idle_done_receiver.recv_timeout(Duration::from_secs(3)),
-            Err(RecvTimeoutError::Timeout)
-        );
-
-        // However, if more work is done then there's another chance to go idle.
-        let (done_sender, done_receiver) = channel();
-        at.queue_hi(move |_shelf| {
-            std::thread::sleep(Duration::from_millis(500));
-            done_sender.send(()).unwrap();
-        });
-        // Idle callback not immediately executed, because the high priority
-        // job is taking a while.
-        assert_eq!(
-            idle_done_receiver.recv_timeout(Duration::from_millis(1)),
-            Err(RecvTimeoutError::Timeout)
-        );
-        done_receiver.recv().unwrap();
-        idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
-    }
-
-    #[test]
-    fn test_async_task_multiple_idle() {
-        let at = AsyncTask::new(Duration::from_secs(3));
-        let (idle_sender, idle_receiver) = sync_channel::<i32>(5);
-        // Queue a high priority job to start things off
-        at.queue_hi(|_shelf| {
-            std::thread::sleep(Duration::from_millis(500));
-        });
-
-        // Multiple idle callbacks.
-        for i in 0..3 {
-            let idle_sender = idle_sender.clone();
-            at.add_idle(move |_shelf| {
-                idle_sender.send(i).unwrap();
-            });
-        }
-
-        // Nothing happens immediately.
-        assert_eq!(
-            idle_receiver.recv_timeout(Duration::from_millis(1)),
-            Err(RecvTimeoutError::Timeout)
-        );
-        // Wait for a moment and the idle jobs should have run.
-        std::thread::sleep(Duration::from_secs(1));
-
-        let mut results = Vec::new();
-        while let Ok(i) = idle_receiver.recv_timeout(Duration::from_millis(1)) {
-            results.push(i);
-        }
-        assert_eq!(results, [0, 1, 2]);
-    }
-
-    #[test]
-    fn test_async_task_idle_queues_job() {
-        let at = Arc::new(AsyncTask::new(Duration::from_secs(1)));
-        let at_clone = at.clone();
-        let (idle_sender, idle_receiver) = sync_channel::<i32>(100);
-        // Add an idle callback that queues a low-priority job.
-        at.add_idle(move |shelf| {
-            at_clone.queue_lo(|_shelf| {
-                // Slow things down so the channel doesn't fill up.
-                std::thread::sleep(Duration::from_millis(50));
-            });
-            let i = shelf.get_mut::<i32>();
-            idle_sender.send(*i).unwrap();
-            *i += 1;
-        });
-
-        // Nothing happens immediately.
-        assert_eq!(
-            idle_receiver.recv_timeout(Duration::from_millis(1500)),
-            Err(RecvTimeoutError::Timeout)
-        );
-
-        // Once we queue a normal job, things start.
-        at.queue_hi(|_shelf| {});
-        assert_eq!(0, idle_receiver.recv_timeout(Duration::from_millis(200)).unwrap());
-
-        // The idle callback queues a job, and completion of that job
-        // means the task is going idle again...so the idle callback will
-        // be called repeatedly.
-        assert_eq!(1, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
-        assert_eq!(2, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
-        assert_eq!(3, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
-    }
-
-    #[test]
-    #[should_panic]
-    fn test_async_task_idle_panic() {
-        let at = AsyncTask::new(Duration::from_secs(1));
-        let (idle_sender, idle_receiver) = sync_channel::<()>(3);
-        // Add an idle callback that panics.
-        at.add_idle(move |_shelf| {
-            idle_sender.send(()).unwrap();
-            panic!("Panic from idle callback");
-        });
-        // Queue a job to trigger idleness and ensuing panic.
-        at.queue_hi(|_shelf| {});
-        idle_receiver.recv().unwrap();
-
-        // Queue another job afterwards to ensure that the async thread gets joined
-        // and the panic detected.
-        let (done_sender, done_receiver) = channel();
-        at.queue_hi(move |_shelf| {
-            done_sender.send(()).unwrap();
-        });
-        done_receiver.recv().unwrap();
-    }
-}
diff --git a/keystore2/src/async_task/tests.rs b/keystore2/src/async_task/tests.rs
new file mode 100644
index 0000000..e67303e
--- /dev/null
+++ b/keystore2/src/async_task/tests.rs
@@ -0,0 +1,287 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Async task tests.
+use super::{AsyncTask, Shelf};
+use std::sync::{
+    mpsc::{channel, sync_channel, RecvTimeoutError},
+    Arc,
+};
+use std::time::Duration;
+
+#[test]
+fn test_shelf() {
+    let mut shelf = Shelf::default();
+
+    let s = "A string".to_string();
+    assert_eq!(shelf.put(s), None);
+
+    let s2 = "Another string".to_string();
+    assert_eq!(shelf.put(s2), Some("A string".to_string()));
+
+    // Put something of a different type on the shelf.
+    #[derive(Debug, PartialEq, Eq)]
+    struct Elf {
+        pub name: String,
+    }
+    let e1 = Elf { name: "Glorfindel".to_string() };
+    assert_eq!(shelf.put(e1), None);
+
+    // The String value is still on the shelf.
+    let s3 = shelf.get_downcast_ref::<String>().unwrap();
+    assert_eq!(s3, "Another string");
+
+    // As is the Elf.
+    {
+        let e2 = shelf.get_downcast_mut::<Elf>().unwrap();
+        assert_eq!(e2.name, "Glorfindel");
+        e2.name = "Celeborn".to_string();
+    }
+
+    // Take the Elf off the shelf.
+    let e3 = shelf.remove_downcast_ref::<Elf>().unwrap();
+    assert_eq!(e3.name, "Celeborn");
+
+    assert_eq!(shelf.remove_downcast_ref::<Elf>(), None);
+
+    // No u64 value has been put on the shelf, so getting one gives the default value.
+    {
+        let i = shelf.get_mut::<u64>();
+        assert_eq!(*i, 0);
+        *i = 42;
+    }
+    let i2 = shelf.get_downcast_ref::<u64>().unwrap();
+    assert_eq!(*i2, 42);
+
+    // No i32 value has ever been seen near the shelf.
+    assert_eq!(shelf.get_downcast_ref::<i32>(), None);
+    assert_eq!(shelf.get_downcast_mut::<i32>(), None);
+    assert_eq!(shelf.remove_downcast_ref::<i32>(), None);
+}
+
+#[test]
+fn test_async_task() {
+    let at = AsyncTask::default();
+
+    // First queue up a job that blocks until we release it, to avoid
+    // unpredictable synchronization.
+    let (start_sender, start_receiver) = channel();
+    at.queue_hi(move |shelf| {
+        start_receiver.recv().unwrap();
+        // Put a trace vector on the shelf
+        shelf.put(Vec::<String>::new());
+    });
+
+    // Queue up some high-priority and low-priority jobs.
+    for i in 0..3 {
+        let j = i;
+        at.queue_lo(move |shelf| {
+            let trace = shelf.get_mut::<Vec<String>>();
+            trace.push(format!("L{}", j));
+        });
+        let j = i;
+        at.queue_hi(move |shelf| {
+            let trace = shelf.get_mut::<Vec<String>>();
+            trace.push(format!("H{}", j));
+        });
+    }
+
+    // Finally queue up a low priority job that emits the trace.
+    let (trace_sender, trace_receiver) = channel();
+    at.queue_lo(move |shelf| {
+        let trace = shelf.get_downcast_ref::<Vec<String>>().unwrap();
+        trace_sender.send(trace.clone()).unwrap();
+    });
+
+    // Ready, set, go.
+    start_sender.send(()).unwrap();
+    let trace = trace_receiver.recv().unwrap();
+
+    assert_eq!(trace, vec!["H0", "H1", "H2", "L0", "L1", "L2"]);
+}
+
+#[test]
+fn test_async_task_chain() {
+    let at = Arc::new(AsyncTask::default());
+    let (sender, receiver) = channel();
+    // Queue up a job that will queue up another job. This confirms
+    // that the job is not invoked with any internal AsyncTask locks held.
+    let at_clone = at.clone();
+    at.queue_hi(move |_shelf| {
+        at_clone.queue_lo(move |_shelf| {
+            sender.send(()).unwrap();
+        });
+    });
+    receiver.recv().unwrap();
+}
+
+#[test]
+#[should_panic]
+fn test_async_task_panic() {
+    let at = AsyncTask::default();
+    at.queue_hi(|_shelf| {
+        panic!("Panic from queued job");
+    });
+    // Queue another job afterwards to ensure that the async thread gets joined.
+    let (done_sender, done_receiver) = channel();
+    at.queue_hi(move |_shelf| {
+        done_sender.send(()).unwrap();
+    });
+    done_receiver.recv().unwrap();
+}
+
+#[test]
+fn test_async_task_idle() {
+    let at = AsyncTask::new(Duration::from_secs(3));
+    // Need a SyncSender as it is Send+Sync.
+    let (idle_done_sender, idle_done_receiver) = sync_channel::<()>(3);
+    at.add_idle(move |_shelf| {
+        idle_done_sender.send(()).unwrap();
+    });
+
+    // Queue up some high-priority and low-priority jobs that take time.
+    for _i in 0..3 {
+        at.queue_lo(|_shelf| {
+            std::thread::sleep(Duration::from_millis(500));
+        });
+        at.queue_hi(|_shelf| {
+            std::thread::sleep(Duration::from_millis(500));
+        });
+    }
+    // Final low-priority job.
+    let (done_sender, done_receiver) = channel();
+    at.queue_lo(move |_shelf| {
+        done_sender.send(()).unwrap();
+    });
+
+    // Nothing happens until the last job completes.
+    assert_eq!(
+        idle_done_receiver.recv_timeout(Duration::from_secs(1)),
+        Err(RecvTimeoutError::Timeout)
+    );
+    done_receiver.recv().unwrap();
+    // Now that the last low-priority job has completed, the idle task should
+    // fire pretty much immediately.
+    idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
+
+    // Idle callback not executed again even if we wait for a while.
+    assert_eq!(
+        idle_done_receiver.recv_timeout(Duration::from_secs(3)),
+        Err(RecvTimeoutError::Timeout)
+    );
+
+    // However, if more work is done then there's another chance to go idle.
+    let (done_sender, done_receiver) = channel();
+    at.queue_hi(move |_shelf| {
+        std::thread::sleep(Duration::from_millis(500));
+        done_sender.send(()).unwrap();
+    });
+    // Idle callback not immediately executed, because the high priority
+    // job is taking a while.
+    assert_eq!(
+        idle_done_receiver.recv_timeout(Duration::from_millis(1)),
+        Err(RecvTimeoutError::Timeout)
+    );
+    done_receiver.recv().unwrap();
+    idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
+}
+
+#[test]
+fn test_async_task_multiple_idle() {
+    let at = AsyncTask::new(Duration::from_secs(3));
+    let (idle_sender, idle_receiver) = sync_channel::<i32>(5);
+    // Queue a high priority job to start things off
+    at.queue_hi(|_shelf| {
+        std::thread::sleep(Duration::from_millis(500));
+    });
+
+    // Multiple idle callbacks.
+    for i in 0..3 {
+        let idle_sender = idle_sender.clone();
+        at.add_idle(move |_shelf| {
+            idle_sender.send(i).unwrap();
+        });
+    }
+
+    // Nothing happens immediately.
+    assert_eq!(
+        idle_receiver.recv_timeout(Duration::from_millis(1)),
+        Err(RecvTimeoutError::Timeout)
+    );
+    // Wait for a moment and the idle jobs should have run.
+    std::thread::sleep(Duration::from_secs(1));
+
+    let mut results = Vec::new();
+    while let Ok(i) = idle_receiver.recv_timeout(Duration::from_millis(1)) {
+        results.push(i);
+    }
+    assert_eq!(results, [0, 1, 2]);
+}
+
+#[test]
+fn test_async_task_idle_queues_job() {
+    let at = Arc::new(AsyncTask::new(Duration::from_secs(1)));
+    let at_clone = at.clone();
+    let (idle_sender, idle_receiver) = sync_channel::<i32>(100);
+    // Add an idle callback that queues a low-priority job.
+    at.add_idle(move |shelf| {
+        at_clone.queue_lo(|_shelf| {
+            // Slow things down so the channel doesn't fill up.
+            std::thread::sleep(Duration::from_millis(50));
+        });
+        let i = shelf.get_mut::<i32>();
+        idle_sender.send(*i).unwrap();
+        *i += 1;
+    });
+
+    // Nothing happens immediately.
+    assert_eq!(
+        idle_receiver.recv_timeout(Duration::from_millis(1500)),
+        Err(RecvTimeoutError::Timeout)
+    );
+
+    // Once we queue a normal job, things start.
+    at.queue_hi(|_shelf| {});
+    assert_eq!(0, idle_receiver.recv_timeout(Duration::from_millis(200)).unwrap());
+
+    // The idle callback queues a job, and completion of that job
+    // means the task is going idle again...so the idle callback will
+    // be called repeatedly.
+    assert_eq!(1, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
+    assert_eq!(2, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
+    assert_eq!(3, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap());
+}
+
+#[test]
+#[should_panic]
+fn test_async_task_idle_panic() {
+    let at = AsyncTask::new(Duration::from_secs(1));
+    let (idle_sender, idle_receiver) = sync_channel::<()>(3);
+    // Add an idle callback that panics.
+    at.add_idle(move |_shelf| {
+        idle_sender.send(()).unwrap();
+        panic!("Panic from idle callback");
+    });
+    // Queue a job to trigger idleness and ensuing panic.
+    at.queue_hi(|_shelf| {});
+    idle_receiver.recv().unwrap();
+
+    // Queue another job afterwards to ensure that the async thread gets joined
+    // and the panic detected.
+    let (done_sender, done_receiver) = channel();
+    at.queue_hi(move |_shelf| {
+        done_sender.send(()).unwrap();
+    });
+    done_receiver.recv().unwrap();
+}
diff --git a/keystore2/src/attestation_key_utils.rs b/keystore2/src/attestation_key_utils.rs
index 184b3cb..4a8923c 100644
--- a/keystore2/src/attestation_key_utils.rs
+++ b/keystore2/src/attestation_key_utils.rs
@@ -23,7 +23,7 @@
 use crate::remote_provisioning::RemProvState;
 use crate::utils::check_key_permission;
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-    AttestationKey::AttestationKey, Certificate::Certificate, KeyParameter::KeyParameter, Tag::Tag,
+    AttestationKey::AttestationKey, KeyParameter::KeyParameter, Tag::Tag,
 };
 use android_system_keystore2::aidl::android::system::keystore2::{
     Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
@@ -37,7 +37,8 @@
 pub enum AttestationKeyInfo {
     RkpdProvisioned {
         attestation_key: AttestationKey,
-        attestation_certs: Certificate,
+        /// Concatenated chain of DER-encoded certificates (ending with the root).
+        attestation_certs: Vec<u8>,
     },
     UserGenerated {
         key_id_guard: KeyIdGuard,
diff --git a/keystore2/src/audit_log.rs b/keystore2/src/audit_log.rs
index 8d9735e..4952b3b 100644
--- a/keystore2/src/audit_log.rs
+++ b/keystore2/src/audit_log.rs
@@ -34,8 +34,8 @@
     match domain {
         Domain::APP => uid,
         Domain::SELINUX => (nspace | FLAG_NAMESPACE) as i32,
-        _ => {
-            log::info!("Not logging audit event for key with unexpected domain");
+        d => {
+            log::info!("Not logging audit event for key with domain {d:?}");
             0
         }
     }
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index c76f86b..7812df6 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -20,7 +20,6 @@
 use crate::ks_err;
 use crate::permission::KeystorePerm;
 use crate::utils::{check_keystore_permission, watchdog as wd};
-use aconfig_android_hardware_biometrics_rust;
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
 };
@@ -36,7 +35,6 @@
 use anyhow::{Context, Result};
 use keystore2_crypto::Password;
 use keystore2_selinux as selinux;
-use std::ffi::CString;
 
 /// This is the Authorization error type, it wraps binder exceptions and the
 /// Authorization ResponseCode
@@ -288,13 +286,6 @@
         secure_user_id: i64,
         auth_types: &[HardwareAuthenticatorType],
     ) -> binder::Result<i64> {
-        if aconfig_android_hardware_biometrics_rust::last_authentication_time() {
-            self.get_last_auth_time(secure_user_id, auth_types).map_err(into_logged_binder)
-        } else {
-            Err(BinderStatus::new_service_specific_error(
-                ResponseCode::PERMISSION_DENIED.0,
-                Some(CString::new("Feature is not enabled.").unwrap().as_c_str()),
-            ))
-        }
+        self.get_last_auth_time(secure_user_id, auth_types).map_err(into_logged_binder)
     }
 }
diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs
index 09b84ec..b6f308b 100644
--- a/keystore2/src/crypto/lib.rs
+++ b/keystore2/src/crypto/lib.rs
@@ -317,7 +317,7 @@
     }
 }
 
-impl<'a> BorrowedECPoint<'a> {
+impl BorrowedECPoint<'_> {
     /// Get the wrapped EC_POINT object.
     pub fn get_point(&self) -> &EC_POINT {
         // Safety: We only create BorrowedECPoint objects for valid EC_POINTs.
diff --git a/keystore2/src/crypto/tests/certificate_utils_test.cpp b/keystore2/src/crypto/tests/certificate_utils_test.cpp
index a851798..e2f7cdb 100644
--- a/keystore2/src/crypto/tests/certificate_utils_test.cpp
+++ b/keystore2/src/crypto/tests/certificate_utils_test.cpp
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
-
 #include "certificate_utils.h"
+#include <gtest/gtest.h>
 
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -231,6 +230,72 @@
     return s.str();
 }
 
+static std::optional<std::vector<uint8_t>> EncodeX509Algor(const X509_ALGOR* alg) {
+    uint8_t* der = nullptr;
+    int der_len = i2d_X509_ALGOR(alg, &der);
+    if (der_len < 0) {
+        return std::nullopt;
+    }
+    std::vector<uint8_t> ret(der, der + der_len);
+    OPENSSL_free(der);
+    return ret;
+}
+
+// `x509_verify` not working with RSA-PSS & SHA1/SHA224 digests. so, manually
+// verify the certificate with RSA-PSS & SHA1/SHA224 digests.
+// BoringSSL after https://boringssl-review.googlesource.com/c/boringssl/+/53865
+// does not support RSA-PSS with SHA1/SHA224 digests.
+static void verifyCertFieldsExplicitly(X509* cert, Digest digest) {
+    // RSA-PSS-SHA1 AlgorithmIdentifier DER encoded value
+    const std::vector<uint8_t> expected_rsa_pss_sha1 = {
+        0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30, 0x00,
+    };
+    // RSA-PSS-SHA224 AlgorithmIdentifier DER encoded value
+    const std::vector<uint8_t> expected_rsa_pss_sha224 = {
+        0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
+        0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
+        0x02, 0x04, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+        0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+        0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x1c,
+    };
+    const X509_ALGOR* alg;
+    const ASN1_BIT_STRING* sig;
+    const EVP_MD* evp_digest;
+    X509_get0_signature(&sig, &alg, cert);
+    auto encoded = EncodeX509Algor(alg);
+    ASSERT_TRUE(encoded);
+
+    // Check the AlgorithmIdentifiers.
+    if (digest == Digest::SHA1) {
+        evp_digest = EVP_sha1();
+        EXPECT_EQ(encoded.value(), expected_rsa_pss_sha1);
+    } else if (digest == Digest::SHA224) {
+        evp_digest = EVP_sha224();
+        EXPECT_EQ(encoded.value(), expected_rsa_pss_sha224);
+    } else {
+        GTEST_FAIL()
+            << "Error: This is expected to be used only for RSA-PSS with SHA1/SHA224 as digests";
+    }
+
+    // Check the signature.
+    EVP_PKEY_Ptr pubkey(X509_get_pubkey(cert));
+    ASSERT_TRUE(pubkey);
+
+    uint8_t* tbs = nullptr;
+    int tbs_len = i2d_X509_tbs(cert, &tbs);
+    ASSERT_GT(tbs_len, 0);
+
+    size_t sig_len;
+    ASSERT_TRUE(ASN1_BIT_STRING_num_bytes(sig, &sig_len));
+    EVP_PKEY_CTX* pctx;
+    bssl::ScopedEVP_MD_CTX ctx;
+    ASSERT_TRUE(EVP_DigestVerifyInit(ctx.get(), &pctx, evp_digest, nullptr, pubkey.get()));
+    ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING));
+    // The salt length should match the digest length.
+    ASSERT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1));
+    EXPECT_TRUE(EVP_DigestVerify(ctx.get(), ASN1_STRING_get0_data(sig), sig_len, tbs, tbs_len));
+}
+
 INSTANTIATE_TEST_SUITE_P(CertSigningWithCallbackRsa, CertificateUtilsWithRsa,
                          testing::Combine(testing::ValuesIn(rsa_key_sizes),
                                           testing::ValuesIn(rsa_paddings),
@@ -315,10 +380,10 @@
     EVP_PKEY_Ptr decoded_pkey(X509_get_pubkey(decoded_cert.get()));
     if ((padding == Padding::PSS) && (digest == Digest::SHA1 || digest == Digest::SHA224)) {
         // BoringSSL after https://boringssl-review.googlesource.com/c/boringssl/+/53865
-        // does not support these PSS combinations, so skip certificate verification for them
-        // and just check _something_ was returned.
+        // does not support these PSS combinations, so verify these certificates manually.
         EXPECT_NE(decoded_cert.get(), nullptr);
         EXPECT_NE(decoded_pkey.get(), nullptr);
+        verifyCertFieldsExplicitly(decoded_cert.get(), digest);
     } else {
         ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get()));
     }
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index a9a1c4b..626a1c0 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -45,6 +45,9 @@
 pub(crate) mod utils;
 mod versioning;
 
+#[cfg(test)]
+pub mod tests;
+
 use crate::gc::Gc;
 use crate::impl_metadata; // This is in database/utils.rs
 use crate::key_parameter::{KeyParameter, KeyParameterValue, Tag};
@@ -67,12 +70,11 @@
 };
 use anyhow::{anyhow, Context, Result};
 use keystore2_flags;
-use std::{convert::TryFrom, convert::TryInto, ops::Deref, time::SystemTimeError};
+use std::{convert::TryFrom, convert::TryInto, ops::Deref, sync::LazyLock, time::SystemTimeError};
 use utils as db_utils;
 use utils::SqlField;
 
 use keystore2_crypto::ZVec;
-use lazy_static::lazy_static;
 use log::error;
 #[cfg(not(test))]
 use rand::prelude::random;
@@ -123,6 +125,14 @@
     }
 }
 
+/// Access information for a key.
+#[derive(Debug)]
+struct KeyAccessInfo {
+    key_id: i64,
+    descriptor: KeyDescriptor,
+    vector: Option<KeyPermSet>,
+}
+
 /// If the database returns a busy error code, retry after this interval.
 const DB_BUSY_RETRY_INTERVAL: Duration = Duration::from_micros(500);
 
@@ -498,6 +508,40 @@
     }
 }
 
+/// Current state of a `blobentry` row.
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Default)]
+enum BlobState {
+    #[default]
+    /// Current blobentry (of its `subcomponent_type`) for the associated key.
+    Current,
+    /// Blobentry that is no longer the current blob (of its `subcomponent_type`) for the associated
+    /// key.
+    Superseded,
+    /// Blobentry for a key that no longer exists.
+    Orphaned,
+}
+
+impl ToSql for BlobState {
+    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
+        match self {
+            Self::Current => Ok(ToSqlOutput::Owned(Value::Integer(0))),
+            Self::Superseded => Ok(ToSqlOutput::Owned(Value::Integer(1))),
+            Self::Orphaned => Ok(ToSqlOutput::Owned(Value::Integer(2))),
+        }
+    }
+}
+
+impl FromSql for BlobState {
+    fn column_result(value: ValueRef) -> FromSqlResult<Self> {
+        match i64::column_result(value)? {
+            0 => Ok(Self::Current),
+            1 => Ok(Self::Superseded),
+            2 => Ok(Self::Orphaned),
+            v => Err(FromSqlError::OutOfRange(v)),
+        }
+    }
+}
+
 /// Keys have a KeyMint blob component and optional public certificate and
 /// certificate chain components.
 /// KeyEntryLoadBits is a bitmap that indicates to `KeystoreDB::load_key_entry`
@@ -526,9 +570,7 @@
     }
 }
 
-lazy_static! {
-    static ref KEY_ID_LOCK: KeyIdLockDb = KeyIdLockDb::new();
-}
+static KEY_ID_LOCK: LazyLock<KeyIdLockDb> = LazyLock::new(KeyIdLockDb::new);
 
 struct KeyIdLockDb {
     locked_keys: Mutex<HashSet<i64>>,
@@ -870,8 +912,9 @@
 
 impl KeystoreDB {
     const UNASSIGNED_KEY_ID: i64 = -1i64;
-    const CURRENT_DB_VERSION: u32 = 1;
-    const UPGRADERS: &'static [fn(&Transaction) -> Result<u32>] = &[Self::from_0_to_1];
+    const CURRENT_DB_VERSION: u32 = 2;
+    const UPGRADERS: &'static [fn(&Transaction) -> Result<u32>] =
+        &[Self::from_0_to_1, Self::from_1_to_2];
 
     /// Name of the file that holds the cross-boot persistent database.
     pub const PERSISTENT_DB_FILENAME: &'static str = "persistent.sqlite";
@@ -913,9 +956,77 @@
             params![KeyLifeCycle::Unreferenced, Tag::MAX_BOOT_LEVEL.0, BlobMetaData::MaxBootLevel],
         )
         .context(ks_err!("Failed to delete logical boot level keys."))?;
+
+        // DB version is now 1.
         Ok(1)
     }
 
+    // This upgrade function adds an additional `state INTEGER` column to the blobentry
+    // table, and populates it based on whether each blob is the most recent of its type for
+    // the corresponding key.
+    fn from_1_to_2(tx: &Transaction) -> Result<u32> {
+        tx.execute(
+            "ALTER TABLE persistent.blobentry ADD COLUMN state INTEGER DEFAULT 0;",
+            params![],
+        )
+        .context(ks_err!("Failed to add state column"))?;
+
+        // Mark keyblobs that are not the most recent for their corresponding key.
+        // This may take a while if there are excessive numbers of keys in the database.
+        let _wp = wd::watch("KeystoreDB::from_1_to_2 mark all non-current keyblobs");
+        let sc_key_blob = SubComponentType::KEY_BLOB;
+        let mut stmt = tx
+            .prepare(
+                "UPDATE persistent.blobentry SET state=?
+                     WHERE subcomponent_type = ?
+                     AND id NOT IN (
+                             SELECT MAX(id) FROM persistent.blobentry
+                             WHERE subcomponent_type = ?
+                             GROUP BY keyentryid, subcomponent_type
+                         );",
+            )
+            .context("Trying to prepare query to mark superseded keyblobs")?;
+        stmt.execute(params![BlobState::Superseded, sc_key_blob, sc_key_blob])
+            .context(ks_err!("Failed to set state=superseded state for keyblobs"))?;
+        log::info!("marked non-current blobentry rows for keyblobs as superseded");
+
+        // Mark keyblobs that don't have a corresponding key.
+        // This may take a while if there are excessive numbers of keys in the database.
+        let _wp = wd::watch("KeystoreDB::from_1_to_2 mark all orphaned keyblobs");
+        let mut stmt = tx
+            .prepare(
+                "UPDATE persistent.blobentry SET state=?
+                     WHERE subcomponent_type = ?
+                     AND NOT EXISTS (SELECT id FROM persistent.keyentry
+                                     WHERE id = keyentryid);",
+            )
+            .context("Trying to prepare query to mark orphaned keyblobs")?;
+        stmt.execute(params![BlobState::Orphaned, sc_key_blob])
+            .context(ks_err!("Failed to set state=orphaned for keyblobs"))?;
+        log::info!("marked orphaned blobentry rows for keyblobs");
+
+        // Add an index to make it fast to find out of date blobentry rows.
+        let _wp = wd::watch("KeystoreDB::from_1_to_2 add blobentry index");
+        tx.execute(
+            "CREATE INDEX IF NOT EXISTS persistent.blobentry_state_index
+            ON blobentry(subcomponent_type, state);",
+            [],
+        )
+        .context("Failed to create index blobentry_state_index.")?;
+
+        // Add an index to make it fast to find unreferenced keyentry rows.
+        let _wp = wd::watch("KeystoreDB::from_1_to_2 add keyentry state index");
+        tx.execute(
+            "CREATE INDEX IF NOT EXISTS persistent.keyentry_state_index
+            ON keyentry(state);",
+            [],
+        )
+        .context("Failed to create index keyentry_state_index.")?;
+
+        // DB version is now 2.
+        Ok(2)
+    }
+
     fn init_tables(tx: &Transaction) -> Result<()> {
         tx.execute(
             "CREATE TABLE IF NOT EXISTS persistent.keyentry (
@@ -944,12 +1055,21 @@
         )
         .context("Failed to create index keyentry_domain_namespace_index.")?;
 
+        // Index added in v2 of database schema.
+        tx.execute(
+            "CREATE INDEX IF NOT EXISTS persistent.keyentry_state_index
+            ON keyentry(state);",
+            [],
+        )
+        .context("Failed to create index keyentry_state_index.")?;
+
         tx.execute(
             "CREATE TABLE IF NOT EXISTS persistent.blobentry (
                     id INTEGER PRIMARY KEY,
                     subcomponent_type INTEGER,
                     keyentryid INTEGER,
-                    blob BLOB);",
+                    blob BLOB,
+                    state INTEGER DEFAULT 0);", // `state` added in v2 of schema
             [],
         )
         .context("Failed to initialize \"blobentry\" table.")?;
@@ -961,6 +1081,14 @@
         )
         .context("Failed to create index blobentry_keyentryid_index.")?;
 
+        // Index added in v2 of database schema.
+        tx.execute(
+            "CREATE INDEX IF NOT EXISTS persistent.blobentry_state_index
+            ON blobentry(subcomponent_type, state);",
+            [],
+        )
+        .context("Failed to create index blobentry_state_index.")?;
+
         tx.execute(
             "CREATE TABLE IF NOT EXISTS persistent.blobmetadata (
                      id INTEGER PRIMARY KEY,
@@ -1113,11 +1241,11 @@
         )
     }
 
-    /// Fetches a storage statisitics atom for a given storage type. For storage
+    /// Fetches a storage statistics atom for a given storage type. For storage
     /// types that map to a table, information about the table's storage is
     /// returned. Requests for storage types that are not DB tables return None.
     pub fn get_storage_stat(&mut self, storage_type: MetricsStorage) -> Result<StorageStats> {
-        let _wp = wd::watch("KeystoreDB::get_storage_stat");
+        let _wp = wd::watch_millis_with("KeystoreDB::get_storage_stat", 500, storage_type);
 
         match storage_type {
             MetricsStorage::DATABASE => self.get_total_size(),
@@ -1195,9 +1323,28 @@
 
             Self::cleanup_unreferenced(tx).context("Trying to cleanup unreferenced.")?;
 
-            // Find up to `max_blobs` more superseded key blobs, load their metadata and return it.
-            let result: Vec<(i64, Vec<u8>)> = {
-                let _wp = wd::watch("handle_next_superseded_blob find_next");
+            // Find up to `max_blobs` more out-of-date key blobs, load their metadata and return it.
+            let result: Vec<(i64, Vec<u8>)> = if keystore2_flags::use_blob_state_column() {
+                let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob find_next v2");
+                let mut stmt = tx
+                    .prepare(
+                        "SELECT id, blob FROM persistent.blobentry
+                        WHERE subcomponent_type = ? AND state != ?
+                        LIMIT ?;",
+                    )
+                    .context("Trying to prepare query for superseded blobs.")?;
+
+                let rows = stmt
+                    .query_map(
+                        params![SubComponentType::KEY_BLOB, BlobState::Current, max_blobs as i64],
+                        |row| Ok((row.get(0)?, row.get(1)?)),
+                    )
+                    .context("Trying to query superseded blob.")?;
+
+                rows.collect::<Result<Vec<(i64, Vec<u8>)>, rusqlite::Error>>()
+                    .context("Trying to extract superseded blobs.")?
+            } else {
+                let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob find_next v1");
                 let mut stmt = tx
                     .prepare(
                         "SELECT id, blob FROM persistent.blobentry
@@ -1228,7 +1375,7 @@
                     .context("Trying to extract superseded blobs.")?
             };
 
-            let _wp = wd::watch("handle_next_superseded_blob load_metadata");
+            let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob load_metadata");
             let result = result
                 .into_iter()
                 .map(|(blob_id, blob)| {
@@ -1244,22 +1391,32 @@
                 return Ok(result).no_gc();
             }
 
-            // We did not find any superseded key blob, so let's remove other superseded blob in
-            // one transaction.
-            let _wp = wd::watch("handle_next_superseded_blob delete");
-            tx.execute(
-                "DELETE FROM persistent.blobentry
-                 WHERE NOT subcomponent_type = ?
-                 AND (
-                     id NOT IN (
-                        SELECT MAX(id) FROM persistent.blobentry
-                        WHERE NOT subcomponent_type = ?
-                        GROUP BY keyentryid, subcomponent_type
-                     ) OR keyentryid NOT IN (SELECT id FROM persistent.keyentry)
-                 );",
-                params![SubComponentType::KEY_BLOB, SubComponentType::KEY_BLOB],
-            )
-            .context("Trying to purge superseded blobs.")?;
+            // We did not find any out-of-date key blobs, so let's remove other types of superseded
+            // blob in one transaction.
+            if keystore2_flags::use_blob_state_column() {
+                let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob delete v2");
+                tx.execute(
+                    "DELETE FROM persistent.blobentry
+                    WHERE subcomponent_type != ? AND state != ?;",
+                    params![SubComponentType::KEY_BLOB, BlobState::Current],
+                )
+                .context("Trying to purge out-of-date blobs (other than keyblobs)")?;
+            } else {
+                let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob delete v1");
+                tx.execute(
+                    "DELETE FROM persistent.blobentry
+                    WHERE NOT subcomponent_type = ?
+                    AND (
+                        id NOT IN (
+                           SELECT MAX(id) FROM persistent.blobentry
+                           WHERE NOT subcomponent_type = ?
+                           GROUP BY keyentryid, subcomponent_type
+                        ) OR keyentryid NOT IN (SELECT id FROM persistent.keyentry)
+                    );",
+                    params![SubComponentType::KEY_BLOB, SubComponentType::KEY_BLOB],
+                )
+                .context("Trying to purge superseded blobs.")?;
+            }
 
             Ok(vec![]).no_gc()
         })
@@ -1530,18 +1687,33 @@
     ) -> Result<()> {
         match (blob, sc_type) {
             (Some(blob), _) => {
+                // Mark any previous blobentry(s) of the same type for the same key as superseded.
+                tx.execute(
+                    "UPDATE persistent.blobentry SET state = ?
+                    WHERE keyentryid = ? AND subcomponent_type = ?",
+                    params![BlobState::Superseded, key_id, sc_type],
+                )
+                .context(ks_err!(
+                    "Failed to mark prior {sc_type:?} blobentrys for {key_id} as superseded"
+                ))?;
+
+                // Now insert the new, un-superseded, blob.  (If this fails, the marking of
+                // old blobs as superseded will be rolled back, because we're inside a
+                // transaction.)
                 tx.execute(
                     "INSERT INTO persistent.blobentry
                      (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
                     params![sc_type, key_id, blob],
                 )
                 .context(ks_err!("Failed to insert blob."))?;
+
                 if let Some(blob_metadata) = blob_metadata {
                     let blob_id = tx
                         .query_row("SELECT MAX(id) FROM persistent.blobentry;", [], |row| {
                             row.get(0)
                         })
                         .context(ks_err!("Failed to get new blob id."))?;
+
                     blob_metadata
                         .store_in_db(blob_id, tx)
                         .context(ks_err!("Trying to store blob metadata."))?;
@@ -1899,6 +2071,7 @@
     ///       `access_vector`.
     /// * Domain::KEY_ID: The keyentry table is queried for the owning `domain` and
     ///       `namespace`.
+    ///
     /// In each case the information returned is sufficient to perform the access
     /// check and the key id can be used to load further key artifacts.
     fn load_access_tuple(
@@ -1906,7 +2079,7 @@
         key: &KeyDescriptor,
         key_type: KeyType,
         caller_uid: u32,
-    ) -> Result<(i64, KeyDescriptor, Option<KeyPermSet>)> {
+    ) -> Result<KeyAccessInfo> {
         match key.domain {
             // Domain App or SELinux. In this case we load the key_id from
             // the keyentry database for further loading of key components.
@@ -1922,7 +2095,7 @@
                 let key_id = Self::load_key_entry_id(tx, &access_key, key_type)
                     .with_context(|| format!("With key.domain = {:?}.", access_key.domain))?;
 
-                Ok((key_id, access_key, None))
+                Ok(KeyAccessInfo { key_id, descriptor: access_key, vector: None })
             }
 
             // Domain::GRANT. In this case we load the key_id and the access_vector
@@ -1948,7 +2121,11 @@
                         ))
                     })
                     .context("Domain::GRANT.")?;
-                Ok((key_id, key.clone(), Some(access_vector.into())))
+                Ok(KeyAccessInfo {
+                    key_id,
+                    descriptor: key.clone(),
+                    vector: Some(access_vector.into()),
+                })
             }
 
             // Domain::KEY_ID. In this case we load the domain and namespace from the
@@ -2004,7 +2181,7 @@
                 access_key.domain = domain;
                 access_key.nspace = namespace;
 
-                Ok((key_id, access_key, access_vector))
+                Ok(KeyAccessInfo { key_id, descriptor: access_key, vector: access_vector })
             }
             _ => Err(anyhow!(KsError::Rc(ResponseCode::INVALID_ARGUMENT))),
         }
@@ -2191,12 +2368,11 @@
             .context(ks_err!("Failed to initialize transaction."))?;
 
         // Load the key_id and complete the access control tuple.
-        let (key_id, access_key_descriptor, access_vector) =
-            Self::load_access_tuple(&tx, key, key_type, caller_uid).context(ks_err!())?;
+        let access = Self::load_access_tuple(&tx, key, key_type, caller_uid).context(ks_err!())?;
 
         // Perform access control. It is vital that we return here if the permission is denied.
         // So do not touch that '?' at the end.
-        check_permission(&access_key_descriptor, access_vector).context(ks_err!())?;
+        check_permission(&access.descriptor, access.vector).context(ks_err!())?;
 
         // KEY ID LOCK 2/2
         // If we did not get a key id lock by now, it was because we got a key descriptor
@@ -2210,13 +2386,13 @@
         // that the caller had access to the given key. But we need to make sure that the
         // key id still exists. So we have to load the key entry by key id this time.
         let (key_id_guard, tx) = match key_id_guard {
-            None => match KEY_ID_LOCK.try_get(key_id) {
+            None => match KEY_ID_LOCK.try_get(access.key_id) {
                 None => {
                     // Roll back the transaction.
                     tx.rollback().context(ks_err!("Failed to roll back transaction."))?;
 
                     // Block until we have a key id lock.
-                    let key_id_guard = KEY_ID_LOCK.get(key_id);
+                    let key_id_guard = KEY_ID_LOCK.get(access.key_id);
 
                     // Create a new transaction.
                     let tx = self
@@ -2230,7 +2406,7 @@
                         // alias may have been rebound after we rolled back the transaction.
                         &KeyDescriptor {
                             domain: Domain::KEY_ID,
-                            nspace: key_id,
+                            nspace: access.key_id,
                             ..Default::default()
                         },
                         key_type,
@@ -2262,6 +2438,17 @@
             .context("Trying to delete keyparameters.")?;
         tx.execute("DELETE FROM persistent.grant WHERE keyentryid = ?;", params![key_id])
             .context("Trying to delete grants.")?;
+        // The associated blobentry rows are not immediately deleted when the owning keyentry is
+        // removed, because a KeyMint `deleteKey()` invocation is needed (specifically for the
+        // `KEY_BLOB`).  That should not be done from within the database transaction.  Also, calls
+        // to `deleteKey()` need to be delayed until the boot has completed, to avoid making
+        // permanent changes during an OTA before the point of no return.  Mark the affected rows
+        // with `state=Orphaned` so a subsequent garbage collection can do the `deleteKey()`.
+        tx.execute(
+            "UPDATE persistent.blobentry SET state = ? WHERE keyentryid = ?",
+            params![BlobState::Orphaned, key_id],
+        )
+        .context("Trying to mark blobentrys as superseded")?;
         Ok(updated != 0)
     }
 
@@ -2277,16 +2464,15 @@
         let _wp = wd::watch("KeystoreDB::unbind_key");
 
         self.with_transaction(Immediate("TX_unbind_key"), |tx| {
-            let (key_id, access_key_descriptor, access_vector) =
-                Self::load_access_tuple(tx, key, key_type, caller_uid)
-                    .context("Trying to get access tuple.")?;
+            let access = Self::load_access_tuple(tx, key, key_type, caller_uid)
+                .context("Trying to get access tuple.")?;
 
             // Perform access control. It is vital that we return here if the permission is denied.
             // So do not touch that '?' at the end.
-            check_permission(&access_key_descriptor, access_vector)
+            check_permission(&access.descriptor, access.vector)
                 .context("While checking permission.")?;
 
-            Self::mark_unreferenced(tx, key_id)
+            Self::mark_unreferenced(tx, access.key_id)
                 .map(|need_gc| (need_gc, ()))
                 .context("Trying to mark the key unreferenced.")
         })
@@ -2546,6 +2732,8 @@
     /// The key descriptors will have the domain, nspace, and alias field set.
     /// The returned list will be sorted by alias.
     /// Domain must be APP or SELINUX, the caller must make sure of that.
+    /// Number of returned values is limited to 10,000 (which is empirically roughly
+    /// what will fit in a Binder message).
     pub fn list_past_alias(
         &mut self,
         domain: Domain,
@@ -2563,7 +2751,8 @@
                      AND state = ?
                      AND key_type = ?
                      {}
-                     ORDER BY alias ASC;",
+                     ORDER BY alias ASC
+                     LIMIT 10000;",
             if start_past_alias.is_some() { " AND alias > ?" } else { "" }
         );
 
@@ -2653,7 +2842,7 @@
             // We could check key.domain == Domain::GRANT and fail early.
             // But even if we load the access tuple by grant here, the permission
             // check denies the attempt to create a grant by grant descriptor.
-            let (key_id, access_key_descriptor, _) =
+            let access =
                 Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(ks_err!())?;
 
             // Perform access control. It is vital that we return here if the permission
@@ -2661,14 +2850,14 @@
             // This permission check checks if the caller has the grant permission
             // for the given key and in addition to all of the permissions
             // expressed in `access_vector`.
-            check_permission(&access_key_descriptor, &access_vector)
+            check_permission(&access.descriptor, &access_vector)
                 .context(ks_err!("check_permission failed"))?;
 
             let grant_id = if let Some(grant_id) = tx
                 .query_row(
                     "SELECT id FROM persistent.grant
                 WHERE keyentryid = ? AND grantee = ?;",
-                    params![key_id, grantee_uid],
+                    params![access.key_id, grantee_uid],
                     |row| row.get(0),
                 )
                 .optional()
@@ -2687,7 +2876,7 @@
                     tx.execute(
                         "INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector)
                         VALUES (?, ?, ?, ?);",
-                        params![id, grantee_uid, key_id, i32::from(access_vector)],
+                        params![id, grantee_uid, access.key_id, i32::from(access_vector)],
                     )
                 })
                 .context(ks_err!())?
@@ -2712,18 +2901,17 @@
         self.with_transaction(Immediate("TX_ungrant"), |tx| {
             // Load the key_id and complete the access control tuple.
             // We ignore the access vector here because grants cannot be granted.
-            let (key_id, access_key_descriptor, _) =
+            let access =
                 Self::load_access_tuple(tx, key, KeyType::Client, caller_uid).context(ks_err!())?;
 
             // Perform access control. We must return here if the permission
             // was denied. So do not touch the '?' at the end of this line.
-            check_permission(&access_key_descriptor)
-                .context(ks_err!("check_permission failed."))?;
+            check_permission(&access.descriptor).context(ks_err!("check_permission failed."))?;
 
             tx.execute(
                 "DELETE FROM persistent.grant
                 WHERE keyentryid = ? AND grantee = ?;",
-                params![key_id, grantee_uid],
+                params![access.key_id, grantee_uid],
             )
             .context("Failed to delete grant.")?;
 
@@ -2863,2621 +3051,11 @@
         let app_uids_vec: Vec<i64> = app_uids_affected_by_sid.into_iter().collect();
         Ok(app_uids_vec)
     }
-}
 
-#[cfg(test)]
-pub mod tests {
-
-    use super::*;
-    use crate::key_parameter::{
-        Algorithm, BlockMode, Digest, EcCurve, HardwareAuthenticatorType, KeyOrigin, KeyParameter,
-        KeyParameterValue, KeyPurpose, PaddingMode, SecurityLevel,
-    };
-    use crate::key_perm_set;
-    use crate::permission::{KeyPerm, KeyPermSet};
-    use crate::super_key::{SuperKeyManager, USER_AFTER_FIRST_UNLOCK_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType};
-    use keystore2_test_utils::TempDir;
-    use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-        HardwareAuthToken::HardwareAuthToken,
-        HardwareAuthenticatorType::HardwareAuthenticatorType as kmhw_authenticator_type,
-    };
-    use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
-        Timestamp::Timestamp,
-    };
-    use std::cell::RefCell;
-    use std::collections::BTreeMap;
-    use std::fmt::Write;
-    use std::sync::atomic::{AtomicU8, Ordering};
-    use std::sync::Arc;
-    use std::thread;
-    use std::time::{Duration, SystemTime};
-    use crate::utils::AesGcm;
-    #[cfg(disabled)]
-    use std::time::Instant;
-
-    pub fn new_test_db() -> Result<KeystoreDB> {
-        let conn = KeystoreDB::make_connection("file::memory:")?;
-
-        let mut db = KeystoreDB { conn, gc: None, perboot: Arc::new(perboot::PerbootDB::new()) };
-        db.with_transaction(Immediate("TX_new_test_db"), |tx| {
-            KeystoreDB::init_tables(tx).context("Failed to initialize tables.").no_gc()
-        })?;
-        Ok(db)
-    }
-
-    fn rebind_alias(
-        db: &mut KeystoreDB,
-        newid: &KeyIdGuard,
-        alias: &str,
-        domain: Domain,
-        namespace: i64,
-    ) -> Result<bool> {
-        db.with_transaction(Immediate("TX_rebind_alias"), |tx| {
-            KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace, KeyType::Client).no_gc()
-        })
-        .context(ks_err!())
-    }
-
-    #[test]
-    fn datetime() -> Result<()> {
-        let conn = Connection::open_in_memory()?;
-        conn.execute("CREATE TABLE test (ts DATETIME);", [])?;
-        let now = SystemTime::now();
-        let duration = Duration::from_secs(1000);
-        let then = now.checked_sub(duration).unwrap();
-        let soon = now.checked_add(duration).unwrap();
-        conn.execute(
-            "INSERT INTO test (ts) VALUES (?), (?), (?);",
-            params![DateTime::try_from(now)?, DateTime::try_from(then)?, DateTime::try_from(soon)?],
-        )?;
-        let mut stmt = conn.prepare("SELECT ts FROM test ORDER BY ts ASC;")?;
-        let mut rows = stmt.query([])?;
-        assert_eq!(DateTime::try_from(then)?, rows.next()?.unwrap().get(0)?);
-        assert_eq!(DateTime::try_from(now)?, rows.next()?.unwrap().get(0)?);
-        assert_eq!(DateTime::try_from(soon)?, rows.next()?.unwrap().get(0)?);
-        assert!(rows.next()?.is_none());
-        assert!(DateTime::try_from(then)? < DateTime::try_from(now)?);
-        assert!(DateTime::try_from(then)? < DateTime::try_from(soon)?);
-        assert!(DateTime::try_from(now)? < DateTime::try_from(soon)?);
-        Ok(())
-    }
-
-    // Ensure that we're using the "injected" random function, not the real one.
-    #[test]
-    fn test_mocked_random() {
-        let rand1 = random();
-        let rand2 = random();
-        let rand3 = random();
-        if rand1 == rand2 {
-            assert_eq!(rand2 + 1, rand3);
-        } else {
-            assert_eq!(rand1 + 1, rand2);
-            assert_eq!(rand2, rand3);
-        }
-    }
-
-    // Test that we have the correct tables.
-    #[test]
-    fn test_tables() -> Result<()> {
-        let db = new_test_db()?;
-        let tables = db
-            .conn
-            .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
-            .query_map(params![], |row| row.get(0))?
-            .collect::<rusqlite::Result<Vec<String>>>()?;
-        assert_eq!(tables.len(), 6);
-        assert_eq!(tables[0], "blobentry");
-        assert_eq!(tables[1], "blobmetadata");
-        assert_eq!(tables[2], "grant");
-        assert_eq!(tables[3], "keyentry");
-        assert_eq!(tables[4], "keymetadata");
-        assert_eq!(tables[5], "keyparameter");
-        Ok(())
-    }
-
-    #[test]
-    fn test_auth_token_table_invariant() -> Result<()> {
-        let mut db = new_test_db()?;
-        let auth_token1 = HardwareAuthToken {
-            challenge: i64::MAX,
-            userId: 200,
-            authenticatorId: 200,
-            authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
-            timestamp: Timestamp { milliSeconds: 500 },
-            mac: String::from("mac").into_bytes(),
-        };
-        db.insert_auth_token(&auth_token1);
-        let auth_tokens_returned = get_auth_tokens(&db);
-        assert_eq!(auth_tokens_returned.len(), 1);
-
-        // insert another auth token with the same values for the columns in the UNIQUE constraint
-        // of the auth token table and different value for timestamp
-        let auth_token2 = HardwareAuthToken {
-            challenge: i64::MAX,
-            userId: 200,
-            authenticatorId: 200,
-            authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
-            timestamp: Timestamp { milliSeconds: 600 },
-            mac: String::from("mac").into_bytes(),
-        };
-
-        db.insert_auth_token(&auth_token2);
-        let mut auth_tokens_returned = get_auth_tokens(&db);
-        assert_eq!(auth_tokens_returned.len(), 1);
-
-        if let Some(auth_token) = auth_tokens_returned.pop() {
-            assert_eq!(auth_token.auth_token.timestamp.milliSeconds, 600);
-        }
-
-        // insert another auth token with the different values for the columns in the UNIQUE
-        // constraint of the auth token table
-        let auth_token3 = HardwareAuthToken {
-            challenge: i64::MAX,
-            userId: 201,
-            authenticatorId: 200,
-            authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
-            timestamp: Timestamp { milliSeconds: 600 },
-            mac: String::from("mac").into_bytes(),
-        };
-
-        db.insert_auth_token(&auth_token3);
-        let auth_tokens_returned = get_auth_tokens(&db);
-        assert_eq!(auth_tokens_returned.len(), 2);
-
-        Ok(())
-    }
-
-    // utility function for test_auth_token_table_invariant()
-    fn get_auth_tokens(db: &KeystoreDB) -> Vec<AuthTokenEntry> {
-        db.perboot.get_all_auth_token_entries()
-    }
-
-    fn create_key_entry(
-        db: &mut KeystoreDB,
-        domain: &Domain,
-        namespace: &i64,
-        key_type: KeyType,
-        km_uuid: &Uuid,
-    ) -> Result<KeyIdGuard> {
-        db.with_transaction(Immediate("TX_create_key_entry"), |tx| {
-            KeystoreDB::create_key_entry_internal(tx, domain, namespace, key_type, km_uuid).no_gc()
-        })
-    }
-
-    #[test]
-    fn test_persistence_for_files() -> Result<()> {
-        let temp_dir = TempDir::new("persistent_db_test")?;
-        let mut db = KeystoreDB::new(temp_dir.path(), None)?;
-
-        create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?;
-        let entries = get_keyentry(&db)?;
-        assert_eq!(entries.len(), 1);
-
-        let db = KeystoreDB::new(temp_dir.path(), None)?;
-
-        let entries_new = get_keyentry(&db)?;
-        assert_eq!(entries, entries_new);
-        Ok(())
-    }
-
-    #[test]
-    fn test_create_key_entry() -> Result<()> {
-        fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>, Uuid) {
-            (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref(), ke.km_uuid.unwrap())
-        }
-
-        let mut db = new_test_db()?;
-
-        create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?;
-        create_key_entry(&mut db, &Domain::SELINUX, &101, KeyType::Client, &KEYSTORE_UUID)?;
-
-        let entries = get_keyentry(&db)?;
-        assert_eq!(entries.len(), 2);
-        assert_eq!(extractor(&entries[0]), (Domain::APP, 100, None, KEYSTORE_UUID));
-        assert_eq!(extractor(&entries[1]), (Domain::SELINUX, 101, None, KEYSTORE_UUID));
-
-        // Test that we must pass in a valid Domain.
-        check_result_is_error_containing_string(
-            create_key_entry(&mut db, &Domain::GRANT, &102, KeyType::Client, &KEYSTORE_UUID),
-            &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
-        );
-        check_result_is_error_containing_string(
-            create_key_entry(&mut db, &Domain::BLOB, &103, KeyType::Client, &KEYSTORE_UUID),
-            &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
-        );
-        check_result_is_error_containing_string(
-            create_key_entry(&mut db, &Domain::KEY_ID, &104, KeyType::Client, &KEYSTORE_UUID),
-            &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_rebind_alias() -> Result<()> {
-        fn extractor(
-            ke: &KeyEntryRow,
-        ) -> (Option<Domain>, Option<i64>, Option<&str>, Option<Uuid>) {
-            (ke.domain, ke.namespace, ke.alias.as_deref(), ke.km_uuid)
-        }
-
-        let mut db = new_test_db()?;
-        create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
-        create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
-        let entries = get_keyentry(&db)?;
-        assert_eq!(entries.len(), 2);
-        assert_eq!(
-            extractor(&entries[0]),
-            (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))
-        );
-        assert_eq!(
-            extractor(&entries[1]),
-            (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))
-        );
-
-        // Test that the first call to rebind_alias sets the alias.
-        rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[0].id), "foo", Domain::APP, 42)?;
-        let entries = get_keyentry(&db)?;
-        assert_eq!(entries.len(), 2);
-        assert_eq!(
-            extractor(&entries[0]),
-            (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
-        );
-        assert_eq!(
-            extractor(&entries[1]),
-            (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))
-        );
-
-        // Test that the second call to rebind_alias also empties the old one.
-        rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[1].id), "foo", Domain::APP, 42)?;
-        let entries = get_keyentry(&db)?;
-        assert_eq!(entries.len(), 2);
-        assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID)));
-        assert_eq!(
-            extractor(&entries[1]),
-            (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
-        );
-
-        // Test that we must pass in a valid Domain.
-        check_result_is_error_containing_string(
-            rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::GRANT, 42),
-            &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
-        );
-        check_result_is_error_containing_string(
-            rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::BLOB, 42),
-            &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
-        );
-        check_result_is_error_containing_string(
-            rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::KEY_ID, 42),
-            &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
-        );
-
-        // Test that we correctly handle setting an alias for something that does not exist.
-        check_result_is_error_containing_string(
-            rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::SELINUX, 42),
-            "Expected to update a single entry but instead updated 0",
-        );
-        // Test that we correctly abort the transaction in this case.
-        let entries = get_keyentry(&db)?;
-        assert_eq!(entries.len(), 2);
-        assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID)));
-        assert_eq!(
-            extractor(&entries[1]),
-            (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_grant_ungrant() -> Result<()> {
-        const CALLER_UID: u32 = 15;
-        const GRANTEE_UID: u32 = 12;
-        const SELINUX_NAMESPACE: i64 = 7;
-
-        let mut db = new_test_db()?;
-        db.conn.execute(
-            "INSERT INTO persistent.keyentry (id, key_type, domain, namespace, alias, state, km_uuid)
-                VALUES (1, 0, 0, 15, 'key', 1, ?), (2, 0, 2, 7, 'yek', 1, ?);",
-            params![KEYSTORE_UUID, KEYSTORE_UUID],
-        )?;
-        let app_key = KeyDescriptor {
-            domain: super::Domain::APP,
-            nspace: 0,
-            alias: Some("key".to_string()),
-            blob: None,
-        };
-        const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo];
-        const PVEC2: KeyPermSet = key_perm_set![KeyPerm::Use];
-
-        // Reset totally predictable random number generator in case we
-        // are not the first test running on this thread.
-        reset_random();
-        let next_random = 0i64;
-
-        let app_granted_key = db
-            .grant(&app_key, CALLER_UID, GRANTEE_UID, PVEC1, |k, a| {
-                assert_eq!(*a, PVEC1);
-                assert_eq!(
-                    *k,
-                    KeyDescriptor {
-                        domain: super::Domain::APP,
-                        // namespace must be set to the caller_uid.
-                        nspace: CALLER_UID as i64,
-                        alias: Some("key".to_string()),
-                        blob: None,
-                    }
-                );
-                Ok(())
-            })
-            .unwrap();
-
-        assert_eq!(
-            app_granted_key,
-            KeyDescriptor {
-                domain: super::Domain::GRANT,
-                // The grantid is next_random due to the mock random number generator.
-                nspace: next_random,
-                alias: None,
-                blob: None,
-            }
-        );
-
-        let selinux_key = KeyDescriptor {
-            domain: super::Domain::SELINUX,
-            nspace: SELINUX_NAMESPACE,
-            alias: Some("yek".to_string()),
-            blob: None,
-        };
-
-        let selinux_granted_key = db
-            .grant(&selinux_key, CALLER_UID, 12, PVEC1, |k, a| {
-                assert_eq!(*a, PVEC1);
-                assert_eq!(
-                    *k,
-                    KeyDescriptor {
-                        domain: super::Domain::SELINUX,
-                        // namespace must be the supplied SELinux
-                        // namespace.
-                        nspace: SELINUX_NAMESPACE,
-                        alias: Some("yek".to_string()),
-                        blob: None,
-                    }
-                );
-                Ok(())
-            })
-            .unwrap();
-
-        assert_eq!(
-            selinux_granted_key,
-            KeyDescriptor {
-                domain: super::Domain::GRANT,
-                // The grantid is next_random + 1 due to the mock random number generator.
-                nspace: next_random + 1,
-                alias: None,
-                blob: None,
-            }
-        );
-
-        // This should update the existing grant with PVEC2.
-        let selinux_granted_key = db
-            .grant(&selinux_key, CALLER_UID, 12, PVEC2, |k, a| {
-                assert_eq!(*a, PVEC2);
-                assert_eq!(
-                    *k,
-                    KeyDescriptor {
-                        domain: super::Domain::SELINUX,
-                        // namespace must be the supplied SELinux
-                        // namespace.
-                        nspace: SELINUX_NAMESPACE,
-                        alias: Some("yek".to_string()),
-                        blob: None,
-                    }
-                );
-                Ok(())
-            })
-            .unwrap();
-
-        assert_eq!(
-            selinux_granted_key,
-            KeyDescriptor {
-                domain: super::Domain::GRANT,
-                // Same grant id as before. The entry was only updated.
-                nspace: next_random + 1,
-                alias: None,
-                blob: None,
-            }
-        );
-
-        {
-            // Limiting scope of stmt, because it borrows db.
-            let mut stmt = db
-                .conn
-                .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
-            let mut rows = stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>([], |row| {
-                Ok((row.get(0)?, row.get(1)?, row.get(2)?, KeyPermSet::from(row.get::<_, i32>(3)?)))
-            })?;
-
-            let r = rows.next().unwrap().unwrap();
-            assert_eq!(r, (next_random, GRANTEE_UID, 1, PVEC1));
-            let r = rows.next().unwrap().unwrap();
-            assert_eq!(r, (next_random + 1, GRANTEE_UID, 2, PVEC2));
-            assert!(rows.next().is_none());
-        }
-
-        debug_dump_keyentry_table(&mut db)?;
-        println!("app_key {:?}", app_key);
-        println!("selinux_key {:?}", selinux_key);
-
-        db.ungrant(&app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
-        db.ungrant(&selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
-
-        Ok(())
-    }
-
-    static TEST_KEY_BLOB: &[u8] = b"my test blob";
-    static TEST_CERT_BLOB: &[u8] = b"my test cert";
-    static TEST_CERT_CHAIN_BLOB: &[u8] = b"my test cert_chain";
-
-    #[test]
-    fn test_set_blob() -> Result<()> {
-        let key_id = KEY_ID_LOCK.get(3000);
-        let mut db = new_test_db()?;
-        let mut blob_metadata = BlobMetaData::new();
-        blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-        db.set_blob(
-            &key_id,
-            SubComponentType::KEY_BLOB,
-            Some(TEST_KEY_BLOB),
-            Some(&blob_metadata),
-        )?;
-        db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
-        db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
-        drop(key_id);
-
-        let mut stmt = db.conn.prepare(
-            "SELECT subcomponent_type, keyentryid, blob, id FROM persistent.blobentry
-                ORDER BY subcomponent_type ASC;",
-        )?;
-        let mut rows = stmt
-            .query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>([], |row| {
-                Ok(((row.get(0)?, row.get(1)?, row.get(2)?), row.get(3)?))
-            })?;
-        let (r, id) = rows.next().unwrap().unwrap();
-        assert_eq!(r, (SubComponentType::KEY_BLOB, 3000, TEST_KEY_BLOB.to_vec()));
-        let (r, _) = rows.next().unwrap().unwrap();
-        assert_eq!(r, (SubComponentType::CERT, 3000, TEST_CERT_BLOB.to_vec()));
-        let (r, _) = rows.next().unwrap().unwrap();
-        assert_eq!(r, (SubComponentType::CERT_CHAIN, 3000, TEST_CERT_CHAIN_BLOB.to_vec()));
-
-        drop(rows);
-        drop(stmt);
-
-        assert_eq!(
-            db.with_transaction(Immediate("TX_test"), |tx| {
-                BlobMetaData::load_from_db(id, tx).no_gc()
-            })
-            .expect("Should find blob metadata."),
-            blob_metadata
-        );
-        Ok(())
-    }
-
-    static TEST_ALIAS: &str = "my super duper key";
-
-    #[test]
-    fn test_insert_and_load_full_keyentry_domain_app() -> Result<()> {
-        let mut db = new_test_db()?;
-        let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)
-            .context("test_insert_and_load_full_keyentry_domain_app")?
-            .0;
-        let (_key_guard, key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: 0,
-                    alias: Some(TEST_ALIAS.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                1,
-                |_k, _av| Ok(()),
-            )
-            .unwrap();
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
-        db.unbind_key(
-            &KeyDescriptor {
-                domain: Domain::APP,
-                nspace: 0,
-                alias: Some(TEST_ALIAS.to_string()),
-                blob: None,
-            },
-            KeyType::Client,
-            1,
-            |_, _| Ok(()),
-        )
-        .unwrap();
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: 0,
-                    alias: Some(TEST_ALIAS.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::NONE,
-                1,
-                |_k, _av| Ok(()),
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_insert_and_load_certificate_entry_domain_app() -> Result<()> {
-        let mut db = new_test_db()?;
-
-        db.store_new_certificate(
-            &KeyDescriptor {
-                domain: Domain::APP,
-                nspace: 1,
-                alias: Some(TEST_ALIAS.to_string()),
-                blob: None,
-            },
-            KeyType::Client,
-            TEST_CERT_BLOB,
-            &KEYSTORE_UUID,
-        )
-        .expect("Trying to insert cert.");
-
-        let (_key_guard, mut key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: 1,
-                    alias: Some(TEST_ALIAS.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::PUBLIC,
-                1,
-                |_k, _av| Ok(()),
-            )
-            .expect("Trying to read certificate entry.");
-
-        assert!(key_entry.pure_cert());
-        assert!(key_entry.cert().is_none());
-        assert_eq!(key_entry.take_cert_chain(), Some(TEST_CERT_BLOB.to_vec()));
-
-        db.unbind_key(
-            &KeyDescriptor {
-                domain: Domain::APP,
-                nspace: 1,
-                alias: Some(TEST_ALIAS.to_string()),
-                blob: None,
-            },
-            KeyType::Client,
-            1,
-            |_, _| Ok(()),
-        )
-        .unwrap();
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: 1,
-                    alias: Some(TEST_ALIAS.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::NONE,
-                1,
-                |_k, _av| Ok(()),
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_insert_and_load_full_keyentry_domain_selinux() -> Result<()> {
-        let mut db = new_test_db()?;
-        let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None)
-            .context("test_insert_and_load_full_keyentry_domain_selinux")?
-            .0;
-        let (_key_guard, key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::SELINUX,
-                    nspace: 1,
-                    alias: Some(TEST_ALIAS.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                1,
-                |_k, _av| Ok(()),
-            )
-            .unwrap();
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
-        db.unbind_key(
-            &KeyDescriptor {
-                domain: Domain::SELINUX,
-                nspace: 1,
-                alias: Some(TEST_ALIAS.to_string()),
-                blob: None,
-            },
-            KeyType::Client,
-            1,
-            |_, _| Ok(()),
-        )
-        .unwrap();
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::SELINUX,
-                    nspace: 1,
-                    alias: Some(TEST_ALIAS.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::NONE,
-                1,
-                |_k, _av| Ok(()),
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_insert_and_load_full_keyentry_domain_key_id() -> Result<()> {
-        let mut db = new_test_db()?;
-        let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None)
-            .context("test_insert_and_load_full_keyentry_domain_key_id")?
-            .0;
-        let (_, key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                1,
-                |_k, _av| Ok(()),
-            )
-            .unwrap();
-
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
-        db.unbind_key(
-            &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
-            KeyType::Client,
-            1,
-            |_, _| Ok(()),
-        )
-        .unwrap();
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
-                KeyType::Client,
-                KeyEntryLoadBits::NONE,
-                1,
-                |_k, _av| Ok(()),
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_check_and_update_key_usage_count_with_limited_use_key() -> Result<()> {
-        let mut db = new_test_db()?;
-        let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(123))
-            .context("test_check_and_update_key_usage_count_with_limited_use_key")?
-            .0;
-        // Update the usage count of the limited use key.
-        db.check_and_update_key_usage_count(key_id)?;
-
-        let (_key_guard, key_entry) = db.load_key_entry(
-            &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
-            KeyType::Client,
-            KeyEntryLoadBits::BOTH,
-            1,
-            |_k, _av| Ok(()),
-        )?;
-
-        // The usage count is decremented now.
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, Some(122)));
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_check_and_update_key_usage_count_with_exhausted_limited_use_key() -> Result<()> {
-        let mut db = new_test_db()?;
-        let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(1))
-            .context("test_check_and_update_key_usage_count_with_exhausted_limited_use_key")?
-            .0;
-        // Update the usage count of the limited use key.
-        db.check_and_update_key_usage_count(key_id).expect(concat!(
-            "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ",
-            "This should succeed."
-        ));
-
-        // Try to update the exhausted limited use key.
-        let e = db.check_and_update_key_usage_count(key_id).expect_err(concat!(
-            "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ",
-            "This should fail."
-        ));
-        assert_eq!(
-            &KsError::Km(ErrorCode::INVALID_KEY_BLOB),
-            e.root_cause().downcast_ref::<KsError>().unwrap()
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_insert_and_load_full_keyentry_from_grant() -> Result<()> {
-        let mut db = new_test_db()?;
-        let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)
-            .context("test_insert_and_load_full_keyentry_from_grant")?
-            .0;
-
-        let granted_key = db
-            .grant(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: 0,
-                    alias: Some(TEST_ALIAS.to_string()),
-                    blob: None,
-                },
-                1,
-                2,
-                key_perm_set![KeyPerm::Use],
-                |_k, _av| Ok(()),
-            )
-            .unwrap();
-
-        debug_dump_grant_table(&mut db)?;
-
-        let (_key_guard, key_entry) = db
-            .load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| {
-                assert_eq!(Domain::GRANT, k.domain);
-                assert!(av.unwrap().includes(KeyPerm::Use));
-                Ok(())
-            })
-            .unwrap();
-
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
-        db.unbind_key(&granted_key, KeyType::Client, 2, |_, _| Ok(())).unwrap();
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &granted_key,
-                KeyType::Client,
-                KeyEntryLoadBits::NONE,
-                2,
-                |_k, _av| Ok(()),
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    // This test attempts to load a key by key id while the caller is not the owner
-    // but a grant exists for the given key and the caller.
-    #[test]
-    fn test_insert_and_load_full_keyentry_from_grant_by_key_id() -> Result<()> {
-        let mut db = new_test_db()?;
-        const OWNER_UID: u32 = 1u32;
-        const GRANTEE_UID: u32 = 2u32;
-        const SOMEONE_ELSE_UID: u32 = 3u32;
-        let key_id = make_test_key_entry(&mut db, Domain::APP, OWNER_UID as i64, TEST_ALIAS, None)
-            .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?
-            .0;
-
-        db.grant(
-            &KeyDescriptor {
-                domain: Domain::APP,
-                nspace: 0,
-                alias: Some(TEST_ALIAS.to_string()),
-                blob: None,
-            },
-            OWNER_UID,
-            GRANTEE_UID,
-            key_perm_set![KeyPerm::Use],
-            |_k, _av| Ok(()),
-        )
-        .unwrap();
-
-        debug_dump_grant_table(&mut db)?;
-
-        let id_descriptor =
-            KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, ..Default::default() };
-
-        let (_, key_entry) = db
-            .load_key_entry(
-                &id_descriptor,
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                GRANTEE_UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(OWNER_UID as i64, k.nspace);
-                    assert!(av.unwrap().includes(KeyPerm::Use));
-                    Ok(())
-                },
-            )
-            .unwrap();
-
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
-        let (_, key_entry) = db
-            .load_key_entry(
-                &id_descriptor,
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                SOMEONE_ELSE_UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(OWNER_UID as i64, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap();
-
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
-        db.unbind_key(&id_descriptor, KeyType::Client, OWNER_UID, |_, _| Ok(())).unwrap();
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &id_descriptor,
-                KeyType::Client,
-                KeyEntryLoadBits::NONE,
-                GRANTEE_UID,
-                |_k, _av| Ok(()),
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    // Creates a key migrates it to a different location and then tries to access it by the old
-    // and new location.
-    #[test]
-    fn test_migrate_key_app_to_app() -> Result<()> {
-        let mut db = new_test_db()?;
-        const SOURCE_UID: u32 = 1u32;
-        const DESTINATION_UID: u32 = 2u32;
-        static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
-        static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
-        let key_id_guard =
-            make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
-                .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
-
-        let source_descriptor: KeyDescriptor = KeyDescriptor {
-            domain: Domain::APP,
-            nspace: -1,
-            alias: Some(SOURCE_ALIAS.to_string()),
-            blob: None,
-        };
-
-        let destination_descriptor: KeyDescriptor = KeyDescriptor {
-            domain: Domain::APP,
-            nspace: -1,
-            alias: Some(DESTINATION_ALIAS.to_string()),
-            blob: None,
-        };
-
-        let key_id = key_id_guard.id();
-
-        db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| {
-            Ok(())
-        })
-        .unwrap();
-
-        let (_, key_entry) = db
-            .load_key_entry(
-                &destination_descriptor,
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                DESTINATION_UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(DESTINATION_UID as i64, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap();
-
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &source_descriptor,
-                KeyType::Client,
-                KeyEntryLoadBits::NONE,
-                SOURCE_UID,
-                |_k, _av| Ok(()),
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    // Creates a key migrates it to a different location and then tries to access it by the old
-    // and new location.
-    #[test]
-    fn test_migrate_key_app_to_selinux() -> Result<()> {
-        let mut db = new_test_db()?;
-        const SOURCE_UID: u32 = 1u32;
-        const DESTINATION_UID: u32 = 2u32;
-        const DESTINATION_NAMESPACE: i64 = 1000i64;
-        static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
-        static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
-        let key_id_guard =
-            make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
-                .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
-
-        let source_descriptor: KeyDescriptor = KeyDescriptor {
-            domain: Domain::APP,
-            nspace: -1,
-            alias: Some(SOURCE_ALIAS.to_string()),
-            blob: None,
-        };
-
-        let destination_descriptor: KeyDescriptor = KeyDescriptor {
-            domain: Domain::SELINUX,
-            nspace: DESTINATION_NAMESPACE,
-            alias: Some(DESTINATION_ALIAS.to_string()),
-            blob: None,
-        };
-
-        let key_id = key_id_guard.id();
-
-        db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| {
-            Ok(())
-        })
-        .unwrap();
-
-        let (_, key_entry) = db
-            .load_key_entry(
-                &destination_descriptor,
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                DESTINATION_UID,
-                |k, av| {
-                    assert_eq!(Domain::SELINUX, k.domain);
-                    assert_eq!(DESTINATION_NAMESPACE, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap();
-
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &source_descriptor,
-                KeyType::Client,
-                KeyEntryLoadBits::NONE,
-                SOURCE_UID,
-                |_k, _av| Ok(()),
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    // Creates two keys and tries to migrate the first to the location of the second which
-    // is expected to fail.
-    #[test]
-    fn test_migrate_key_destination_occupied() -> Result<()> {
-        let mut db = new_test_db()?;
-        const SOURCE_UID: u32 = 1u32;
-        const DESTINATION_UID: u32 = 2u32;
-        static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
-        static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
-        let key_id_guard =
-            make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
-                .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
-        make_test_key_entry(&mut db, Domain::APP, DESTINATION_UID as i64, DESTINATION_ALIAS, None)
-            .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
-
-        let destination_descriptor: KeyDescriptor = KeyDescriptor {
-            domain: Domain::APP,
-            nspace: -1,
-            alias: Some(DESTINATION_ALIAS.to_string()),
-            blob: None,
-        };
-
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::INVALID_ARGUMENT)),
-            db.migrate_key_namespace(
-                key_id_guard,
-                &destination_descriptor,
-                DESTINATION_UID,
-                |_k| Ok(())
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_upgrade_0_to_1() {
-        const ALIAS1: &str = "test_upgrade_0_to_1_1";
-        const ALIAS2: &str = "test_upgrade_0_to_1_2";
-        const ALIAS3: &str = "test_upgrade_0_to_1_3";
-        const UID: u32 = 33;
-        let temp_dir = Arc::new(TempDir::new("test_upgrade_0_to_1").unwrap());
-        let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
-        let key_id_untouched1 =
-            make_test_key_entry(&mut db, Domain::APP, UID as i64, ALIAS1, None).unwrap().id();
-        let key_id_untouched2 =
-            make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS2, false).unwrap().id();
-        let key_id_deleted =
-            make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS3, true).unwrap().id();
-
-        let (_, key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(ALIAS1.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(UID as i64, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap();
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None));
-        let (_, key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(ALIAS2.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(UID as i64, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap();
-        assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false));
-        let (_, key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(ALIAS3.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(UID as i64, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap();
-        assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_deleted, true));
-
-        db.with_transaction(Immediate("TX_test"), |tx| KeystoreDB::from_0_to_1(tx).no_gc())
-            .unwrap();
-
-        let (_, key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(ALIAS1.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(UID as i64, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap();
-        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None));
-        let (_, key_entry) = db
-            .load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(ALIAS2.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(UID as i64, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap();
-        assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false));
-        assert_eq!(
-            Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
-            db.load_key_entry(
-                &KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(ALIAS3.to_string()),
-                    blob: None,
-                },
-                KeyType::Client,
-                KeyEntryLoadBits::BOTH,
-                UID,
-                |k, av| {
-                    assert_eq!(Domain::APP, k.domain);
-                    assert_eq!(UID as i64, k.nspace);
-                    assert!(av.is_none());
-                    Ok(())
-                },
-            )
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-    }
-
-    static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key";
-
-    #[test]
-    fn test_insert_and_load_full_keyentry_domain_app_concurrently() -> Result<()> {
-        let handle = {
-            let temp_dir = Arc::new(TempDir::new("id_lock_test")?);
-            let temp_dir_clone = temp_dir.clone();
-            let mut db = KeystoreDB::new(temp_dir.path(), None)?;
-            let key_id = make_test_key_entry(&mut db, Domain::APP, 33, KEY_LOCK_TEST_ALIAS, None)
-                .context("test_insert_and_load_full_keyentry_domain_app")?
-                .0;
-            let (_key_guard, key_entry) = db
-                .load_key_entry(
-                    &KeyDescriptor {
-                        domain: Domain::APP,
-                        nspace: 0,
-                        alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
-                        blob: None,
-                    },
-                    KeyType::Client,
-                    KeyEntryLoadBits::BOTH,
-                    33,
-                    |_k, _av| Ok(()),
-                )
-                .unwrap();
-            assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
-            let state = Arc::new(AtomicU8::new(1));
-            let state2 = state.clone();
-
-            // Spawning a second thread that attempts to acquire the key id lock
-            // for the same key as the primary thread. The primary thread then
-            // waits, thereby forcing the secondary thread into the second stage
-            // of acquiring the lock (see KEY ID LOCK 2/2 above).
-            // The test succeeds if the secondary thread observes the transition
-            // of `state` from 1 to 2, despite having a whole second to overtake
-            // the primary thread.
-            let handle = thread::spawn(move || {
-                let temp_dir = temp_dir_clone;
-                let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
-                assert!(db
-                    .load_key_entry(
-                        &KeyDescriptor {
-                            domain: Domain::APP,
-                            nspace: 0,
-                            alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
-                            blob: None,
-                        },
-                        KeyType::Client,
-                        KeyEntryLoadBits::BOTH,
-                        33,
-                        |_k, _av| Ok(()),
-                    )
-                    .is_ok());
-                // We should only see a 2 here because we can only return
-                // from load_key_entry when the `_key_guard` expires,
-                // which happens at the end of the scope.
-                assert_eq!(2, state2.load(Ordering::Relaxed));
-            });
-
-            thread::sleep(std::time::Duration::from_millis(1000));
-
-            assert_eq!(Ok(1), state.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed));
-
-            // Return the handle from this scope so we can join with the
-            // secondary thread after the key id lock has expired.
-            handle
-            // This is where the `_key_guard` goes out of scope,
-            // which is the reason for concurrent load_key_entry on the same key
-            // to unblock.
-        };
-        // Join with the secondary thread and unwrap, to propagate failing asserts to the
-        // main test thread. We will not see failing asserts in secondary threads otherwise.
-        handle.join().unwrap();
-        Ok(())
-    }
-
-    #[test]
-    fn test_database_busy_error_code() {
-        let temp_dir =
-            TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir.");
-
-        let mut db1 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database1.");
-        let mut db2 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database2.");
-
-        let _tx1 = db1
-            .conn
-            .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
-            .expect("Failed to create first transaction.");
-
-        let error = db2
-            .conn
-            .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
-            .context("Transaction begin failed.")
-            .expect_err("This should fail.");
-        let root_cause = error.root_cause();
-        if let Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. }) =
-            root_cause.downcast_ref::<rusqlite::ffi::Error>()
-        {
-            return;
-        }
-        panic!(
-            "Unexpected error {:?} \n{:?} \n{:?}",
-            error,
-            root_cause,
-            root_cause.downcast_ref::<rusqlite::ffi::Error>()
-        )
-    }
-
-    #[cfg(disabled)]
-    #[test]
-    fn test_large_number_of_concurrent_db_manipulations() -> Result<()> {
-        let temp_dir = Arc::new(
-            TempDir::new("test_large_number_of_concurrent_db_manipulations_")
-                .expect("Failed to create temp dir."),
-        );
-
-        let test_begin = Instant::now();
-
-        const KEY_COUNT: u32 = 500u32;
-        let mut db =
-            new_test_db_with_gc(temp_dir.path(), |_, _| Ok(())).expect("Failed to open database.");
-        const OPEN_DB_COUNT: u32 = 50u32;
-
-        let mut actual_key_count = KEY_COUNT;
-        // First insert KEY_COUNT keys.
-        for count in 0..KEY_COUNT {
-            if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) {
-                actual_key_count = count;
-                break;
-            }
-            let alias = format!("test_alias_{}", count);
-            make_test_key_entry(&mut db, Domain::APP, 1, &alias, None)
-                .expect("Failed to make key entry.");
-        }
-
-        // Insert more keys from a different thread and into a different namespace.
-        let temp_dir1 = temp_dir.clone();
-        let handle1 = thread::spawn(move || {
-            let mut db = new_test_db_with_gc(temp_dir1.path(), |_, _| Ok(()))
-                .expect("Failed to open database.");
-
-            for count in 0..actual_key_count {
-                if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
-                    return;
-                }
-                let alias = format!("test_alias_{}", count);
-                make_test_key_entry(&mut db, Domain::APP, 2, &alias, None)
-                    .expect("Failed to make key entry.");
-            }
-
-            // then unbind them again.
-            for count in 0..actual_key_count {
-                if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
-                    return;
-                }
-                let key = KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(format!("test_alias_{}", count)),
-                    blob: None,
-                };
-                db.unbind_key(&key, KeyType::Client, 2, |_, _| Ok(())).expect("Unbind Failed.");
-            }
-        });
-
-        // And start unbinding the first set of keys.
-        let temp_dir2 = temp_dir.clone();
-        let handle2 = thread::spawn(move || {
-            let mut db = new_test_db_with_gc(temp_dir2.path(), |_, _| Ok(()))
-                .expect("Failed to open database.");
-
-            for count in 0..actual_key_count {
-                if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
-                    return;
-                }
-                let key = KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(format!("test_alias_{}", count)),
-                    blob: None,
-                };
-                db.unbind_key(&key, KeyType::Client, 1, |_, _| Ok(())).expect("Unbind Failed.");
-            }
-        });
-
-        // While a lot of inserting and deleting is going on we have to open database connections
-        // successfully and use them.
-        // This clone is not redundant, because temp_dir needs to be kept alive until db goes
-        // out of scope.
-        #[allow(clippy::redundant_clone)]
-        let temp_dir4 = temp_dir.clone();
-        let handle4 = thread::spawn(move || {
-            for count in 0..OPEN_DB_COUNT {
-                if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
-                    return;
-                }
-                let mut db = new_test_db_with_gc(temp_dir4.path(), |_, _| Ok(()))
-                    .expect("Failed to open database.");
-
-                let alias = format!("test_alias_{}", count);
-                make_test_key_entry(&mut db, Domain::APP, 3, &alias, None)
-                    .expect("Failed to make key entry.");
-                let key = KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(alias),
-                    blob: None,
-                };
-                db.unbind_key(&key, KeyType::Client, 3, |_, _| Ok(())).expect("Unbind Failed.");
-            }
-        });
-
-        handle1.join().expect("Thread 1 panicked.");
-        handle2.join().expect("Thread 2 panicked.");
-        handle4.join().expect("Thread 4 panicked.");
-
-        Ok(())
-    }
-
-    #[test]
-    fn list() -> Result<()> {
-        let temp_dir = TempDir::new("list_test")?;
-        let mut db = KeystoreDB::new(temp_dir.path(), None)?;
-        static LIST_O_ENTRIES: &[(Domain, i64, &str)] = &[
-            (Domain::APP, 1, "test1"),
-            (Domain::APP, 1, "test2"),
-            (Domain::APP, 1, "test3"),
-            (Domain::APP, 1, "test4"),
-            (Domain::APP, 1, "test5"),
-            (Domain::APP, 1, "test6"),
-            (Domain::APP, 1, "test7"),
-            (Domain::APP, 2, "test1"),
-            (Domain::APP, 2, "test2"),
-            (Domain::APP, 2, "test3"),
-            (Domain::APP, 2, "test4"),
-            (Domain::APP, 2, "test5"),
-            (Domain::APP, 2, "test6"),
-            (Domain::APP, 2, "test8"),
-            (Domain::SELINUX, 100, "test1"),
-            (Domain::SELINUX, 100, "test2"),
-            (Domain::SELINUX, 100, "test3"),
-            (Domain::SELINUX, 100, "test4"),
-            (Domain::SELINUX, 100, "test5"),
-            (Domain::SELINUX, 100, "test6"),
-            (Domain::SELINUX, 100, "test9"),
-        ];
-
-        let list_o_keys: Vec<(i64, i64)> = LIST_O_ENTRIES
-            .iter()
-            .map(|(domain, ns, alias)| {
-                let entry =
-                    make_test_key_entry(&mut db, *domain, *ns, alias, None).unwrap_or_else(|e| {
-                        panic!("Failed to insert {:?} {} {}. Error {:?}", domain, ns, alias, e)
-                    });
-                (entry.id(), *ns)
-            })
-            .collect();
-
-        for (domain, namespace) in
-            &[(Domain::APP, 1i64), (Domain::APP, 2i64), (Domain::SELINUX, 100i64)]
-        {
-            let mut list_o_descriptors: Vec<KeyDescriptor> = LIST_O_ENTRIES
-                .iter()
-                .filter_map(|(domain, ns, alias)| match ns {
-                    ns if *ns == *namespace => Some(KeyDescriptor {
-                        domain: *domain,
-                        nspace: *ns,
-                        alias: Some(alias.to_string()),
-                        blob: None,
-                    }),
-                    _ => None,
-                })
-                .collect();
-            list_o_descriptors.sort();
-            let mut list_result = db.list_past_alias(*domain, *namespace, KeyType::Client, None)?;
-            list_result.sort();
-            assert_eq!(list_o_descriptors, list_result);
-
-            let mut list_o_ids: Vec<i64> = list_o_descriptors
-                .into_iter()
-                .map(|d| {
-                    let (_, entry) = db
-                        .load_key_entry(
-                            &d,
-                            KeyType::Client,
-                            KeyEntryLoadBits::NONE,
-                            *namespace as u32,
-                            |_, _| Ok(()),
-                        )
-                        .unwrap();
-                    entry.id()
-                })
-                .collect();
-            list_o_ids.sort_unstable();
-            let mut loaded_entries: Vec<i64> = list_o_keys
-                .iter()
-                .filter_map(|(id, ns)| match ns {
-                    ns if *ns == *namespace => Some(*id),
-                    _ => None,
-                })
-                .collect();
-            loaded_entries.sort_unstable();
-            assert_eq!(list_o_ids, loaded_entries);
-        }
-        assert_eq!(
-            Vec::<KeyDescriptor>::new(),
-            db.list_past_alias(Domain::SELINUX, 101, KeyType::Client, None)?
-        );
-
-        Ok(())
-    }
-
-    // Helpers
-
-    // Checks that the given result is an error containing the given string.
-    fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) {
-        let error_str = format!(
-            "{:#?}",
-            result.err().unwrap_or_else(|| panic!("Expected the error: {}", target))
-        );
-        assert!(
-            error_str.contains(target),
-            "The string \"{}\" should contain \"{}\"",
-            error_str,
-            target
-        );
-    }
-
-    #[derive(Debug, PartialEq)]
-    struct KeyEntryRow {
-        id: i64,
-        key_type: KeyType,
-        domain: Option<Domain>,
-        namespace: Option<i64>,
-        alias: Option<String>,
-        state: KeyLifeCycle,
-        km_uuid: Option<Uuid>,
-    }
-
-    fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> {
-        db.conn
-            .prepare("SELECT * FROM persistent.keyentry;")?
-            .query_map([], |row| {
-                Ok(KeyEntryRow {
-                    id: row.get(0)?,
-                    key_type: row.get(1)?,
-                    domain: row.get::<_, Option<_>>(2)?.map(Domain),
-                    namespace: row.get(3)?,
-                    alias: row.get(4)?,
-                    state: row.get(5)?,
-                    km_uuid: row.get(6)?,
-                })
-            })?
-            .map(|r| r.context("Could not read keyentry row."))
-            .collect::<Result<Vec<_>>>()
-    }
-
-    fn make_test_params(max_usage_count: Option<i32>) -> Vec<KeyParameter> {
-        make_test_params_with_sids(max_usage_count, &[42])
-    }
-
-    // Note: The parameters and SecurityLevel associations are nonsensical. This
-    // collection is only used to check if the parameters are preserved as expected by the
-    // database.
-    fn make_test_params_with_sids(
-        max_usage_count: Option<i32>,
-        user_secure_ids: &[i64],
-    ) -> Vec<KeyParameter> {
-        let mut params = vec![
-            KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::TRUSTED_ENVIRONMENT),
-            KeyParameter::new(
-                KeyParameterValue::KeyPurpose(KeyPurpose::SIGN),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::Algorithm(Algorithm::RSA),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::TRUSTED_ENVIRONMENT),
-            KeyParameter::new(
-                KeyParameterValue::BlockMode(BlockMode::ECB),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::BlockMode(BlockMode::GCM),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(KeyParameterValue::Digest(Digest::NONE), SecurityLevel::STRONGBOX),
-            KeyParameter::new(
-                KeyParameterValue::Digest(Digest::MD5),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::Digest(Digest::SHA_2_224),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::Digest(Digest::SHA_2_256),
-                SecurityLevel::STRONGBOX,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::PaddingMode(PaddingMode::NONE),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::PaddingMode(PaddingMode::RSA_OAEP),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS),
-                SecurityLevel::STRONGBOX,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::PaddingMode(PaddingMode::RSA_PKCS1_1_5_SIGN),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::TRUSTED_ENVIRONMENT),
-            KeyParameter::new(KeyParameterValue::MinMacLength(256), SecurityLevel::STRONGBOX),
-            KeyParameter::new(
-                KeyParameterValue::EcCurve(EcCurve::P_224),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::STRONGBOX),
-            KeyParameter::new(
-                KeyParameterValue::EcCurve(EcCurve::P_384),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::EcCurve(EcCurve::P_521),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::RSAPublicExponent(3),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::IncludeUniqueID,
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(KeyParameterValue::BootLoaderOnly, SecurityLevel::STRONGBOX),
-            KeyParameter::new(KeyParameterValue::RollbackResistance, SecurityLevel::STRONGBOX),
-            KeyParameter::new(
-                KeyParameterValue::ActiveDateTime(1234567890),
-                SecurityLevel::STRONGBOX,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::OriginationExpireDateTime(1234567890),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::UsageExpireDateTime(1234567890),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::MinSecondsBetweenOps(1234567890),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::MaxUsesPerBoot(1234567890),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(KeyParameterValue::UserID(1), SecurityLevel::STRONGBOX),
-            KeyParameter::new(
-                KeyParameterValue::NoAuthRequired,
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::PASSWORD),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(KeyParameterValue::AuthTimeout(1234567890), SecurityLevel::SOFTWARE),
-            KeyParameter::new(KeyParameterValue::AllowWhileOnBody, SecurityLevel::SOFTWARE),
-            KeyParameter::new(
-                KeyParameterValue::TrustedUserPresenceRequired,
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::TrustedConfirmationRequired,
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::UnlockedDeviceRequired,
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::ApplicationID(vec![1u8, 2u8, 3u8, 4u8]),
-                SecurityLevel::SOFTWARE,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::ApplicationData(vec![4u8, 3u8, 2u8, 1u8]),
-                SecurityLevel::SOFTWARE,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::CreationDateTime(12345677890),
-                SecurityLevel::SOFTWARE,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::RootOfTrust(vec![3u8, 2u8, 1u8, 4u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(KeyParameterValue::OSVersion(1), SecurityLevel::TRUSTED_ENVIRONMENT),
-            KeyParameter::new(KeyParameterValue::OSPatchLevel(2), SecurityLevel::SOFTWARE),
-            KeyParameter::new(
-                KeyParameterValue::UniqueID(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::SOFTWARE,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationChallenge(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationApplicationID(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdBrand(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdDevice(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdProduct(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdSerial(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdIMEI(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdSecondIMEI(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdMEID(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdManufacturer(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AttestationIdModel(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::VendorPatchLevel(3),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::BootPatchLevel(4),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::AssociatedData(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::Nonce(vec![4u8, 3u8, 1u8, 2u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::MacLength(256),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::ResetSinceIdRotation,
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-            KeyParameter::new(
-                KeyParameterValue::ConfirmationToken(vec![5u8, 5u8, 5u8, 5u8]),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ),
-        ];
-        if let Some(value) = max_usage_count {
-            params.push(KeyParameter::new(
-                KeyParameterValue::UsageCountLimit(value),
-                SecurityLevel::SOFTWARE,
-            ));
-        }
-
-        for sid in user_secure_ids.iter() {
-            params.push(KeyParameter::new(
-                KeyParameterValue::UserSecureID(*sid),
-                SecurityLevel::STRONGBOX,
-            ));
-        }
-        params
-    }
-
-    pub fn make_test_key_entry(
-        db: &mut KeystoreDB,
-        domain: Domain,
-        namespace: i64,
-        alias: &str,
-        max_usage_count: Option<i32>,
-    ) -> Result<KeyIdGuard> {
-        make_test_key_entry_with_sids(db, domain, namespace, alias, max_usage_count, &[42])
-    }
-
-    pub fn make_test_key_entry_with_sids(
-        db: &mut KeystoreDB,
-        domain: Domain,
-        namespace: i64,
-        alias: &str,
-        max_usage_count: Option<i32>,
-        sids: &[i64],
-    ) -> Result<KeyIdGuard> {
-        let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
-        let mut blob_metadata = BlobMetaData::new();
-        blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
-        blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
-        blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
-        blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
-        blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-
-        db.set_blob(
-            &key_id,
-            SubComponentType::KEY_BLOB,
-            Some(TEST_KEY_BLOB),
-            Some(&blob_metadata),
-        )?;
-        db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
-        db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
-
-        let params = make_test_params_with_sids(max_usage_count, sids);
-        db.insert_keyparameter(&key_id, &params)?;
-
-        let mut metadata = KeyMetaData::new();
-        metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
-        db.insert_key_metadata(&key_id, &metadata)?;
-        rebind_alias(db, &key_id, alias, domain, namespace)?;
-        Ok(key_id)
-    }
-
-    fn make_test_key_entry_test_vector(key_id: i64, max_usage_count: Option<i32>) -> KeyEntry {
-        let params = make_test_params(max_usage_count);
-
-        let mut blob_metadata = BlobMetaData::new();
-        blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
-        blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
-        blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
-        blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
-        blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-
-        let mut metadata = KeyMetaData::new();
-        metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
-
-        KeyEntry {
-            id: key_id,
-            key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
-            cert: Some(TEST_CERT_BLOB.to_vec()),
-            cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
-            km_uuid: KEYSTORE_UUID,
-            parameters: params,
-            metadata,
-            pure_cert: false,
-        }
-    }
-
-    pub fn make_bootlevel_key_entry(
-        db: &mut KeystoreDB,
-        domain: Domain,
-        namespace: i64,
-        alias: &str,
-        logical_only: bool,
-    ) -> Result<KeyIdGuard> {
-        let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
-        let mut blob_metadata = BlobMetaData::new();
-        if !logical_only {
-            blob_metadata.add(BlobMetaEntry::MaxBootLevel(3));
-        }
-        blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-
-        db.set_blob(
-            &key_id,
-            SubComponentType::KEY_BLOB,
-            Some(TEST_KEY_BLOB),
-            Some(&blob_metadata),
-        )?;
-        db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
-        db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
-
-        let mut params = make_test_params(None);
-        params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE));
-
-        db.insert_keyparameter(&key_id, &params)?;
-
-        let mut metadata = KeyMetaData::new();
-        metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
-        db.insert_key_metadata(&key_id, &metadata)?;
-        rebind_alias(db, &key_id, alias, domain, namespace)?;
-        Ok(key_id)
-    }
-
-    // Creates an app key that is marked as being superencrypted by the given
-    // super key ID and that has the given authentication and unlocked device
-    // parameters. This does not actually superencrypt the key blob.
-    fn make_superencrypted_key_entry(
-        db: &mut KeystoreDB,
-        namespace: i64,
-        alias: &str,
-        requires_authentication: bool,
-        requires_unlocked_device: bool,
-        super_key_id: i64,
-    ) -> Result<KeyIdGuard> {
-        let domain = Domain::APP;
-        let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
-
-        let mut blob_metadata = BlobMetaData::new();
-        blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-        blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
-        db.set_blob(
-            &key_id,
-            SubComponentType::KEY_BLOB,
-            Some(TEST_KEY_BLOB),
-            Some(&blob_metadata),
-        )?;
-
-        let mut params = vec![];
-        if requires_unlocked_device {
-            params.push(KeyParameter::new(
-                KeyParameterValue::UnlockedDeviceRequired,
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ));
-        }
-        if requires_authentication {
-            params.push(KeyParameter::new(
-                KeyParameterValue::UserSecureID(42),
-                SecurityLevel::TRUSTED_ENVIRONMENT,
-            ));
-        }
-        db.insert_keyparameter(&key_id, &params)?;
-
-        let mut metadata = KeyMetaData::new();
-        metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
-        db.insert_key_metadata(&key_id, &metadata)?;
-
-        rebind_alias(db, &key_id, alias, domain, namespace)?;
-        Ok(key_id)
-    }
-
-    fn make_bootlevel_test_key_entry_test_vector(key_id: i64, logical_only: bool) -> KeyEntry {
-        let mut params = make_test_params(None);
-        params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE));
-
-        let mut blob_metadata = BlobMetaData::new();
-        if !logical_only {
-            blob_metadata.add(BlobMetaEntry::MaxBootLevel(3));
-        }
-        blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
-
-        let mut metadata = KeyMetaData::new();
-        metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
-
-        KeyEntry {
-            id: key_id,
-            key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
-            cert: Some(TEST_CERT_BLOB.to_vec()),
-            cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
-            km_uuid: KEYSTORE_UUID,
-            parameters: params,
-            metadata,
-            pure_cert: false,
-        }
-    }
-
-    fn debug_dump_keyentry_table(db: &mut KeystoreDB) -> Result<()> {
-        let mut stmt = db.conn.prepare(
-            "SELECT id, key_type, domain, namespace, alias, state, km_uuid FROM persistent.keyentry;",
-        )?;
-        let rows = stmt.query_map::<(i64, KeyType, i32, i64, String, KeyLifeCycle, Uuid), _, _>(
-            [],
-            |row| {
-                Ok((
-                    row.get(0)?,
-                    row.get(1)?,
-                    row.get(2)?,
-                    row.get(3)?,
-                    row.get(4)?,
-                    row.get(5)?,
-                    row.get(6)?,
-                ))
-            },
-        )?;
-
-        println!("Key entry table rows:");
-        for r in rows {
-            let (id, key_type, domain, namespace, alias, state, km_uuid) = r.unwrap();
-            println!(
-                "    id: {} KeyType: {:?} Domain: {} Namespace: {} Alias: {} State: {:?} KmUuid: {:?}",
-                id, key_type, domain, namespace, alias, state, km_uuid
-            );
-        }
-        Ok(())
-    }
-
-    fn debug_dump_grant_table(db: &mut KeystoreDB) -> Result<()> {
-        let mut stmt = db
-            .conn
-            .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
-        let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>([], |row| {
-            Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
-        })?;
-
-        println!("Grant table rows:");
-        for r in rows {
-            let (id, gt, ki, av) = r.unwrap();
-            println!("    id: {} grantee: {} key_id: {} access_vector: {}", id, gt, ki, av);
-        }
-        Ok(())
-    }
-
-    // Use a custom random number generator that repeats each number once.
-    // This allows us to test repeated elements.
-
-    thread_local! {
-        static RANDOM_COUNTER: RefCell<i64> = const { RefCell::new(0) };
-    }
-
-    fn reset_random() {
-        RANDOM_COUNTER.with(|counter| {
-            *counter.borrow_mut() = 0;
-        })
-    }
-
-    pub fn random() -> i64 {
-        RANDOM_COUNTER.with(|counter| {
-            let result = *counter.borrow() / 2;
-            *counter.borrow_mut() += 1;
-            result
-        })
-    }
-
-    #[test]
-    fn test_unbind_keys_for_user() -> Result<()> {
-        let mut db = new_test_db()?;
-        db.unbind_keys_for_user(1)?;
-
-        make_test_key_entry(&mut db, Domain::APP, 210000, TEST_ALIAS, None)?;
-        make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?;
-        db.unbind_keys_for_user(2)?;
-
-        assert_eq!(1, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len());
-        assert_eq!(0, db.list_past_alias(Domain::APP, 210000, KeyType::Client, None)?.len());
-
-        db.unbind_keys_for_user(1)?;
-        assert_eq!(0, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len());
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_unbind_keys_for_user_removes_superkeys() -> Result<()> {
-        let mut db = new_test_db()?;
-        let super_key = keystore2_crypto::generate_aes256_key()?;
-        let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
-        let (encrypted_super_key, metadata) =
-            SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
-
-        let key_name_enc = SuperKeyType {
-            alias: "test_super_key_1",
-            algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
-            name: "test_super_key_1",
-        };
-
-        let key_name_nonenc = SuperKeyType {
-            alias: "test_super_key_2",
-            algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
-            name: "test_super_key_2",
-        };
-
-        // Install two super keys.
-        db.store_super_key(
-            1,
-            &key_name_nonenc,
-            &super_key,
-            &BlobMetaData::new(),
-            &KeyMetaData::new(),
-        )?;
-        db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?;
-
-        // Check that both can be found in the database.
-        assert!(db.load_super_key(&key_name_enc, 1)?.is_some());
-        assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some());
-
-        // Install the same keys for a different user.
-        db.store_super_key(
-            2,
-            &key_name_nonenc,
-            &super_key,
-            &BlobMetaData::new(),
-            &KeyMetaData::new(),
-        )?;
-        db.store_super_key(2, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?;
-
-        // Check that the second pair of keys can be found in the database.
-        assert!(db.load_super_key(&key_name_enc, 2)?.is_some());
-        assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some());
-
-        // Delete all keys for user 1.
-        db.unbind_keys_for_user(1)?;
-
-        // All of user 1's keys should be gone.
-        assert!(db.load_super_key(&key_name_enc, 1)?.is_none());
-        assert!(db.load_super_key(&key_name_nonenc, 1)?.is_none());
-
-        // User 2's keys should not have been touched.
-        assert!(db.load_super_key(&key_name_enc, 2)?.is_some());
-        assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some());
-
-        Ok(())
-    }
-
-    fn app_key_exists(db: &mut KeystoreDB, nspace: i64, alias: &str) -> Result<bool> {
-        db.key_exists(Domain::APP, nspace, alias, KeyType::Client)
-    }
-
-    // Tests the unbind_auth_bound_keys_for_user() function.
-    #[test]
-    fn test_unbind_auth_bound_keys_for_user() -> Result<()> {
-        let mut db = new_test_db()?;
-        let user_id = 1;
-        let nspace: i64 = (user_id * AID_USER_OFFSET).into();
-        let other_user_id = 2;
-        let other_user_nspace: i64 = (other_user_id * AID_USER_OFFSET).into();
-        let super_key_type = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY;
-
-        // Create a superencryption key.
-        let super_key = keystore2_crypto::generate_aes256_key()?;
-        let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
-        let (encrypted_super_key, blob_metadata) =
-            SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
-        db.store_super_key(
-            user_id,
-            super_key_type,
-            &encrypted_super_key,
-            &blob_metadata,
-            &KeyMetaData::new(),
-        )?;
-        let super_key_id = db.load_super_key(super_key_type, user_id)?.unwrap().0 .0;
-
-        // Store 4 superencrypted app keys, one for each possible combination of
-        // (authentication required, unlocked device required).
-        make_superencrypted_key_entry(&mut db, nspace, "noauth_noud", false, false, super_key_id)?;
-        make_superencrypted_key_entry(&mut db, nspace, "noauth_ud", false, true, super_key_id)?;
-        make_superencrypted_key_entry(&mut db, nspace, "auth_noud", true, false, super_key_id)?;
-        make_superencrypted_key_entry(&mut db, nspace, "auth_ud", true, true, super_key_id)?;
-        assert!(app_key_exists(&mut db, nspace, "noauth_noud")?);
-        assert!(app_key_exists(&mut db, nspace, "noauth_ud")?);
-        assert!(app_key_exists(&mut db, nspace, "auth_noud")?);
-        assert!(app_key_exists(&mut db, nspace, "auth_ud")?);
-
-        // Also store a key for a different user that requires authentication.
-        make_superencrypted_key_entry(
-            &mut db,
-            other_user_nspace,
-            "auth_ud",
-            true,
-            true,
-            super_key_id,
-        )?;
-
-        db.unbind_auth_bound_keys_for_user(user_id)?;
-
-        // Verify that only the user's app keys that require authentication were
-        // deleted. Keys that require an unlocked device but not authentication
-        // should *not* have been deleted, nor should the super key have been
-        // deleted, nor should other users' keys have been deleted.
-        assert!(db.load_super_key(super_key_type, user_id)?.is_some());
-        assert!(app_key_exists(&mut db, nspace, "noauth_noud")?);
-        assert!(app_key_exists(&mut db, nspace, "noauth_ud")?);
-        assert!(!app_key_exists(&mut db, nspace, "auth_noud")?);
-        assert!(!app_key_exists(&mut db, nspace, "auth_ud")?);
-        assert!(app_key_exists(&mut db, other_user_nspace, "auth_ud")?);
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_store_super_key() -> Result<()> {
-        let mut db = new_test_db()?;
-        let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
-        let super_key = keystore2_crypto::generate_aes256_key()?;
-        let secret_bytes = b"keystore2 is great.";
-        let (encrypted_secret, iv, tag) =
-            keystore2_crypto::aes_gcm_encrypt(secret_bytes, &super_key)?;
-
-        let (encrypted_super_key, metadata) =
-            SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
-        db.store_super_key(
-            1,
-            &USER_AFTER_FIRST_UNLOCK_SUPER_KEY,
-            &encrypted_super_key,
-            &metadata,
-            &KeyMetaData::new(),
-        )?;
-
-        // Check if super key exists.
-        assert!(db.key_exists(
-            Domain::APP,
-            1,
-            USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias,
-            KeyType::Super
-        )?);
-
-        let (_, key_entry) = db.load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, 1)?.unwrap();
-        let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry(
-            USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm,
-            key_entry,
-            &pw,
-            None,
-        )?;
-
-        let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?;
-        assert_eq!(secret_bytes, &*decrypted_secret_bytes);
-
-        Ok(())
-    }
-
-    fn get_valid_statsd_storage_types() -> Vec<MetricsStorage> {
-        vec![
-            MetricsStorage::KEY_ENTRY,
-            MetricsStorage::KEY_ENTRY_ID_INDEX,
-            MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX,
-            MetricsStorage::BLOB_ENTRY,
-            MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX,
-            MetricsStorage::KEY_PARAMETER,
-            MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX,
-            MetricsStorage::KEY_METADATA,
-            MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX,
-            MetricsStorage::GRANT,
-            MetricsStorage::AUTH_TOKEN,
-            MetricsStorage::BLOB_METADATA,
-            MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX,
-        ]
-    }
-
-    /// Perform a simple check to ensure that we can query all the storage types
-    /// that are supported by the DB. Check for reasonable values.
-    #[test]
-    fn test_query_all_valid_table_sizes() -> Result<()> {
-        const PAGE_SIZE: i32 = 4096;
-
-        let mut db = new_test_db()?;
-
-        for t in get_valid_statsd_storage_types() {
-            let stat = db.get_storage_stat(t)?;
-            // AuthToken can be less than a page since it's in a btree, not sqlite
-            // TODO(b/187474736) stop using if-let here
-            if let MetricsStorage::AUTH_TOKEN = t {
-            } else {
-                assert!(stat.size >= PAGE_SIZE);
-            }
-            assert!(stat.size >= stat.unused_size);
-        }
-
-        Ok(())
-    }
-
-    fn get_storage_stats_map(db: &mut KeystoreDB) -> BTreeMap<i32, StorageStats> {
-        get_valid_statsd_storage_types()
-            .into_iter()
-            .map(|t| (t.0, db.get_storage_stat(t).unwrap()))
-            .collect()
-    }
-
-    fn assert_storage_increased(
-        db: &mut KeystoreDB,
-        increased_storage_types: Vec<MetricsStorage>,
-        baseline: &mut BTreeMap<i32, StorageStats>,
-    ) {
-        for storage in increased_storage_types {
-            // Verify the expected storage increased.
-            let new = db.get_storage_stat(storage).unwrap();
-            let old = &baseline[&storage.0];
-            assert!(new.size >= old.size, "{}: {} >= {}", storage.0, new.size, old.size);
-            assert!(
-                new.unused_size <= old.unused_size,
-                "{}: {} <= {}",
-                storage.0,
-                new.unused_size,
-                old.unused_size
-            );
-
-            // Update the baseline with the new value so that it succeeds in the
-            // later comparison.
-            baseline.insert(storage.0, new);
-        }
-
-        // Get an updated map of the storage and verify there were no unexpected changes.
-        let updated_stats = get_storage_stats_map(db);
-        assert_eq!(updated_stats.len(), baseline.len());
-
-        for &k in baseline.keys() {
-            let stringify = |map: &BTreeMap<i32, StorageStats>| -> String {
-                let mut s = String::new();
-                for &k in map.keys() {
-                    writeln!(&mut s, "  {}: {}, {}", &k, map[&k].size, map[&k].unused_size)
-                        .expect("string concat failed");
-                }
-                s
-            };
-
-            assert!(
-                updated_stats[&k].size == baseline[&k].size
-                    && updated_stats[&k].unused_size == baseline[&k].unused_size,
-                "updated_stats:\n{}\nbaseline:\n{}",
-                stringify(&updated_stats),
-                stringify(baseline)
-            );
-        }
-    }
-
-    #[test]
-    fn test_verify_key_table_size_reporting() -> Result<()> {
-        let mut db = new_test_db()?;
-        let mut working_stats = get_storage_stats_map(&mut db);
-
-        let key_id = create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
-        assert_storage_increased(
-            &mut db,
-            vec![
-                MetricsStorage::KEY_ENTRY,
-                MetricsStorage::KEY_ENTRY_ID_INDEX,
-                MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX,
-            ],
-            &mut working_stats,
-        );
-
-        let mut blob_metadata = BlobMetaData::new();
-        blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
-        db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), None)?;
-        assert_storage_increased(
-            &mut db,
-            vec![
-                MetricsStorage::BLOB_ENTRY,
-                MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX,
-                MetricsStorage::BLOB_METADATA,
-                MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX,
-            ],
-            &mut working_stats,
-        );
-
-        let params = make_test_params(None);
-        db.insert_keyparameter(&key_id, &params)?;
-        assert_storage_increased(
-            &mut db,
-            vec![MetricsStorage::KEY_PARAMETER, MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX],
-            &mut working_stats,
-        );
-
-        let mut metadata = KeyMetaData::new();
-        metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
-        db.insert_key_metadata(&key_id, &metadata)?;
-        assert_storage_increased(
-            &mut db,
-            vec![MetricsStorage::KEY_METADATA, MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX],
-            &mut working_stats,
-        );
-
-        let mut sum = 0;
-        for stat in working_stats.values() {
-            sum += stat.size;
-        }
-        let total = db.get_storage_stat(MetricsStorage::DATABASE)?.size;
-        assert!(sum <= total, "Expected sum <= total. sum: {}, total: {}", sum, total);
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_verify_auth_table_size_reporting() -> Result<()> {
-        let mut db = new_test_db()?;
-        let mut working_stats = get_storage_stats_map(&mut db);
-        db.insert_auth_token(&HardwareAuthToken {
-            challenge: 123,
-            userId: 456,
-            authenticatorId: 789,
-            authenticatorType: kmhw_authenticator_type::ANY,
-            timestamp: Timestamp { milliSeconds: 10 },
-            mac: b"mac".to_vec(),
-        });
-        assert_storage_increased(&mut db, vec![MetricsStorage::AUTH_TOKEN], &mut working_stats);
-        Ok(())
-    }
-
-    #[test]
-    fn test_verify_grant_table_size_reporting() -> Result<()> {
-        const OWNER: i64 = 1;
-        let mut db = new_test_db()?;
-        make_test_key_entry(&mut db, Domain::APP, OWNER, TEST_ALIAS, None)?;
-
-        let mut working_stats = get_storage_stats_map(&mut db);
-        db.grant(
-            &KeyDescriptor {
-                domain: Domain::APP,
-                nspace: 0,
-                alias: Some(TEST_ALIAS.to_string()),
-                blob: None,
-            },
-            OWNER as u32,
-            123,
-            key_perm_set![KeyPerm::Use],
-            |_, _| Ok(()),
-        )?;
-
-        assert_storage_increased(&mut db, vec![MetricsStorage::GRANT], &mut working_stats);
-
-        Ok(())
-    }
-
-    #[test]
-    fn find_auth_token_entry_returns_latest() -> Result<()> {
-        let mut db = new_test_db()?;
-        db.insert_auth_token(&HardwareAuthToken {
-            challenge: 123,
-            userId: 456,
-            authenticatorId: 789,
-            authenticatorType: kmhw_authenticator_type::ANY,
-            timestamp: Timestamp { milliSeconds: 10 },
-            mac: b"mac0".to_vec(),
-        });
-        std::thread::sleep(std::time::Duration::from_millis(1));
-        db.insert_auth_token(&HardwareAuthToken {
-            challenge: 123,
-            userId: 457,
-            authenticatorId: 789,
-            authenticatorType: kmhw_authenticator_type::ANY,
-            timestamp: Timestamp { milliSeconds: 12 },
-            mac: b"mac1".to_vec(),
-        });
-        std::thread::sleep(std::time::Duration::from_millis(1));
-        db.insert_auth_token(&HardwareAuthToken {
-            challenge: 123,
-            userId: 458,
-            authenticatorId: 789,
-            authenticatorType: kmhw_authenticator_type::ANY,
-            timestamp: Timestamp { milliSeconds: 3 },
-            mac: b"mac2".to_vec(),
-        });
-        // All three entries are in the database
-        assert_eq!(db.perboot.auth_tokens_len(), 3);
-        // It selected the most recent timestamp
-        assert_eq!(db.find_auth_token_entry(|_| true).unwrap().auth_token.mac, b"mac2".to_vec());
-        Ok(())
-    }
-
-    #[test]
-    fn test_load_key_descriptor() -> Result<()> {
-        let mut db = new_test_db()?;
-        let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)?.0;
-
-        let key = db.load_key_descriptor(key_id)?.unwrap();
-
-        assert_eq!(key.domain, Domain::APP);
-        assert_eq!(key.nspace, 1);
-        assert_eq!(key.alias, Some(TEST_ALIAS.to_string()));
-
-        // No such id
-        assert_eq!(db.load_key_descriptor(key_id + 1)?, None);
-        Ok(())
-    }
-
-    #[test]
-    fn test_get_list_app_uids_for_sid() -> Result<()> {
-        let uid: i32 = 1;
-        let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64);
-        let first_sid = 667;
-        let second_sid = 669;
-        let first_app_id: i64 = 123 + uid_offset;
-        let second_app_id: i64 = 456 + uid_offset;
-        let third_app_id: i64 = 789 + uid_offset;
-        let unrelated_app_id: i64 = 1011 + uid_offset;
-        let mut db = new_test_db()?;
-        make_test_key_entry_with_sids(
-            &mut db,
-            Domain::APP,
-            first_app_id,
-            TEST_ALIAS,
-            None,
-            &[first_sid],
-        )
-        .context("test_get_list_app_uids_for_sid")?;
-        make_test_key_entry_with_sids(
-            &mut db,
-            Domain::APP,
-            second_app_id,
-            "alias2",
-            None,
-            &[first_sid],
-        )
-        .context("test_get_list_app_uids_for_sid")?;
-        make_test_key_entry_with_sids(
-            &mut db,
-            Domain::APP,
-            second_app_id,
-            TEST_ALIAS,
-            None,
-            &[second_sid],
-        )
-        .context("test_get_list_app_uids_for_sid")?;
-        make_test_key_entry_with_sids(
-            &mut db,
-            Domain::APP,
-            third_app_id,
-            "alias3",
-            None,
-            &[second_sid],
-        )
-        .context("test_get_list_app_uids_for_sid")?;
-        make_test_key_entry_with_sids(
-            &mut db,
-            Domain::APP,
-            unrelated_app_id,
-            TEST_ALIAS,
-            None,
-            &[],
-        )
-        .context("test_get_list_app_uids_for_sid")?;
-
-        let mut first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?;
-        first_sid_apps.sort();
-        assert_eq!(first_sid_apps, vec![first_app_id, second_app_id]);
-        let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?;
-        second_sid_apps.sort();
-        assert_eq!(second_sid_apps, vec![second_app_id, third_app_id]);
-        Ok(())
-    }
-
-    #[test]
-    fn test_get_list_app_uids_with_multiple_sids() -> Result<()> {
-        let uid: i32 = 1;
-        let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64);
-        let first_sid = 667;
-        let second_sid = 669;
-        let third_sid = 772;
-        let first_app_id: i64 = 123 + uid_offset;
-        let second_app_id: i64 = 456 + uid_offset;
-        let mut db = new_test_db()?;
-        make_test_key_entry_with_sids(
-            &mut db,
-            Domain::APP,
-            first_app_id,
-            TEST_ALIAS,
-            None,
-            &[first_sid, second_sid],
-        )
-        .context("test_get_list_app_uids_for_sid")?;
-        make_test_key_entry_with_sids(
-            &mut db,
-            Domain::APP,
-            second_app_id,
-            "alias2",
-            None,
-            &[second_sid, third_sid],
-        )
-        .context("test_get_list_app_uids_for_sid")?;
-
-        let first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?;
-        assert_eq!(first_sid_apps, vec![first_app_id]);
-
-        let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?;
-        second_sid_apps.sort();
-        assert_eq!(second_sid_apps, vec![first_app_id, second_app_id]);
-
-        let third_sid_apps = db.get_app_uids_affected_by_sid(uid, third_sid)?;
-        assert_eq!(third_sid_apps, vec![second_app_id]);
-        Ok(())
+    /// Retrieve a database PRAGMA config value.
+    pub fn pragma<T: FromSql>(&mut self, name: &str) -> Result<T> {
+        self.conn
+            .query_row(&format!("PRAGMA persistent.{name}"), (), |row| row.get(0))
+            .context(format!("failed to read pragma {name}"))
     }
 }
diff --git a/keystore2/src/database/perboot.rs b/keystore2/src/database/perboot.rs
index 4727015..a1890a6 100644
--- a/keystore2/src/database/perboot.rs
+++ b/keystore2/src/database/perboot.rs
@@ -19,9 +19,9 @@
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
 };
-use lazy_static::lazy_static;
 use std::collections::HashSet;
 use std::sync::Arc;
+use std::sync::LazyLock;
 use std::sync::RwLock;
 
 #[derive(PartialEq, PartialOrd, Ord, Eq, Hash)]
@@ -70,11 +70,9 @@
     auth_tokens: RwLock<HashSet<AuthTokenEntryWrap>>,
 }
 
-lazy_static! {
-    /// The global instance of the perboot DB. Located here rather than in globals
-    /// in order to restrict access to the database module.
-    pub static ref PERBOOT_DB: Arc<PerbootDB> = Arc::new(PerbootDB::new());
-}
+/// The global instance of the perboot DB. Located here rather than in globals
+/// in order to restrict access to the database module.
+pub static PERBOOT_DB: LazyLock<Arc<PerbootDB>> = LazyLock::new(|| Arc::new(PerbootDB::new()));
 
 impl PerbootDB {
     /// Construct a new perboot database. Currently just uses default values.
diff --git a/keystore2/src/database/tests.rs b/keystore2/src/database/tests.rs
new file mode 100644
index 0000000..4ada694
--- /dev/null
+++ b/keystore2/src/database/tests.rs
@@ -0,0 +1,2875 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Database tests.
+
+use super::*;
+use crate::key_parameter::{
+    Algorithm, BlockMode, Digest, EcCurve, HardwareAuthenticatorType, KeyOrigin, KeyParameter,
+    KeyParameterValue, KeyPurpose, PaddingMode, SecurityLevel,
+};
+use crate::key_perm_set;
+use crate::permission::{KeyPerm, KeyPermSet};
+use crate::super_key::{SuperKeyManager, USER_AFTER_FIRST_UNLOCK_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType};
+use keystore2_test_utils::TempDir;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+    HardwareAuthToken::HardwareAuthToken,
+    HardwareAuthenticatorType::HardwareAuthenticatorType as kmhw_authenticator_type,
+};
+use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
+    Timestamp::Timestamp,
+};
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::fmt::Write;
+use std::sync::atomic::{AtomicU8, Ordering};
+use std::sync::Arc;
+use std::thread;
+use std::time::{Duration, SystemTime};
+use crate::utils::AesGcm;
+#[cfg(disabled)]
+use std::time::Instant;
+
+pub fn new_test_db() -> Result<KeystoreDB> {
+    new_test_db_at("file::memory:")
+}
+
+fn new_test_db_at(path: &str) -> Result<KeystoreDB> {
+    let conn = KeystoreDB::make_connection(path)?;
+
+    let mut db = KeystoreDB { conn, gc: None, perboot: Arc::new(perboot::PerbootDB::new()) };
+    db.with_transaction(Immediate("TX_new_test_db"), |tx| {
+        KeystoreDB::init_tables(tx).context("Failed to initialize tables.").no_gc()
+    })?;
+    Ok(db)
+}
+
+fn rebind_alias(
+    db: &mut KeystoreDB,
+    newid: &KeyIdGuard,
+    alias: &str,
+    domain: Domain,
+    namespace: i64,
+) -> Result<bool> {
+    db.with_transaction(Immediate("TX_rebind_alias"), |tx| {
+        KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace, KeyType::Client).no_gc()
+    })
+    .context(ks_err!())
+}
+
+#[test]
+fn datetime() -> Result<()> {
+    let conn = Connection::open_in_memory()?;
+    conn.execute("CREATE TABLE test (ts DATETIME);", [])?;
+    let now = SystemTime::now();
+    let duration = Duration::from_secs(1000);
+    let then = now.checked_sub(duration).unwrap();
+    let soon = now.checked_add(duration).unwrap();
+    conn.execute(
+        "INSERT INTO test (ts) VALUES (?), (?), (?);",
+        params![DateTime::try_from(now)?, DateTime::try_from(then)?, DateTime::try_from(soon)?],
+    )?;
+    let mut stmt = conn.prepare("SELECT ts FROM test ORDER BY ts ASC;")?;
+    let mut rows = stmt.query([])?;
+    assert_eq!(DateTime::try_from(then)?, rows.next()?.unwrap().get(0)?);
+    assert_eq!(DateTime::try_from(now)?, rows.next()?.unwrap().get(0)?);
+    assert_eq!(DateTime::try_from(soon)?, rows.next()?.unwrap().get(0)?);
+    assert!(rows.next()?.is_none());
+    assert!(DateTime::try_from(then)? < DateTime::try_from(now)?);
+    assert!(DateTime::try_from(then)? < DateTime::try_from(soon)?);
+    assert!(DateTime::try_from(now)? < DateTime::try_from(soon)?);
+    Ok(())
+}
+
+// Ensure that we're using the "injected" random function, not the real one.
+#[test]
+fn test_mocked_random() {
+    let rand1 = random();
+    let rand2 = random();
+    let rand3 = random();
+    if rand1 == rand2 {
+        assert_eq!(rand2 + 1, rand3);
+    } else {
+        assert_eq!(rand1 + 1, rand2);
+        assert_eq!(rand2, rand3);
+    }
+}
+
+// Test that we have the correct tables.
+#[test]
+fn test_tables() -> Result<()> {
+    let db = new_test_db()?;
+    let tables = db
+        .conn
+        .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
+        .query_map(params![], |row| row.get(0))?
+        .collect::<rusqlite::Result<Vec<String>>>()?;
+    assert_eq!(tables.len(), 6);
+    assert_eq!(tables[0], "blobentry");
+    assert_eq!(tables[1], "blobmetadata");
+    assert_eq!(tables[2], "grant");
+    assert_eq!(tables[3], "keyentry");
+    assert_eq!(tables[4], "keymetadata");
+    assert_eq!(tables[5], "keyparameter");
+    Ok(())
+}
+
+#[test]
+fn test_auth_token_table_invariant() -> Result<()> {
+    let mut db = new_test_db()?;
+    let auth_token1 = HardwareAuthToken {
+        challenge: i64::MAX,
+        userId: 200,
+        authenticatorId: 200,
+        authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
+        timestamp: Timestamp { milliSeconds: 500 },
+        mac: String::from("mac").into_bytes(),
+    };
+    db.insert_auth_token(&auth_token1);
+    let auth_tokens_returned = get_auth_tokens(&db);
+    assert_eq!(auth_tokens_returned.len(), 1);
+
+    // insert another auth token with the same values for the columns in the UNIQUE constraint
+    // of the auth token table and different value for timestamp
+    let auth_token2 = HardwareAuthToken {
+        challenge: i64::MAX,
+        userId: 200,
+        authenticatorId: 200,
+        authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
+        timestamp: Timestamp { milliSeconds: 600 },
+        mac: String::from("mac").into_bytes(),
+    };
+
+    db.insert_auth_token(&auth_token2);
+    let mut auth_tokens_returned = get_auth_tokens(&db);
+    assert_eq!(auth_tokens_returned.len(), 1);
+
+    if let Some(auth_token) = auth_tokens_returned.pop() {
+        assert_eq!(auth_token.auth_token.timestamp.milliSeconds, 600);
+    }
+
+    // insert another auth token with the different values for the columns in the UNIQUE
+    // constraint of the auth token table
+    let auth_token3 = HardwareAuthToken {
+        challenge: i64::MAX,
+        userId: 201,
+        authenticatorId: 200,
+        authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0),
+        timestamp: Timestamp { milliSeconds: 600 },
+        mac: String::from("mac").into_bytes(),
+    };
+
+    db.insert_auth_token(&auth_token3);
+    let auth_tokens_returned = get_auth_tokens(&db);
+    assert_eq!(auth_tokens_returned.len(), 2);
+
+    Ok(())
+}
+
+// utility function for test_auth_token_table_invariant()
+fn get_auth_tokens(db: &KeystoreDB) -> Vec<AuthTokenEntry> {
+    db.perboot.get_all_auth_token_entries()
+}
+
+fn create_key_entry(
+    db: &mut KeystoreDB,
+    domain: &Domain,
+    namespace: &i64,
+    key_type: KeyType,
+    km_uuid: &Uuid,
+) -> Result<KeyIdGuard> {
+    db.with_transaction(Immediate("TX_create_key_entry"), |tx| {
+        KeystoreDB::create_key_entry_internal(tx, domain, namespace, key_type, km_uuid).no_gc()
+    })
+}
+
+#[test]
+fn test_persistence_for_files() -> Result<()> {
+    let temp_dir = TempDir::new("persistent_db_test")?;
+    let mut db = KeystoreDB::new(temp_dir.path(), None)?;
+
+    create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?;
+    let entries = get_keyentry(&db)?;
+    assert_eq!(entries.len(), 1);
+
+    let db = KeystoreDB::new(temp_dir.path(), None)?;
+
+    let entries_new = get_keyentry(&db)?;
+    assert_eq!(entries, entries_new);
+    Ok(())
+}
+
+#[test]
+fn test_create_key_entry() -> Result<()> {
+    fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>, Uuid) {
+        (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref(), ke.km_uuid.unwrap())
+    }
+
+    let mut db = new_test_db()?;
+
+    create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?;
+    create_key_entry(&mut db, &Domain::SELINUX, &101, KeyType::Client, &KEYSTORE_UUID)?;
+
+    let entries = get_keyentry(&db)?;
+    assert_eq!(entries.len(), 2);
+    assert_eq!(extractor(&entries[0]), (Domain::APP, 100, None, KEYSTORE_UUID));
+    assert_eq!(extractor(&entries[1]), (Domain::SELINUX, 101, None, KEYSTORE_UUID));
+
+    // Test that we must pass in a valid Domain.
+    check_result_is_error_containing_string(
+        create_key_entry(&mut db, &Domain::GRANT, &102, KeyType::Client, &KEYSTORE_UUID),
+        &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
+    );
+    check_result_is_error_containing_string(
+        create_key_entry(&mut db, &Domain::BLOB, &103, KeyType::Client, &KEYSTORE_UUID),
+        &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
+    );
+    check_result_is_error_containing_string(
+        create_key_entry(&mut db, &Domain::KEY_ID, &104, KeyType::Client, &KEYSTORE_UUID),
+        &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_rebind_alias() -> Result<()> {
+    fn extractor(ke: &KeyEntryRow) -> (Option<Domain>, Option<i64>, Option<&str>, Option<Uuid>) {
+        (ke.domain, ke.namespace, ke.alias.as_deref(), ke.km_uuid)
+    }
+
+    let mut db = new_test_db()?;
+    create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
+    create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
+    let entries = get_keyentry(&db)?;
+    assert_eq!(entries.len(), 2);
+    assert_eq!(extractor(&entries[0]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)));
+    assert_eq!(extractor(&entries[1]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)));
+
+    // Test that the first call to rebind_alias sets the alias.
+    rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[0].id), "foo", Domain::APP, 42)?;
+    let entries = get_keyentry(&db)?;
+    assert_eq!(entries.len(), 2);
+    assert_eq!(
+        extractor(&entries[0]),
+        (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
+    );
+    assert_eq!(extractor(&entries[1]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)));
+
+    // Test that the second call to rebind_alias also empties the old one.
+    rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[1].id), "foo", Domain::APP, 42)?;
+    let entries = get_keyentry(&db)?;
+    assert_eq!(entries.len(), 2);
+    assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID)));
+    assert_eq!(
+        extractor(&entries[1]),
+        (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
+    );
+
+    // Test that we must pass in a valid Domain.
+    check_result_is_error_containing_string(
+        rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::GRANT, 42),
+        &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT),
+    );
+    check_result_is_error_containing_string(
+        rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::BLOB, 42),
+        &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB),
+    );
+    check_result_is_error_containing_string(
+        rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::KEY_ID, 42),
+        &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID),
+    );
+
+    // Test that we correctly handle setting an alias for something that does not exist.
+    check_result_is_error_containing_string(
+        rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::SELINUX, 42),
+        "Expected to update a single entry but instead updated 0",
+    );
+    // Test that we correctly abort the transaction in this case.
+    let entries = get_keyentry(&db)?;
+    assert_eq!(entries.len(), 2);
+    assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID)));
+    assert_eq!(
+        extractor(&entries[1]),
+        (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID))
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_grant_ungrant() -> Result<()> {
+    const CALLER_UID: u32 = 15;
+    const GRANTEE_UID: u32 = 12;
+    const SELINUX_NAMESPACE: i64 = 7;
+
+    let mut db = new_test_db()?;
+    db.conn.execute(
+        "INSERT INTO persistent.keyentry (id, key_type, domain, namespace, alias, state, km_uuid)
+                VALUES (1, 0, 0, 15, 'key', 1, ?), (2, 0, 2, 7, 'yek', 1, ?);",
+        params![KEYSTORE_UUID, KEYSTORE_UUID],
+    )?;
+    let app_key = KeyDescriptor {
+        domain: super::Domain::APP,
+        nspace: 0,
+        alias: Some("key".to_string()),
+        blob: None,
+    };
+    const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo];
+    const PVEC2: KeyPermSet = key_perm_set![KeyPerm::Use];
+
+    // Reset totally predictable random number generator in case we
+    // are not the first test running on this thread.
+    reset_random();
+    let next_random = 0i64;
+
+    let app_granted_key = db
+        .grant(&app_key, CALLER_UID, GRANTEE_UID, PVEC1, |k, a| {
+            assert_eq!(*a, PVEC1);
+            assert_eq!(
+                *k,
+                KeyDescriptor {
+                    domain: super::Domain::APP,
+                    // namespace must be set to the caller_uid.
+                    nspace: CALLER_UID as i64,
+                    alias: Some("key".to_string()),
+                    blob: None,
+                }
+            );
+            Ok(())
+        })
+        .unwrap();
+
+    assert_eq!(
+        app_granted_key,
+        KeyDescriptor {
+            domain: super::Domain::GRANT,
+            // The grantid is next_random due to the mock random number generator.
+            nspace: next_random,
+            alias: None,
+            blob: None,
+        }
+    );
+
+    let selinux_key = KeyDescriptor {
+        domain: super::Domain::SELINUX,
+        nspace: SELINUX_NAMESPACE,
+        alias: Some("yek".to_string()),
+        blob: None,
+    };
+
+    let selinux_granted_key = db
+        .grant(&selinux_key, CALLER_UID, 12, PVEC1, |k, a| {
+            assert_eq!(*a, PVEC1);
+            assert_eq!(
+                *k,
+                KeyDescriptor {
+                    domain: super::Domain::SELINUX,
+                    // namespace must be the supplied SELinux
+                    // namespace.
+                    nspace: SELINUX_NAMESPACE,
+                    alias: Some("yek".to_string()),
+                    blob: None,
+                }
+            );
+            Ok(())
+        })
+        .unwrap();
+
+    assert_eq!(
+        selinux_granted_key,
+        KeyDescriptor {
+            domain: super::Domain::GRANT,
+            // The grantid is next_random + 1 due to the mock random number generator.
+            nspace: next_random + 1,
+            alias: None,
+            blob: None,
+        }
+    );
+
+    // This should update the existing grant with PVEC2.
+    let selinux_granted_key = db
+        .grant(&selinux_key, CALLER_UID, 12, PVEC2, |k, a| {
+            assert_eq!(*a, PVEC2);
+            assert_eq!(
+                *k,
+                KeyDescriptor {
+                    domain: super::Domain::SELINUX,
+                    // namespace must be the supplied SELinux
+                    // namespace.
+                    nspace: SELINUX_NAMESPACE,
+                    alias: Some("yek".to_string()),
+                    blob: None,
+                }
+            );
+            Ok(())
+        })
+        .unwrap();
+
+    assert_eq!(
+        selinux_granted_key,
+        KeyDescriptor {
+            domain: super::Domain::GRANT,
+            // Same grant id as before. The entry was only updated.
+            nspace: next_random + 1,
+            alias: None,
+            blob: None,
+        }
+    );
+
+    {
+        // Limiting scope of stmt, because it borrows db.
+        let mut stmt = db
+            .conn
+            .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
+        let mut rows = stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>([], |row| {
+            Ok((row.get(0)?, row.get(1)?, row.get(2)?, KeyPermSet::from(row.get::<_, i32>(3)?)))
+        })?;
+
+        let r = rows.next().unwrap().unwrap();
+        assert_eq!(r, (next_random, GRANTEE_UID, 1, PVEC1));
+        let r = rows.next().unwrap().unwrap();
+        assert_eq!(r, (next_random + 1, GRANTEE_UID, 2, PVEC2));
+        assert!(rows.next().is_none());
+    }
+
+    debug_dump_keyentry_table(&mut db)?;
+    println!("app_key {:?}", app_key);
+    println!("selinux_key {:?}", selinux_key);
+
+    db.ungrant(&app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
+    db.ungrant(&selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?;
+
+    Ok(())
+}
+
+static TEST_KEY_BLOB: &[u8] = b"my test blob";
+static TEST_CERT_BLOB: &[u8] = b"my test cert";
+static TEST_CERT_CHAIN_BLOB: &[u8] = b"my test cert_chain";
+
+#[test]
+fn test_set_blob() -> Result<()> {
+    let key_id = KEY_ID_LOCK.get(3000);
+    let mut db = new_test_db()?;
+    let mut blob_metadata = BlobMetaData::new();
+    blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+    db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?;
+    db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
+    db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
+    drop(key_id);
+
+    let mut stmt = db.conn.prepare(
+        "SELECT subcomponent_type, keyentryid, blob, id FROM persistent.blobentry
+                ORDER BY subcomponent_type ASC;",
+    )?;
+    let mut rows = stmt.query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>([], |row| {
+        Ok(((row.get(0)?, row.get(1)?, row.get(2)?), row.get(3)?))
+    })?;
+    let (r, id) = rows.next().unwrap().unwrap();
+    assert_eq!(r, (SubComponentType::KEY_BLOB, 3000, TEST_KEY_BLOB.to_vec()));
+    let (r, _) = rows.next().unwrap().unwrap();
+    assert_eq!(r, (SubComponentType::CERT, 3000, TEST_CERT_BLOB.to_vec()));
+    let (r, _) = rows.next().unwrap().unwrap();
+    assert_eq!(r, (SubComponentType::CERT_CHAIN, 3000, TEST_CERT_CHAIN_BLOB.to_vec()));
+
+    drop(rows);
+    drop(stmt);
+
+    assert_eq!(
+        db.with_transaction(Immediate("TX_test"), |tx| {
+            BlobMetaData::load_from_db(id, tx).no_gc()
+        })
+        .expect("Should find blob metadata."),
+        blob_metadata
+    );
+    Ok(())
+}
+
+static TEST_ALIAS: &str = "my super duper key";
+
+#[test]
+fn test_insert_and_load_full_keyentry_domain_app() -> Result<()> {
+    let mut db = new_test_db()?;
+    let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)
+        .context("test_insert_and_load_full_keyentry_domain_app")?
+        .0;
+    let (_key_guard, key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: 0,
+                alias: Some(TEST_ALIAS.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            1,
+            |_k, _av| Ok(()),
+        )
+        .unwrap();
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+    db.unbind_key(
+        &KeyDescriptor {
+            domain: Domain::APP,
+            nspace: 0,
+            alias: Some(TEST_ALIAS.to_string()),
+            blob: None,
+        },
+        KeyType::Client,
+        1,
+        |_, _| Ok(()),
+    )
+    .unwrap();
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: 0,
+                alias: Some(TEST_ALIAS.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::NONE,
+            1,
+            |_k, _av| Ok(()),
+        )
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_insert_and_load_certificate_entry_domain_app() -> Result<()> {
+    let mut db = new_test_db()?;
+
+    db.store_new_certificate(
+        &KeyDescriptor {
+            domain: Domain::APP,
+            nspace: 1,
+            alias: Some(TEST_ALIAS.to_string()),
+            blob: None,
+        },
+        KeyType::Client,
+        TEST_CERT_BLOB,
+        &KEYSTORE_UUID,
+    )
+    .expect("Trying to insert cert.");
+
+    let (_key_guard, mut key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: 1,
+                alias: Some(TEST_ALIAS.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::PUBLIC,
+            1,
+            |_k, _av| Ok(()),
+        )
+        .expect("Trying to read certificate entry.");
+
+    assert!(key_entry.pure_cert());
+    assert!(key_entry.cert().is_none());
+    assert_eq!(key_entry.take_cert_chain(), Some(TEST_CERT_BLOB.to_vec()));
+
+    db.unbind_key(
+        &KeyDescriptor {
+            domain: Domain::APP,
+            nspace: 1,
+            alias: Some(TEST_ALIAS.to_string()),
+            blob: None,
+        },
+        KeyType::Client,
+        1,
+        |_, _| Ok(()),
+    )
+    .unwrap();
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: 1,
+                alias: Some(TEST_ALIAS.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::NONE,
+            1,
+            |_k, _av| Ok(()),
+        )
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_insert_and_load_full_keyentry_domain_selinux() -> Result<()> {
+    let mut db = new_test_db()?;
+    let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None)
+        .context("test_insert_and_load_full_keyentry_domain_selinux")?
+        .0;
+    let (_key_guard, key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::SELINUX,
+                nspace: 1,
+                alias: Some(TEST_ALIAS.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            1,
+            |_k, _av| Ok(()),
+        )
+        .unwrap();
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+    db.unbind_key(
+        &KeyDescriptor {
+            domain: Domain::SELINUX,
+            nspace: 1,
+            alias: Some(TEST_ALIAS.to_string()),
+            blob: None,
+        },
+        KeyType::Client,
+        1,
+        |_, _| Ok(()),
+    )
+    .unwrap();
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::SELINUX,
+                nspace: 1,
+                alias: Some(TEST_ALIAS.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::NONE,
+            1,
+            |_k, _av| Ok(()),
+        )
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_insert_and_load_full_keyentry_domain_key_id() -> Result<()> {
+    let mut db = new_test_db()?;
+    let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None)
+        .context("test_insert_and_load_full_keyentry_domain_key_id")?
+        .0;
+    let (_, key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            1,
+            |_k, _av| Ok(()),
+        )
+        .unwrap();
+
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+    db.unbind_key(
+        &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+        KeyType::Client,
+        1,
+        |_, _| Ok(()),
+    )
+    .unwrap();
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(
+            &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+            KeyType::Client,
+            KeyEntryLoadBits::NONE,
+            1,
+            |_k, _av| Ok(()),
+        )
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_check_and_update_key_usage_count_with_limited_use_key() -> Result<()> {
+    let mut db = new_test_db()?;
+    let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(123))
+        .context("test_check_and_update_key_usage_count_with_limited_use_key")?
+        .0;
+    // Update the usage count of the limited use key.
+    db.check_and_update_key_usage_count(key_id)?;
+
+    let (_key_guard, key_entry) = db.load_key_entry(
+        &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None },
+        KeyType::Client,
+        KeyEntryLoadBits::BOTH,
+        1,
+        |_k, _av| Ok(()),
+    )?;
+
+    // The usage count is decremented now.
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, Some(122)));
+
+    Ok(())
+}
+
+#[test]
+fn test_check_and_update_key_usage_count_with_exhausted_limited_use_key() -> Result<()> {
+    let mut db = new_test_db()?;
+    let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(1))
+        .context("test_check_and_update_key_usage_count_with_exhausted_limited_use_key")?
+        .0;
+    // Update the usage count of the limited use key.
+    db.check_and_update_key_usage_count(key_id).expect(concat!(
+        "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ",
+        "This should succeed."
+    ));
+
+    // Try to update the exhausted limited use key.
+    let e = db.check_and_update_key_usage_count(key_id).expect_err(concat!(
+        "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ",
+        "This should fail."
+    ));
+    assert_eq!(
+        &KsError::Km(ErrorCode::INVALID_KEY_BLOB),
+        e.root_cause().downcast_ref::<KsError>().unwrap()
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_insert_and_load_full_keyentry_from_grant() -> Result<()> {
+    let mut db = new_test_db()?;
+    let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)
+        .context("test_insert_and_load_full_keyentry_from_grant")?
+        .0;
+
+    let granted_key = db
+        .grant(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: 0,
+                alias: Some(TEST_ALIAS.to_string()),
+                blob: None,
+            },
+            1,
+            2,
+            key_perm_set![KeyPerm::Use],
+            |_k, _av| Ok(()),
+        )
+        .unwrap();
+
+    debug_dump_grant_table(&mut db)?;
+
+    let (_key_guard, key_entry) = db
+        .load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| {
+            assert_eq!(Domain::GRANT, k.domain);
+            assert!(av.unwrap().includes(KeyPerm::Use));
+            Ok(())
+        })
+        .unwrap();
+
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+    db.unbind_key(&granted_key, KeyType::Client, 2, |_, _| Ok(())).unwrap();
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::NONE, 2, |_k, _av| Ok(
+            ()
+        ),)
+            .unwrap_err()
+            .root_cause()
+            .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+// This test attempts to load a key by key id while the caller is not the owner
+// but a grant exists for the given key and the caller.
+#[test]
+fn test_insert_and_load_full_keyentry_from_grant_by_key_id() -> Result<()> {
+    let mut db = new_test_db()?;
+    const OWNER_UID: u32 = 1u32;
+    const GRANTEE_UID: u32 = 2u32;
+    const SOMEONE_ELSE_UID: u32 = 3u32;
+    let key_id = make_test_key_entry(&mut db, Domain::APP, OWNER_UID as i64, TEST_ALIAS, None)
+        .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?
+        .0;
+
+    db.grant(
+        &KeyDescriptor {
+            domain: Domain::APP,
+            nspace: 0,
+            alias: Some(TEST_ALIAS.to_string()),
+            blob: None,
+        },
+        OWNER_UID,
+        GRANTEE_UID,
+        key_perm_set![KeyPerm::Use],
+        |_k, _av| Ok(()),
+    )
+    .unwrap();
+
+    debug_dump_grant_table(&mut db)?;
+
+    let id_descriptor =
+        KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, ..Default::default() };
+
+    let (_, key_entry) = db
+        .load_key_entry(
+            &id_descriptor,
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            GRANTEE_UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(OWNER_UID as i64, k.nspace);
+                assert!(av.unwrap().includes(KeyPerm::Use));
+                Ok(())
+            },
+        )
+        .unwrap();
+
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+    let (_, key_entry) = db
+        .load_key_entry(
+            &id_descriptor,
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            SOMEONE_ELSE_UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(OWNER_UID as i64, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap();
+
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+    db.unbind_key(&id_descriptor, KeyType::Client, OWNER_UID, |_, _| Ok(())).unwrap();
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(
+            &id_descriptor,
+            KeyType::Client,
+            KeyEntryLoadBits::NONE,
+            GRANTEE_UID,
+            |_k, _av| Ok(()),
+        )
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+// Creates a key migrates it to a different location and then tries to access it by the old
+// and new location.
+#[test]
+fn test_migrate_key_app_to_app() -> Result<()> {
+    let mut db = new_test_db()?;
+    const SOURCE_UID: u32 = 1u32;
+    const DESTINATION_UID: u32 = 2u32;
+    static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+    static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
+    let key_id_guard =
+        make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+            .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+    let source_descriptor: KeyDescriptor = KeyDescriptor {
+        domain: Domain::APP,
+        nspace: -1,
+        alias: Some(SOURCE_ALIAS.to_string()),
+        blob: None,
+    };
+
+    let destination_descriptor: KeyDescriptor = KeyDescriptor {
+        domain: Domain::APP,
+        nspace: -1,
+        alias: Some(DESTINATION_ALIAS.to_string()),
+        blob: None,
+    };
+
+    let key_id = key_id_guard.id();
+
+    db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok(()))
+        .unwrap();
+
+    let (_, key_entry) = db
+        .load_key_entry(
+            &destination_descriptor,
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            DESTINATION_UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(DESTINATION_UID as i64, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap();
+
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(
+            &source_descriptor,
+            KeyType::Client,
+            KeyEntryLoadBits::NONE,
+            SOURCE_UID,
+            |_k, _av| Ok(()),
+        )
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+// Creates a key migrates it to a different location and then tries to access it by the old
+// and new location.
+#[test]
+fn test_migrate_key_app_to_selinux() -> Result<()> {
+    let mut db = new_test_db()?;
+    const SOURCE_UID: u32 = 1u32;
+    const DESTINATION_UID: u32 = 2u32;
+    const DESTINATION_NAMESPACE: i64 = 1000i64;
+    static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+    static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
+    let key_id_guard =
+        make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+            .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+    let source_descriptor: KeyDescriptor = KeyDescriptor {
+        domain: Domain::APP,
+        nspace: -1,
+        alias: Some(SOURCE_ALIAS.to_string()),
+        blob: None,
+    };
+
+    let destination_descriptor: KeyDescriptor = KeyDescriptor {
+        domain: Domain::SELINUX,
+        nspace: DESTINATION_NAMESPACE,
+        alias: Some(DESTINATION_ALIAS.to_string()),
+        blob: None,
+    };
+
+    let key_id = key_id_guard.id();
+
+    db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok(()))
+        .unwrap();
+
+    let (_, key_entry) = db
+        .load_key_entry(
+            &destination_descriptor,
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            DESTINATION_UID,
+            |k, av| {
+                assert_eq!(Domain::SELINUX, k.domain);
+                assert_eq!(DESTINATION_NAMESPACE, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap();
+
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(
+            &source_descriptor,
+            KeyType::Client,
+            KeyEntryLoadBits::NONE,
+            SOURCE_UID,
+            |_k, _av| Ok(()),
+        )
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+// Creates two keys and tries to migrate the first to the location of the second which
+// is expected to fail.
+#[test]
+fn test_migrate_key_destination_occupied() -> Result<()> {
+    let mut db = new_test_db()?;
+    const SOURCE_UID: u32 = 1u32;
+    const DESTINATION_UID: u32 = 2u32;
+    static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+    static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
+    let key_id_guard =
+        make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+            .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+    make_test_key_entry(&mut db, Domain::APP, DESTINATION_UID as i64, DESTINATION_ALIAS, None)
+        .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+    let destination_descriptor: KeyDescriptor = KeyDescriptor {
+        domain: Domain::APP,
+        nspace: -1,
+        alias: Some(DESTINATION_ALIAS.to_string()),
+        blob: None,
+    };
+
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::INVALID_ARGUMENT)),
+        db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok(
+            ()
+        ))
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_upgrade_0_to_1() {
+    const ALIAS1: &str = "test_upgrade_0_to_1_1";
+    const ALIAS2: &str = "test_upgrade_0_to_1_2";
+    const ALIAS3: &str = "test_upgrade_0_to_1_3";
+    const UID: u32 = 33;
+    let temp_dir = Arc::new(TempDir::new("test_upgrade_0_to_1").unwrap());
+    let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
+    let key_id_untouched1 =
+        make_test_key_entry(&mut db, Domain::APP, UID as i64, ALIAS1, None).unwrap().id();
+    let key_id_untouched2 =
+        make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS2, false).unwrap().id();
+    let key_id_deleted =
+        make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS3, true).unwrap().id();
+
+    let (_, key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(ALIAS1.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(UID as i64, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap();
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None));
+    let (_, key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(ALIAS2.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(UID as i64, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap();
+    assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false));
+    let (_, key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(ALIAS3.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(UID as i64, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap();
+    assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_deleted, true));
+
+    db.with_transaction(Immediate("TX_test"), |tx| KeystoreDB::from_0_to_1(tx).no_gc()).unwrap();
+
+    let (_, key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(ALIAS1.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(UID as i64, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap();
+    assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None));
+    let (_, key_entry) = db
+        .load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(ALIAS2.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(UID as i64, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap();
+    assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false));
+    assert_eq!(
+        Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+        db.load_key_entry(
+            &KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(ALIAS3.to_string()),
+                blob: None,
+            },
+            KeyType::Client,
+            KeyEntryLoadBits::BOTH,
+            UID,
+            |k, av| {
+                assert_eq!(Domain::APP, k.domain);
+                assert_eq!(UID as i64, k.nspace);
+                assert!(av.is_none());
+                Ok(())
+            },
+        )
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+}
+
+static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key";
+
+#[test]
+fn test_insert_and_load_full_keyentry_domain_app_concurrently() -> Result<()> {
+    let handle = {
+        let temp_dir = Arc::new(TempDir::new("id_lock_test")?);
+        let temp_dir_clone = temp_dir.clone();
+        let mut db = KeystoreDB::new(temp_dir.path(), None)?;
+        let key_id = make_test_key_entry(&mut db, Domain::APP, 33, KEY_LOCK_TEST_ALIAS, None)
+            .context("test_insert_and_load_full_keyentry_domain_app")?
+            .0;
+        let (_key_guard, key_entry) = db
+            .load_key_entry(
+                &KeyDescriptor {
+                    domain: Domain::APP,
+                    nspace: 0,
+                    alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
+                    blob: None,
+                },
+                KeyType::Client,
+                KeyEntryLoadBits::BOTH,
+                33,
+                |_k, _av| Ok(()),
+            )
+            .unwrap();
+        assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+        let state = Arc::new(AtomicU8::new(1));
+        let state2 = state.clone();
+
+        // Spawning a second thread that attempts to acquire the key id lock
+        // for the same key as the primary thread. The primary thread then
+        // waits, thereby forcing the secondary thread into the second stage
+        // of acquiring the lock (see KEY ID LOCK 2/2 above).
+        // The test succeeds if the secondary thread observes the transition
+        // of `state` from 1 to 2, despite having a whole second to overtake
+        // the primary thread.
+        let handle = thread::spawn(move || {
+            let temp_dir = temp_dir_clone;
+            let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
+            assert!(db
+                .load_key_entry(
+                    &KeyDescriptor {
+                        domain: Domain::APP,
+                        nspace: 0,
+                        alias: Some(KEY_LOCK_TEST_ALIAS.to_string()),
+                        blob: None,
+                    },
+                    KeyType::Client,
+                    KeyEntryLoadBits::BOTH,
+                    33,
+                    |_k, _av| Ok(()),
+                )
+                .is_ok());
+            // We should only see a 2 here because we can only return
+            // from load_key_entry when the `_key_guard` expires,
+            // which happens at the end of the scope.
+            assert_eq!(2, state2.load(Ordering::Relaxed));
+        });
+
+        thread::sleep(std::time::Duration::from_millis(1000));
+
+        assert_eq!(Ok(1), state.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed));
+
+        // Return the handle from this scope so we can join with the
+        // secondary thread after the key id lock has expired.
+        handle
+        // This is where the `_key_guard` goes out of scope,
+        // which is the reason for concurrent load_key_entry on the same key
+        // to unblock.
+    };
+    // Join with the secondary thread and unwrap, to propagate failing asserts to the
+    // main test thread. We will not see failing asserts in secondary threads otherwise.
+    handle.join().unwrap();
+    Ok(())
+}
+
+#[test]
+fn test_database_busy_error_code() {
+    let temp_dir =
+        TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir.");
+
+    let mut db1 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database1.");
+    let mut db2 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database2.");
+
+    let _tx1 = db1
+        .conn
+        .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
+        .expect("Failed to create first transaction.");
+
+    let error = db2
+        .conn
+        .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)
+        .context("Transaction begin failed.")
+        .expect_err("This should fail.");
+    let root_cause = error.root_cause();
+    if let Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. }) =
+        root_cause.downcast_ref::<rusqlite::ffi::Error>()
+    {
+        return;
+    }
+    panic!(
+        "Unexpected error {:?} \n{:?} \n{:?}",
+        error,
+        root_cause,
+        root_cause.downcast_ref::<rusqlite::ffi::Error>()
+    )
+}
+
+#[cfg(disabled)]
+#[test]
+fn test_large_number_of_concurrent_db_manipulations() -> Result<()> {
+    let temp_dir = Arc::new(
+        TempDir::new("test_large_number_of_concurrent_db_manipulations_")
+            .expect("Failed to create temp dir."),
+    );
+
+    let test_begin = Instant::now();
+
+    const KEY_COUNT: u32 = 500u32;
+    let mut db =
+        new_test_db_with_gc(temp_dir.path(), |_, _| Ok(())).expect("Failed to open database.");
+    const OPEN_DB_COUNT: u32 = 50u32;
+
+    let mut actual_key_count = KEY_COUNT;
+    // First insert KEY_COUNT keys.
+    for count in 0..KEY_COUNT {
+        if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) {
+            actual_key_count = count;
+            break;
+        }
+        let alias = format!("test_alias_{}", count);
+        make_test_key_entry(&mut db, Domain::APP, 1, &alias, None)
+            .expect("Failed to make key entry.");
+    }
+
+    // Insert more keys from a different thread and into a different namespace.
+    let temp_dir1 = temp_dir.clone();
+    let handle1 = thread::spawn(move || {
+        let mut db =
+            new_test_db_with_gc(temp_dir1.path(), |_, _| Ok(())).expect("Failed to open database.");
+
+        for count in 0..actual_key_count {
+            if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+                return;
+            }
+            let alias = format!("test_alias_{}", count);
+            make_test_key_entry(&mut db, Domain::APP, 2, &alias, None)
+                .expect("Failed to make key entry.");
+        }
+
+        // then unbind them again.
+        for count in 0..actual_key_count {
+            if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+                return;
+            }
+            let key = KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(format!("test_alias_{}", count)),
+                blob: None,
+            };
+            db.unbind_key(&key, KeyType::Client, 2, |_, _| Ok(())).expect("Unbind Failed.");
+        }
+    });
+
+    // And start unbinding the first set of keys.
+    let temp_dir2 = temp_dir.clone();
+    let handle2 = thread::spawn(move || {
+        let mut db =
+            new_test_db_with_gc(temp_dir2.path(), |_, _| Ok(())).expect("Failed to open database.");
+
+        for count in 0..actual_key_count {
+            if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+                return;
+            }
+            let key = KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(format!("test_alias_{}", count)),
+                blob: None,
+            };
+            db.unbind_key(&key, KeyType::Client, 1, |_, _| Ok(())).expect("Unbind Failed.");
+        }
+    });
+
+    // While a lot of inserting and deleting is going on we have to open database connections
+    // successfully and use them.
+    // This clone is not redundant, because temp_dir needs to be kept alive until db goes
+    // out of scope.
+    #[allow(clippy::redundant_clone)]
+    let temp_dir4 = temp_dir.clone();
+    let handle4 = thread::spawn(move || {
+        for count in 0..OPEN_DB_COUNT {
+            if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+                return;
+            }
+            let mut db = new_test_db_with_gc(temp_dir4.path(), |_, _| Ok(()))
+                .expect("Failed to open database.");
+
+            let alias = format!("test_alias_{}", count);
+            make_test_key_entry(&mut db, Domain::APP, 3, &alias, None)
+                .expect("Failed to make key entry.");
+            let key =
+                KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None };
+            db.unbind_key(&key, KeyType::Client, 3, |_, _| Ok(())).expect("Unbind Failed.");
+        }
+    });
+
+    handle1.join().expect("Thread 1 panicked.");
+    handle2.join().expect("Thread 2 panicked.");
+    handle4.join().expect("Thread 4 panicked.");
+
+    Ok(())
+}
+
+#[test]
+fn list() -> Result<()> {
+    let temp_dir = TempDir::new("list_test")?;
+    let mut db = KeystoreDB::new(temp_dir.path(), None)?;
+    static LIST_O_ENTRIES: &[(Domain, i64, &str)] = &[
+        (Domain::APP, 1, "test1"),
+        (Domain::APP, 1, "test2"),
+        (Domain::APP, 1, "test3"),
+        (Domain::APP, 1, "test4"),
+        (Domain::APP, 1, "test5"),
+        (Domain::APP, 1, "test6"),
+        (Domain::APP, 1, "test7"),
+        (Domain::APP, 2, "test1"),
+        (Domain::APP, 2, "test2"),
+        (Domain::APP, 2, "test3"),
+        (Domain::APP, 2, "test4"),
+        (Domain::APP, 2, "test5"),
+        (Domain::APP, 2, "test6"),
+        (Domain::APP, 2, "test8"),
+        (Domain::SELINUX, 100, "test1"),
+        (Domain::SELINUX, 100, "test2"),
+        (Domain::SELINUX, 100, "test3"),
+        (Domain::SELINUX, 100, "test4"),
+        (Domain::SELINUX, 100, "test5"),
+        (Domain::SELINUX, 100, "test6"),
+        (Domain::SELINUX, 100, "test9"),
+    ];
+
+    let list_o_keys: Vec<(i64, i64)> = LIST_O_ENTRIES
+        .iter()
+        .map(|(domain, ns, alias)| {
+            let entry =
+                make_test_key_entry(&mut db, *domain, *ns, alias, None).unwrap_or_else(|e| {
+                    panic!("Failed to insert {:?} {} {}. Error {:?}", domain, ns, alias, e)
+                });
+            (entry.id(), *ns)
+        })
+        .collect();
+
+    for (domain, namespace) in
+        &[(Domain::APP, 1i64), (Domain::APP, 2i64), (Domain::SELINUX, 100i64)]
+    {
+        let mut list_o_descriptors: Vec<KeyDescriptor> = LIST_O_ENTRIES
+            .iter()
+            .filter_map(|(domain, ns, alias)| match ns {
+                ns if *ns == *namespace => Some(KeyDescriptor {
+                    domain: *domain,
+                    nspace: *ns,
+                    alias: Some(alias.to_string()),
+                    blob: None,
+                }),
+                _ => None,
+            })
+            .collect();
+        list_o_descriptors.sort();
+        let mut list_result = db.list_past_alias(*domain, *namespace, KeyType::Client, None)?;
+        list_result.sort();
+        assert_eq!(list_o_descriptors, list_result);
+
+        let mut list_o_ids: Vec<i64> = list_o_descriptors
+            .into_iter()
+            .map(|d| {
+                let (_, entry) = db
+                    .load_key_entry(
+                        &d,
+                        KeyType::Client,
+                        KeyEntryLoadBits::NONE,
+                        *namespace as u32,
+                        |_, _| Ok(()),
+                    )
+                    .unwrap();
+                entry.id()
+            })
+            .collect();
+        list_o_ids.sort_unstable();
+        let mut loaded_entries: Vec<i64> = list_o_keys
+            .iter()
+            .filter_map(|(id, ns)| match ns {
+                ns if *ns == *namespace => Some(*id),
+                _ => None,
+            })
+            .collect();
+        loaded_entries.sort_unstable();
+        assert_eq!(list_o_ids, loaded_entries);
+    }
+    assert_eq!(
+        Vec::<KeyDescriptor>::new(),
+        db.list_past_alias(Domain::SELINUX, 101, KeyType::Client, None)?
+    );
+
+    Ok(())
+}
+
+// Helpers
+
+// Checks that the given result is an error containing the given string.
+fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) {
+    let error_str =
+        format!("{:#?}", result.err().unwrap_or_else(|| panic!("Expected the error: {}", target)));
+    assert!(
+        error_str.contains(target),
+        "The string \"{}\" should contain \"{}\"",
+        error_str,
+        target
+    );
+}
+
+#[derive(Debug, PartialEq)]
+struct KeyEntryRow {
+    id: i64,
+    key_type: KeyType,
+    domain: Option<Domain>,
+    namespace: Option<i64>,
+    alias: Option<String>,
+    state: KeyLifeCycle,
+    km_uuid: Option<Uuid>,
+}
+
+fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> {
+    db.conn
+        .prepare("SELECT * FROM persistent.keyentry;")?
+        .query_map([], |row| {
+            Ok(KeyEntryRow {
+                id: row.get(0)?,
+                key_type: row.get(1)?,
+                domain: row.get::<_, Option<_>>(2)?.map(Domain),
+                namespace: row.get(3)?,
+                alias: row.get(4)?,
+                state: row.get(5)?,
+                km_uuid: row.get(6)?,
+            })
+        })?
+        .map(|r| r.context("Could not read keyentry row."))
+        .collect::<Result<Vec<_>>>()
+}
+
+fn make_test_params(max_usage_count: Option<i32>) -> Vec<KeyParameter> {
+    make_test_params_with_sids(max_usage_count, &[42])
+}
+
+// Note: The parameters and SecurityLevel associations are nonsensical. This
+// collection is only used to check if the parameters are preserved as expected by the
+// database.
+fn make_test_params_with_sids(
+    max_usage_count: Option<i32>,
+    user_secure_ids: &[i64],
+) -> Vec<KeyParameter> {
+    let mut params = vec![
+        KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(
+            KeyParameterValue::KeyPurpose(KeyPurpose::SIGN),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::Algorithm(Algorithm::RSA),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(
+            KeyParameterValue::BlockMode(BlockMode::ECB),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::BlockMode(BlockMode::GCM),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::Digest(Digest::NONE), SecurityLevel::STRONGBOX),
+        KeyParameter::new(
+            KeyParameterValue::Digest(Digest::MD5),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::Digest(Digest::SHA_2_224),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::Digest(Digest::SHA_2_256), SecurityLevel::STRONGBOX),
+        KeyParameter::new(
+            KeyParameterValue::PaddingMode(PaddingMode::NONE),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::PaddingMode(PaddingMode::RSA_OAEP),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS),
+            SecurityLevel::STRONGBOX,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::PaddingMode(PaddingMode::RSA_PKCS1_1_5_SIGN),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(KeyParameterValue::MinMacLength(256), SecurityLevel::STRONGBOX),
+        KeyParameter::new(
+            KeyParameterValue::EcCurve(EcCurve::P_224),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::STRONGBOX),
+        KeyParameter::new(
+            KeyParameterValue::EcCurve(EcCurve::P_384),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::EcCurve(EcCurve::P_521),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::RSAPublicExponent(3),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::IncludeUniqueID, SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(KeyParameterValue::BootLoaderOnly, SecurityLevel::STRONGBOX),
+        KeyParameter::new(KeyParameterValue::RollbackResistance, SecurityLevel::STRONGBOX),
+        KeyParameter::new(KeyParameterValue::ActiveDateTime(1234567890), SecurityLevel::STRONGBOX),
+        KeyParameter::new(
+            KeyParameterValue::OriginationExpireDateTime(1234567890),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::UsageExpireDateTime(1234567890),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::MinSecondsBetweenOps(1234567890),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::MaxUsesPerBoot(1234567890),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::UserID(1), SecurityLevel::STRONGBOX),
+        KeyParameter::new(KeyParameterValue::NoAuthRequired, SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(
+            KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::PASSWORD),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::AuthTimeout(1234567890), SecurityLevel::SOFTWARE),
+        KeyParameter::new(KeyParameterValue::AllowWhileOnBody, SecurityLevel::SOFTWARE),
+        KeyParameter::new(
+            KeyParameterValue::TrustedUserPresenceRequired,
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::TrustedConfirmationRequired,
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::UnlockedDeviceRequired,
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::ApplicationID(vec![1u8, 2u8, 3u8, 4u8]),
+            SecurityLevel::SOFTWARE,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::ApplicationData(vec![4u8, 3u8, 2u8, 1u8]),
+            SecurityLevel::SOFTWARE,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::CreationDateTime(12345677890),
+            SecurityLevel::SOFTWARE,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::RootOfTrust(vec![3u8, 2u8, 1u8, 4u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::OSVersion(1), SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(KeyParameterValue::OSPatchLevel(2), SecurityLevel::SOFTWARE),
+        KeyParameter::new(
+            KeyParameterValue::UniqueID(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::SOFTWARE,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationChallenge(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationApplicationID(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdBrand(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdDevice(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdProduct(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdSerial(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdIMEI(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdSecondIMEI(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdMEID(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdManufacturer(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::AttestationIdModel(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::VendorPatchLevel(3),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::BootPatchLevel(4), SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(
+            KeyParameterValue::AssociatedData(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::Nonce(vec![4u8, 3u8, 1u8, 2u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(KeyParameterValue::MacLength(256), SecurityLevel::TRUSTED_ENVIRONMENT),
+        KeyParameter::new(
+            KeyParameterValue::ResetSinceIdRotation,
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+        KeyParameter::new(
+            KeyParameterValue::ConfirmationToken(vec![5u8, 5u8, 5u8, 5u8]),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ),
+    ];
+    if let Some(value) = max_usage_count {
+        params.push(KeyParameter::new(
+            KeyParameterValue::UsageCountLimit(value),
+            SecurityLevel::SOFTWARE,
+        ));
+    }
+
+    for sid in user_secure_ids.iter() {
+        params.push(KeyParameter::new(
+            KeyParameterValue::UserSecureID(*sid),
+            SecurityLevel::STRONGBOX,
+        ));
+    }
+    params
+}
+
+pub fn make_test_key_entry(
+    db: &mut KeystoreDB,
+    domain: Domain,
+    namespace: i64,
+    alias: &str,
+    max_usage_count: Option<i32>,
+) -> Result<KeyIdGuard> {
+    make_test_key_entry_with_sids(db, domain, namespace, alias, max_usage_count, &[42])
+}
+
+pub fn make_test_key_entry_with_sids(
+    db: &mut KeystoreDB,
+    domain: Domain,
+    namespace: i64,
+    alias: &str,
+    max_usage_count: Option<i32>,
+    sids: &[i64],
+) -> Result<KeyIdGuard> {
+    let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
+    let mut blob_metadata = BlobMetaData::new();
+    blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+    blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
+    blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
+    blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
+    blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+    db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?;
+    db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
+    db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
+
+    let params = make_test_params_with_sids(max_usage_count, sids);
+    db.insert_keyparameter(&key_id, &params)?;
+
+    let mut metadata = KeyMetaData::new();
+    metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+    db.insert_key_metadata(&key_id, &metadata)?;
+    rebind_alias(db, &key_id, alias, domain, namespace)?;
+    Ok(key_id)
+}
+
+fn make_test_key_entry_test_vector(key_id: i64, max_usage_count: Option<i32>) -> KeyEntry {
+    let params = make_test_params(max_usage_count);
+
+    let mut blob_metadata = BlobMetaData::new();
+    blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+    blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3]));
+    blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1]));
+    blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2]));
+    blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+    let mut metadata = KeyMetaData::new();
+    metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+
+    KeyEntry {
+        id: key_id,
+        key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
+        cert: Some(TEST_CERT_BLOB.to_vec()),
+        cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
+        km_uuid: KEYSTORE_UUID,
+        parameters: params,
+        metadata,
+        pure_cert: false,
+    }
+}
+
+pub fn make_bootlevel_key_entry(
+    db: &mut KeystoreDB,
+    domain: Domain,
+    namespace: i64,
+    alias: &str,
+    logical_only: bool,
+) -> Result<KeyIdGuard> {
+    let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
+    let mut blob_metadata = BlobMetaData::new();
+    if !logical_only {
+        blob_metadata.add(BlobMetaEntry::MaxBootLevel(3));
+    }
+    blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+    db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?;
+    db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?;
+    db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?;
+
+    let mut params = make_test_params(None);
+    params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE));
+
+    db.insert_keyparameter(&key_id, &params)?;
+
+    let mut metadata = KeyMetaData::new();
+    metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+    db.insert_key_metadata(&key_id, &metadata)?;
+    rebind_alias(db, &key_id, alias, domain, namespace)?;
+    Ok(key_id)
+}
+
+// Creates an app key that is marked as being superencrypted by the given
+// super key ID and that has the given authentication and unlocked device
+// parameters. This does not actually superencrypt the key blob.
+fn make_superencrypted_key_entry(
+    db: &mut KeystoreDB,
+    namespace: i64,
+    alias: &str,
+    requires_authentication: bool,
+    requires_unlocked_device: bool,
+    super_key_id: i64,
+) -> Result<KeyIdGuard> {
+    let domain = Domain::APP;
+    let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?;
+
+    let mut blob_metadata = BlobMetaData::new();
+    blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+    blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+    db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?;
+
+    let mut params = vec![];
+    if requires_unlocked_device {
+        params.push(KeyParameter::new(
+            KeyParameterValue::UnlockedDeviceRequired,
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ));
+    }
+    if requires_authentication {
+        params.push(KeyParameter::new(
+            KeyParameterValue::UserSecureID(42),
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+        ));
+    }
+    db.insert_keyparameter(&key_id, &params)?;
+
+    let mut metadata = KeyMetaData::new();
+    metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+    db.insert_key_metadata(&key_id, &metadata)?;
+
+    rebind_alias(db, &key_id, alias, domain, namespace)?;
+    Ok(key_id)
+}
+
+fn make_bootlevel_test_key_entry_test_vector(key_id: i64, logical_only: bool) -> KeyEntry {
+    let mut params = make_test_params(None);
+    params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE));
+
+    let mut blob_metadata = BlobMetaData::new();
+    if !logical_only {
+        blob_metadata.add(BlobMetaEntry::MaxBootLevel(3));
+    }
+    blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID));
+
+    let mut metadata = KeyMetaData::new();
+    metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+
+    KeyEntry {
+        id: key_id,
+        key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)),
+        cert: Some(TEST_CERT_BLOB.to_vec()),
+        cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()),
+        km_uuid: KEYSTORE_UUID,
+        parameters: params,
+        metadata,
+        pure_cert: false,
+    }
+}
+
+fn debug_dump_keyentry_table(db: &mut KeystoreDB) -> Result<()> {
+    let mut stmt = db.conn.prepare(
+        "SELECT id, key_type, domain, namespace, alias, state, km_uuid FROM persistent.keyentry;",
+    )?;
+    let rows =
+        stmt.query_map::<(i64, KeyType, i32, i64, String, KeyLifeCycle, Uuid), _, _>([], |row| {
+            Ok((
+                row.get(0)?,
+                row.get(1)?,
+                row.get(2)?,
+                row.get(3)?,
+                row.get(4)?,
+                row.get(5)?,
+                row.get(6)?,
+            ))
+        })?;
+
+    println!("Key entry table rows:");
+    for r in rows {
+        let (id, key_type, domain, namespace, alias, state, km_uuid) = r.unwrap();
+        println!(
+            "    id: {} KeyType: {:?} Domain: {} Namespace: {} Alias: {} State: {:?} KmUuid: {:?}",
+            id, key_type, domain, namespace, alias, state, km_uuid
+        );
+    }
+    Ok(())
+}
+
+fn debug_dump_grant_table(db: &mut KeystoreDB) -> Result<()> {
+    let mut stmt =
+        db.conn.prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
+    let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>([], |row| {
+        Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
+    })?;
+
+    println!("Grant table rows:");
+    for r in rows {
+        let (id, gt, ki, av) = r.unwrap();
+        println!("    id: {} grantee: {} key_id: {} access_vector: {}", id, gt, ki, av);
+    }
+    Ok(())
+}
+
+// Use a custom random number generator that repeats each number once.
+// This allows us to test repeated elements.
+
+thread_local! {
+    static RANDOM_COUNTER: RefCell<i64> = const { RefCell::new(0) };
+}
+
+fn reset_random() {
+    RANDOM_COUNTER.with(|counter| {
+        *counter.borrow_mut() = 0;
+    })
+}
+
+pub fn random() -> i64 {
+    RANDOM_COUNTER.with(|counter| {
+        let result = *counter.borrow() / 2;
+        *counter.borrow_mut() += 1;
+        result
+    })
+}
+
+#[test]
+fn test_unbind_keys_for_user() -> Result<()> {
+    let mut db = new_test_db()?;
+    db.unbind_keys_for_user(1)?;
+
+    make_test_key_entry(&mut db, Domain::APP, 210000, TEST_ALIAS, None)?;
+    make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?;
+    db.unbind_keys_for_user(2)?;
+
+    assert_eq!(1, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len());
+    assert_eq!(0, db.list_past_alias(Domain::APP, 210000, KeyType::Client, None)?.len());
+
+    db.unbind_keys_for_user(1)?;
+    assert_eq!(0, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len());
+
+    Ok(())
+}
+
+#[test]
+fn test_unbind_keys_for_user_removes_superkeys() -> Result<()> {
+    let mut db = new_test_db()?;
+    let super_key = keystore2_crypto::generate_aes256_key()?;
+    let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
+    let (encrypted_super_key, metadata) = SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
+
+    let key_name_enc = SuperKeyType {
+        alias: "test_super_key_1",
+        algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
+        name: "test_super_key_1",
+    };
+
+    let key_name_nonenc = SuperKeyType {
+        alias: "test_super_key_2",
+        algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
+        name: "test_super_key_2",
+    };
+
+    // Install two super keys.
+    db.store_super_key(1, &key_name_nonenc, &super_key, &BlobMetaData::new(), &KeyMetaData::new())?;
+    db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?;
+
+    // Check that both can be found in the database.
+    assert!(db.load_super_key(&key_name_enc, 1)?.is_some());
+    assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some());
+
+    // Install the same keys for a different user.
+    db.store_super_key(2, &key_name_nonenc, &super_key, &BlobMetaData::new(), &KeyMetaData::new())?;
+    db.store_super_key(2, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?;
+
+    // Check that the second pair of keys can be found in the database.
+    assert!(db.load_super_key(&key_name_enc, 2)?.is_some());
+    assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some());
+
+    // Delete all keys for user 1.
+    db.unbind_keys_for_user(1)?;
+
+    // All of user 1's keys should be gone.
+    assert!(db.load_super_key(&key_name_enc, 1)?.is_none());
+    assert!(db.load_super_key(&key_name_nonenc, 1)?.is_none());
+
+    // User 2's keys should not have been touched.
+    assert!(db.load_super_key(&key_name_enc, 2)?.is_some());
+    assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some());
+
+    Ok(())
+}
+
+fn app_key_exists(db: &mut KeystoreDB, nspace: i64, alias: &str) -> Result<bool> {
+    db.key_exists(Domain::APP, nspace, alias, KeyType::Client)
+}
+
+// Tests the unbind_auth_bound_keys_for_user() function.
+#[test]
+fn test_unbind_auth_bound_keys_for_user() -> Result<()> {
+    let mut db = new_test_db()?;
+    let user_id = 1;
+    let nspace: i64 = (user_id * AID_USER_OFFSET).into();
+    let other_user_id = 2;
+    let other_user_nspace: i64 = (other_user_id * AID_USER_OFFSET).into();
+    let super_key_type = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY;
+
+    // Create a superencryption key.
+    let super_key = keystore2_crypto::generate_aes256_key()?;
+    let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
+    let (encrypted_super_key, blob_metadata) =
+        SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
+    db.store_super_key(
+        user_id,
+        super_key_type,
+        &encrypted_super_key,
+        &blob_metadata,
+        &KeyMetaData::new(),
+    )?;
+    let super_key_id = db.load_super_key(super_key_type, user_id)?.unwrap().0 .0;
+
+    // Store 4 superencrypted app keys, one for each possible combination of
+    // (authentication required, unlocked device required).
+    make_superencrypted_key_entry(&mut db, nspace, "noauth_noud", false, false, super_key_id)?;
+    make_superencrypted_key_entry(&mut db, nspace, "noauth_ud", false, true, super_key_id)?;
+    make_superencrypted_key_entry(&mut db, nspace, "auth_noud", true, false, super_key_id)?;
+    make_superencrypted_key_entry(&mut db, nspace, "auth_ud", true, true, super_key_id)?;
+    assert!(app_key_exists(&mut db, nspace, "noauth_noud")?);
+    assert!(app_key_exists(&mut db, nspace, "noauth_ud")?);
+    assert!(app_key_exists(&mut db, nspace, "auth_noud")?);
+    assert!(app_key_exists(&mut db, nspace, "auth_ud")?);
+
+    // Also store a key for a different user that requires authentication.
+    make_superencrypted_key_entry(&mut db, other_user_nspace, "auth_ud", true, true, super_key_id)?;
+
+    db.unbind_auth_bound_keys_for_user(user_id)?;
+
+    // Verify that only the user's app keys that require authentication were
+    // deleted. Keys that require an unlocked device but not authentication
+    // should *not* have been deleted, nor should the super key have been
+    // deleted, nor should other users' keys have been deleted.
+    assert!(db.load_super_key(super_key_type, user_id)?.is_some());
+    assert!(app_key_exists(&mut db, nspace, "noauth_noud")?);
+    assert!(app_key_exists(&mut db, nspace, "noauth_ud")?);
+    assert!(!app_key_exists(&mut db, nspace, "auth_noud")?);
+    assert!(!app_key_exists(&mut db, nspace, "auth_ud")?);
+    assert!(app_key_exists(&mut db, other_user_nspace, "auth_ud")?);
+
+    Ok(())
+}
+
+#[test]
+fn test_store_super_key() -> Result<()> {
+    let mut db = new_test_db()?;
+    let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into();
+    let super_key = keystore2_crypto::generate_aes256_key()?;
+    let secret_bytes = b"keystore2 is great.";
+    let (encrypted_secret, iv, tag) = keystore2_crypto::aes_gcm_encrypt(secret_bytes, &super_key)?;
+
+    let (encrypted_super_key, metadata) = SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
+    db.store_super_key(
+        1,
+        &USER_AFTER_FIRST_UNLOCK_SUPER_KEY,
+        &encrypted_super_key,
+        &metadata,
+        &KeyMetaData::new(),
+    )?;
+
+    // Check if super key exists.
+    assert!(db.key_exists(
+        Domain::APP,
+        1,
+        USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias,
+        KeyType::Super
+    )?);
+
+    let (_, key_entry) = db.load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, 1)?.unwrap();
+    let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry(
+        USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm,
+        key_entry,
+        &pw,
+        None,
+    )?;
+
+    let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?;
+    assert_eq!(secret_bytes, &*decrypted_secret_bytes);
+
+    Ok(())
+}
+
+fn get_valid_statsd_storage_types() -> Vec<MetricsStorage> {
+    vec![
+        MetricsStorage::KEY_ENTRY,
+        MetricsStorage::KEY_ENTRY_ID_INDEX,
+        MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX,
+        MetricsStorage::BLOB_ENTRY,
+        MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX,
+        MetricsStorage::KEY_PARAMETER,
+        MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX,
+        MetricsStorage::KEY_METADATA,
+        MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX,
+        MetricsStorage::GRANT,
+        MetricsStorage::AUTH_TOKEN,
+        MetricsStorage::BLOB_METADATA,
+        MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX,
+    ]
+}
+
+/// Perform a simple check to ensure that we can query all the storage types
+/// that are supported by the DB. Check for reasonable values.
+#[test]
+fn test_query_all_valid_table_sizes() -> Result<()> {
+    const PAGE_SIZE: i32 = 4096;
+
+    let mut db = new_test_db()?;
+
+    for t in get_valid_statsd_storage_types() {
+        let stat = db.get_storage_stat(t)?;
+        // AuthToken can be less than a page since it's in a btree, not sqlite
+        // TODO(b/187474736) stop using if-let here
+        if let MetricsStorage::AUTH_TOKEN = t {
+        } else {
+            assert!(stat.size >= PAGE_SIZE);
+        }
+        assert!(stat.size >= stat.unused_size);
+    }
+
+    Ok(())
+}
+
+fn get_storage_stats_map(db: &mut KeystoreDB) -> BTreeMap<i32, StorageStats> {
+    get_valid_statsd_storage_types()
+        .into_iter()
+        .map(|t| (t.0, db.get_storage_stat(t).unwrap()))
+        .collect()
+}
+
+fn assert_storage_increased(
+    db: &mut KeystoreDB,
+    increased_storage_types: Vec<MetricsStorage>,
+    baseline: &mut BTreeMap<i32, StorageStats>,
+) {
+    for storage in increased_storage_types {
+        // Verify the expected storage increased.
+        let new = db.get_storage_stat(storage).unwrap();
+        let old = &baseline[&storage.0];
+        assert!(new.size >= old.size, "{}: {} >= {}", storage.0, new.size, old.size);
+        assert!(
+            new.unused_size <= old.unused_size,
+            "{}: {} <= {}",
+            storage.0,
+            new.unused_size,
+            old.unused_size
+        );
+
+        // Update the baseline with the new value so that it succeeds in the
+        // later comparison.
+        baseline.insert(storage.0, new);
+    }
+
+    // Get an updated map of the storage and verify there were no unexpected changes.
+    let updated_stats = get_storage_stats_map(db);
+    assert_eq!(updated_stats.len(), baseline.len());
+
+    for &k in baseline.keys() {
+        let stringify = |map: &BTreeMap<i32, StorageStats>| -> String {
+            let mut s = String::new();
+            for &k in map.keys() {
+                writeln!(&mut s, "  {}: {}, {}", &k, map[&k].size, map[&k].unused_size)
+                    .expect("string concat failed");
+            }
+            s
+        };
+
+        assert!(
+            updated_stats[&k].size == baseline[&k].size
+                && updated_stats[&k].unused_size == baseline[&k].unused_size,
+            "updated_stats:\n{}\nbaseline:\n{}",
+            stringify(&updated_stats),
+            stringify(baseline)
+        );
+    }
+}
+
+#[test]
+fn test_verify_key_table_size_reporting() -> Result<()> {
+    let mut db = new_test_db()?;
+    let mut working_stats = get_storage_stats_map(&mut db);
+
+    let key_id = create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?;
+    assert_storage_increased(
+        &mut db,
+        vec![
+            MetricsStorage::KEY_ENTRY,
+            MetricsStorage::KEY_ENTRY_ID_INDEX,
+            MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX,
+        ],
+        &mut working_stats,
+    );
+
+    let mut blob_metadata = BlobMetaData::new();
+    blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+    db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), None)?;
+    assert_storage_increased(
+        &mut db,
+        vec![
+            MetricsStorage::BLOB_ENTRY,
+            MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX,
+            MetricsStorage::BLOB_METADATA,
+            MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX,
+        ],
+        &mut working_stats,
+    );
+
+    let params = make_test_params(None);
+    db.insert_keyparameter(&key_id, &params)?;
+    assert_storage_increased(
+        &mut db,
+        vec![MetricsStorage::KEY_PARAMETER, MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX],
+        &mut working_stats,
+    );
+
+    let mut metadata = KeyMetaData::new();
+    metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+    db.insert_key_metadata(&key_id, &metadata)?;
+    assert_storage_increased(
+        &mut db,
+        vec![MetricsStorage::KEY_METADATA, MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX],
+        &mut working_stats,
+    );
+
+    let mut sum = 0;
+    for stat in working_stats.values() {
+        sum += stat.size;
+    }
+    let total = db.get_storage_stat(MetricsStorage::DATABASE)?.size;
+    assert!(sum <= total, "Expected sum <= total. sum: {}, total: {}", sum, total);
+
+    Ok(())
+}
+
+#[test]
+fn test_verify_auth_table_size_reporting() -> Result<()> {
+    let mut db = new_test_db()?;
+    let mut working_stats = get_storage_stats_map(&mut db);
+    db.insert_auth_token(&HardwareAuthToken {
+        challenge: 123,
+        userId: 456,
+        authenticatorId: 789,
+        authenticatorType: kmhw_authenticator_type::ANY,
+        timestamp: Timestamp { milliSeconds: 10 },
+        mac: b"mac".to_vec(),
+    });
+    assert_storage_increased(&mut db, vec![MetricsStorage::AUTH_TOKEN], &mut working_stats);
+    Ok(())
+}
+
+#[test]
+fn test_verify_grant_table_size_reporting() -> Result<()> {
+    const OWNER: i64 = 1;
+    let mut db = new_test_db()?;
+    make_test_key_entry(&mut db, Domain::APP, OWNER, TEST_ALIAS, None)?;
+
+    let mut working_stats = get_storage_stats_map(&mut db);
+    db.grant(
+        &KeyDescriptor {
+            domain: Domain::APP,
+            nspace: 0,
+            alias: Some(TEST_ALIAS.to_string()),
+            blob: None,
+        },
+        OWNER as u32,
+        123,
+        key_perm_set![KeyPerm::Use],
+        |_, _| Ok(()),
+    )?;
+
+    assert_storage_increased(&mut db, vec![MetricsStorage::GRANT], &mut working_stats);
+
+    Ok(())
+}
+
+#[test]
+fn find_auth_token_entry_returns_latest() -> Result<()> {
+    let mut db = new_test_db()?;
+    db.insert_auth_token(&HardwareAuthToken {
+        challenge: 123,
+        userId: 456,
+        authenticatorId: 789,
+        authenticatorType: kmhw_authenticator_type::ANY,
+        timestamp: Timestamp { milliSeconds: 10 },
+        mac: b"mac0".to_vec(),
+    });
+    std::thread::sleep(std::time::Duration::from_millis(1));
+    db.insert_auth_token(&HardwareAuthToken {
+        challenge: 123,
+        userId: 457,
+        authenticatorId: 789,
+        authenticatorType: kmhw_authenticator_type::ANY,
+        timestamp: Timestamp { milliSeconds: 12 },
+        mac: b"mac1".to_vec(),
+    });
+    std::thread::sleep(std::time::Duration::from_millis(1));
+    db.insert_auth_token(&HardwareAuthToken {
+        challenge: 123,
+        userId: 458,
+        authenticatorId: 789,
+        authenticatorType: kmhw_authenticator_type::ANY,
+        timestamp: Timestamp { milliSeconds: 3 },
+        mac: b"mac2".to_vec(),
+    });
+    // All three entries are in the database
+    assert_eq!(db.perboot.auth_tokens_len(), 3);
+    // It selected the most recent timestamp
+    assert_eq!(db.find_auth_token_entry(|_| true).unwrap().auth_token.mac, b"mac2".to_vec());
+    Ok(())
+}
+
+fn blob_count(db: &mut KeystoreDB, sc_type: SubComponentType) -> usize {
+    db.with_transaction(TransactionBehavior::Deferred, |tx| {
+        tx.query_row(
+            "SELECT COUNT(*) FROM persistent.blobentry
+                     WHERE subcomponent_type = ?;",
+            params![sc_type],
+            |row| row.get(0),
+        )
+        .context(ks_err!("Failed to count number of {sc_type:?} blobs"))
+        .no_gc()
+    })
+    .unwrap()
+}
+
+fn blob_count_in_state(db: &mut KeystoreDB, sc_type: SubComponentType, state: BlobState) -> usize {
+    db.with_transaction(TransactionBehavior::Deferred, |tx| {
+        tx.query_row(
+            "SELECT COUNT(*) FROM persistent.blobentry
+                     WHERE subcomponent_type = ? AND state = ?;",
+            params![sc_type, state],
+            |row| row.get(0),
+        )
+        .context(ks_err!("Failed to count number of {sc_type:?} blobs"))
+        .no_gc()
+    })
+    .unwrap()
+}
+
+#[test]
+fn test_blobentry_gc() -> Result<()> {
+    let mut db = new_test_db()?;
+    let _key_id1 = make_test_key_entry(&mut db, Domain::APP, 1, "key1", None)?.0;
+    let key_guard2 = make_test_key_entry(&mut db, Domain::APP, 2, "key2", None)?;
+    let key_guard3 = make_test_key_entry(&mut db, Domain::APP, 3, "key3", None)?;
+    let key_id4 = make_test_key_entry(&mut db, Domain::APP, 4, "key4", None)?.0;
+    let key_id5 = make_test_key_entry(&mut db, Domain::APP, 5, "key5", None)?.0;
+
+    assert_eq!(5, blob_count(&mut db, SubComponentType::KEY_BLOB));
+    assert_eq!(5, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+    assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+    assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+    // Replace the keyblobs for keys 2 and 3.  The previous blobs will still exist.
+    db.set_blob(&key_guard2, SubComponentType::KEY_BLOB, Some(&[1, 2, 3]), None)?;
+    db.set_blob(&key_guard3, SubComponentType::KEY_BLOB, Some(&[1, 2, 3]), None)?;
+
+    assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+    assert_eq!(5, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+    assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+    assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+    // Delete keys 4 and 5.  The keyblobs aren't removed yet.
+    db.with_transaction(Immediate("TX_delete_test_keys"), |tx| {
+        KeystoreDB::mark_unreferenced(tx, key_id4)?;
+        KeystoreDB::mark_unreferenced(tx, key_id5)?;
+        Ok(()).no_gc()
+    })
+    .unwrap();
+
+    assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+    assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+    assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+    assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+    // First garbage collection should return all 4 blobentry rows that are no longer current for
+    // their key.
+    let superseded = db.handle_next_superseded_blobs(&[], 20).unwrap();
+    let superseded_ids: Vec<i64> = superseded.iter().map(|v| v.blob_id).collect();
+    assert_eq!(4, superseded.len());
+    assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+    assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+    assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+    assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+    // Feed the superseded blob IDs back in, to trigger removal of the old KEY_BLOB entries.  As no
+    // new superseded KEY_BLOBs are found, the unreferenced CERT/CERT_CHAIN blobs are removed.
+    let superseded = db.handle_next_superseded_blobs(&superseded_ids, 20).unwrap();
+    let superseded_ids: Vec<i64> = superseded.iter().map(|v| v.blob_id).collect();
+    assert_eq!(0, superseded.len());
+    assert_eq!(3, blob_count(&mut db, SubComponentType::KEY_BLOB));
+    assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+    assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+    assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+    assert_eq!(3, blob_count(&mut db, SubComponentType::CERT));
+    assert_eq!(3, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+    // Nothing left to garbage collect.
+    let superseded = db.handle_next_superseded_blobs(&superseded_ids, 20).unwrap();
+    assert_eq!(0, superseded.len());
+    assert_eq!(3, blob_count(&mut db, SubComponentType::KEY_BLOB));
+    assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+    assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+    assert_eq!(0, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+    assert_eq!(3, blob_count(&mut db, SubComponentType::CERT));
+    assert_eq!(3, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+    Ok(())
+}
+
+#[test]
+fn test_upgrade_1_to_2() -> Result<()> {
+    let mut db = new_test_db()?;
+    let _key_id1 = make_test_key_entry(&mut db, Domain::APP, 1, "key1", None)?.0;
+    let key_guard2 = make_test_key_entry(&mut db, Domain::APP, 2, "key2", None)?;
+    let key_guard3 = make_test_key_entry(&mut db, Domain::APP, 3, "key3", None)?;
+    let key_id4 = make_test_key_entry(&mut db, Domain::APP, 4, "key4", None)?.0;
+    let key_id5 = make_test_key_entry(&mut db, Domain::APP, 5, "key5", None)?.0;
+
+    // Replace the keyblobs for keys 2 and 3.  The previous blobs will still exist.
+    db.set_blob(&key_guard2, SubComponentType::KEY_BLOB, Some(&[1, 2, 3]), None)?;
+    db.set_blob(&key_guard3, SubComponentType::KEY_BLOB, Some(&[1, 2, 3]), None)?;
+
+    // Delete keys 4 and 5.  The keyblobs aren't removed yet.
+    db.with_transaction(Immediate("TX_delete_test_keys"), |tx| {
+        KeystoreDB::mark_unreferenced(tx, key_id4)?;
+        KeystoreDB::mark_unreferenced(tx, key_id5)?;
+        Ok(()).no_gc()
+    })
+    .unwrap();
+    assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+    // Manually downgrade the database to the v1 schema.
+    db.with_transaction(Immediate("TX_downgrade_2_to_1"), |tx| {
+        tx.execute("DROP INDEX persistent.keyentry_state_index;", params!())?;
+        tx.execute("DROP INDEX persistent.blobentry_state_index;", params!())?;
+        tx.execute("ALTER TABLE persistent.blobentry DROP COLUMN state;", params!())?;
+        Ok(()).no_gc()
+    })?;
+
+    // Run the upgrade process.
+    let version = db.with_transaction(Immediate("TX_upgrade_1_to_2"), |tx| {
+        KeystoreDB::from_1_to_2(tx).no_gc()
+    })?;
+    assert_eq!(version, 2);
+
+    // Check blobs have acquired the right `state` values.
+    assert_eq!(7, blob_count(&mut db, SubComponentType::KEY_BLOB));
+    assert_eq!(3, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Current));
+    assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Superseded));
+    assert_eq!(2, blob_count_in_state(&mut db, SubComponentType::KEY_BLOB, BlobState::Orphaned));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT));
+    assert_eq!(5, blob_count(&mut db, SubComponentType::CERT_CHAIN));
+
+    Ok(())
+}
+
+#[test]
+fn test_load_key_descriptor() -> Result<()> {
+    let mut db = new_test_db()?;
+    let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)?.0;
+
+    let key = db.load_key_descriptor(key_id)?.unwrap();
+
+    assert_eq!(key.domain, Domain::APP);
+    assert_eq!(key.nspace, 1);
+    assert_eq!(key.alias, Some(TEST_ALIAS.to_string()));
+
+    // No such id
+    assert_eq!(db.load_key_descriptor(key_id + 1)?, None);
+    Ok(())
+}
+
+#[test]
+fn test_get_list_app_uids_for_sid() -> Result<()> {
+    let uid: i32 = 1;
+    let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64);
+    let first_sid = 667;
+    let second_sid = 669;
+    let first_app_id: i64 = 123 + uid_offset;
+    let second_app_id: i64 = 456 + uid_offset;
+    let third_app_id: i64 = 789 + uid_offset;
+    let unrelated_app_id: i64 = 1011 + uid_offset;
+    let mut db = new_test_db()?;
+    make_test_key_entry_with_sids(
+        &mut db,
+        Domain::APP,
+        first_app_id,
+        TEST_ALIAS,
+        None,
+        &[first_sid],
+    )
+    .context("test_get_list_app_uids_for_sid")?;
+    make_test_key_entry_with_sids(
+        &mut db,
+        Domain::APP,
+        second_app_id,
+        "alias2",
+        None,
+        &[first_sid],
+    )
+    .context("test_get_list_app_uids_for_sid")?;
+    make_test_key_entry_with_sids(
+        &mut db,
+        Domain::APP,
+        second_app_id,
+        TEST_ALIAS,
+        None,
+        &[second_sid],
+    )
+    .context("test_get_list_app_uids_for_sid")?;
+    make_test_key_entry_with_sids(
+        &mut db,
+        Domain::APP,
+        third_app_id,
+        "alias3",
+        None,
+        &[second_sid],
+    )
+    .context("test_get_list_app_uids_for_sid")?;
+    make_test_key_entry_with_sids(&mut db, Domain::APP, unrelated_app_id, TEST_ALIAS, None, &[])
+        .context("test_get_list_app_uids_for_sid")?;
+
+    let mut first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?;
+    first_sid_apps.sort();
+    assert_eq!(first_sid_apps, vec![first_app_id, second_app_id]);
+    let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?;
+    second_sid_apps.sort();
+    assert_eq!(second_sid_apps, vec![second_app_id, third_app_id]);
+    Ok(())
+}
+
+#[test]
+fn test_get_list_app_uids_with_multiple_sids() -> Result<()> {
+    let uid: i32 = 1;
+    let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64);
+    let first_sid = 667;
+    let second_sid = 669;
+    let third_sid = 772;
+    let first_app_id: i64 = 123 + uid_offset;
+    let second_app_id: i64 = 456 + uid_offset;
+    let mut db = new_test_db()?;
+    make_test_key_entry_with_sids(
+        &mut db,
+        Domain::APP,
+        first_app_id,
+        TEST_ALIAS,
+        None,
+        &[first_sid, second_sid],
+    )
+    .context("test_get_list_app_uids_for_sid")?;
+    make_test_key_entry_with_sids(
+        &mut db,
+        Domain::APP,
+        second_app_id,
+        "alias2",
+        None,
+        &[second_sid, third_sid],
+    )
+    .context("test_get_list_app_uids_for_sid")?;
+
+    let first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?;
+    assert_eq!(first_sid_apps, vec![first_app_id]);
+
+    let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?;
+    second_sid_apps.sort();
+    assert_eq!(second_sid_apps, vec![first_app_id, second_app_id]);
+
+    let third_sid_apps = db.get_app_uids_affected_by_sid(uid, third_sid)?;
+    assert_eq!(third_sid_apps, vec![second_app_id]);
+    Ok(())
+}
+
+// Starting from `next_keyid`, add keys to the database until the count reaches
+// `key_count`.  (`next_keyid` is assumed to indicate how many rows already exist.)
+fn db_populate_keys(db: &mut KeystoreDB, next_keyid: usize, key_count: usize) {
+    db.with_transaction(Immediate("test_keyentry"), |tx| {
+        for next_keyid in next_keyid..key_count {
+            tx.execute(
+                "INSERT into persistent.keyentry
+                        (id, key_type, domain, namespace, alias, state, km_uuid)
+                        VALUES(?, ?, ?, ?, ?, ?, ?);",
+                params![
+                    next_keyid,
+                    KeyType::Client,
+                    Domain::APP.0 as u32,
+                    10001,
+                    &format!("alias-{next_keyid}"),
+                    KeyLifeCycle::Live,
+                    KEYSTORE_UUID,
+                ],
+            )?;
+            tx.execute(
+                "INSERT INTO persistent.blobentry
+                         (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
+                params![SubComponentType::KEY_BLOB, next_keyid, TEST_KEY_BLOB],
+            )?;
+            tx.execute(
+                "INSERT INTO persistent.blobentry
+                         (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
+                params![SubComponentType::CERT, next_keyid, TEST_CERT_BLOB],
+            )?;
+            tx.execute(
+                "INSERT INTO persistent.blobentry
+                         (subcomponent_type, keyentryid, blob) VALUES (?, ?, ?);",
+                params![SubComponentType::CERT_CHAIN, next_keyid, TEST_CERT_CHAIN_BLOB],
+            )?;
+        }
+        Ok(()).no_gc()
+    })
+    .unwrap()
+}
+
+/// Run the provided `test_fn` against the database at various increasing stages of
+/// database population.
+fn run_with_many_keys<F, T>(max_count: usize, test_fn: F) -> Result<()>
+where
+    F: Fn(&mut KeystoreDB) -> T,
+{
+    prep_and_run_with_many_keys(max_count, |_db| (), test_fn)
+}
+
+/// Run the provided `test_fn` against the database at various increasing stages of
+/// database population.
+fn prep_and_run_with_many_keys<F, T, P>(max_count: usize, prep_fn: P, test_fn: F) -> Result<()>
+where
+    F: Fn(&mut KeystoreDB) -> T,
+    P: Fn(&mut KeystoreDB),
+{
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("keystore2_test")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+    // Put the test database on disk for a more realistic result.
+    let db_root = tempfile::Builder::new().prefix("ks2db-test-").tempdir().unwrap();
+    let mut db_path = db_root.path().to_owned();
+    db_path.push("ks2-test.sqlite");
+    let mut db = new_test_db_at(&db_path.to_string_lossy())?;
+
+    println!("\nNumber_of_keys,time_in_s");
+    let mut key_count = 10;
+    let mut next_keyid = 0;
+    while key_count < max_count {
+        db_populate_keys(&mut db, next_keyid, key_count);
+        assert_eq!(db_key_count(&mut db), key_count);
+
+        // Perform any test-specific preparation
+        prep_fn(&mut db);
+
+        // Time execution of the test function.
+        let start = std::time::Instant::now();
+        let _result = test_fn(&mut db);
+        println!("{key_count}, {}", start.elapsed().as_secs_f64());
+
+        next_keyid = key_count;
+        key_count *= 2;
+    }
+
+    Ok(())
+}
+
+fn db_key_count(db: &mut KeystoreDB) -> usize {
+    db.with_transaction(TransactionBehavior::Deferred, |tx| {
+        tx.query_row(
+            "SELECT COUNT(*) FROM persistent.keyentry
+                         WHERE domain = ? AND state = ? AND key_type = ?;",
+            params![Domain::APP.0 as u32, KeyLifeCycle::Live, KeyType::Client],
+            |row| row.get::<usize, usize>(0),
+        )
+        .context(ks_err!("Failed to count number of keys."))
+        .no_gc()
+    })
+    .unwrap()
+}
+
+#[test]
+fn test_handle_superseded_with_many_keys() -> Result<()> {
+    run_with_many_keys(1_000_000, |db| db.handle_next_superseded_blobs(&[], 20))
+}
+
+#[test]
+fn test_get_storage_stats_with_many_keys() -> Result<()> {
+    use android_security_metrics::aidl::android::security::metrics::Storage::Storage as MetricsStorage;
+    run_with_many_keys(1_000_000, |db| {
+        db.get_storage_stat(MetricsStorage::DATABASE).unwrap();
+        db.get_storage_stat(MetricsStorage::KEY_ENTRY).unwrap();
+        db.get_storage_stat(MetricsStorage::KEY_ENTRY_ID_INDEX).unwrap();
+        db.get_storage_stat(MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX).unwrap();
+        db.get_storage_stat(MetricsStorage::BLOB_ENTRY).unwrap();
+        db.get_storage_stat(MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX).unwrap();
+        db.get_storage_stat(MetricsStorage::KEY_PARAMETER).unwrap();
+        db.get_storage_stat(MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX).unwrap();
+        db.get_storage_stat(MetricsStorage::KEY_METADATA).unwrap();
+        db.get_storage_stat(MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX).unwrap();
+        db.get_storage_stat(MetricsStorage::GRANT).unwrap();
+        db.get_storage_stat(MetricsStorage::AUTH_TOKEN).unwrap();
+        db.get_storage_stat(MetricsStorage::BLOB_METADATA).unwrap();
+        db.get_storage_stat(MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX).unwrap();
+    })
+}
+
+#[test]
+fn test_list_keys_with_many_keys() -> Result<()> {
+    run_with_many_keys(1_000_000, |db: &mut KeystoreDB| -> Result<()> {
+        // Behave equivalently to how clients list aliases.
+        let domain = Domain::APP;
+        let namespace = 10001;
+        let mut start_past: Option<String> = None;
+        let mut count = 0;
+        let mut batches = 0;
+        loop {
+            let keys = db
+                .list_past_alias(domain, namespace, KeyType::Client, start_past.as_deref())
+                .unwrap();
+            let batch_size = crate::utils::estimate_safe_amount_to_return(
+                domain,
+                namespace,
+                None,
+                &keys,
+                crate::utils::RESPONSE_SIZE_LIMIT,
+            );
+            let batch = &keys[..batch_size];
+            count += batch.len();
+            match batch.last() {
+                Some(key) => start_past.clone_from(&key.alias),
+                None => {
+                    log::info!("got {count} keys in {batches} non-empty batches");
+                    return Ok(());
+                }
+            }
+            batches += 1;
+        }
+    })
+}
+
+#[test]
+fn test_upgrade_1_to_2_with_many_keys() -> Result<()> {
+    prep_and_run_with_many_keys(
+        1_000_000,
+        |db: &mut KeystoreDB| {
+            // Manually downgrade the database to the v1 schema.
+            db.with_transaction(Immediate("TX_downgrade_2_to_1"), |tx| {
+                tx.execute("DROP INDEX persistent.keyentry_state_index;", params!())?;
+                tx.execute("DROP INDEX persistent.blobentry_state_index;", params!())?;
+                tx.execute("ALTER TABLE persistent.blobentry DROP COLUMN state;", params!())?;
+                Ok(()).no_gc()
+            })
+            .unwrap();
+        },
+        |db: &mut KeystoreDB| -> Result<()> {
+            // Run the upgrade process.
+            db.with_transaction(Immediate("TX_upgrade_1_to_2"), |tx| {
+                KeystoreDB::from_1_to_2(tx).no_gc()
+            })?;
+            Ok(())
+        },
+    )
+}
diff --git a/keystore2/src/database/versioning.rs b/keystore2/src/database/versioning.rs
index 2c816f4..a047cf3 100644
--- a/keystore2/src/database/versioning.rs
+++ b/keystore2/src/database/versioning.rs
@@ -15,7 +15,7 @@
 use anyhow::{anyhow, Context, Result};
 use rusqlite::{params, OptionalExtension, Transaction};
 
-pub fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32> {
+fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32> {
     tx.execute(
         "CREATE TABLE IF NOT EXISTS persistent.version (
                 id INTEGER PRIMARY KEY,
@@ -61,7 +61,7 @@
     Ok(version)
 }
 
-pub fn update_version(tx: &Transaction, new_version: u32) -> Result<()> {
+pub(crate) fn update_version(tx: &Transaction, new_version: u32) -> Result<()> {
     let updated = tx
         .execute("UPDATE persistent.version SET version = ? WHERE id = 0;", params![new_version])
         .context("In update_version: Failed to update row.")?;
@@ -82,9 +82,11 @@
     let mut db_version = create_or_get_version(tx, current_version)
         .context("In upgrade_database: Failed to get database version.")?;
     while db_version < current_version {
+        log::info!("Current DB version={db_version}, perform upgrade");
         db_version = upgraders[db_version as usize](tx).with_context(|| {
             format!("In upgrade_database: Trying to upgrade from db version {}.", db_version)
         })?;
+        log::info!("DB upgrade successful, current DB version now={db_version}");
     }
     update_version(tx, db_version).context("In upgrade_database.")
 }
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 7038323..d086dd2 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -545,8 +545,9 @@
             || (user_auth_type.is_none() && !user_secure_ids.is_empty())
         {
             return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!(
-                "Auth required, but either auth type or secure ids \
-                 are not present."
+                "Auth required, but auth type {:?} + sids {:?} inconsistently specified",
+                user_auth_type,
+                user_secure_ids,
             ));
         }
 
@@ -582,17 +583,36 @@
                 None => false, // not reachable due to earlier check
             })
             .ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED))
-            .context(ks_err!("No suitable auth token found."))?;
+            .context(ks_err!(
+                "No suitable auth token for sids {:?} type {:?} received in last {}s found.",
+                user_secure_ids,
+                user_auth_type,
+                key_time_out
+            ))?;
             let now = BootTime::now();
             let token_age =
                 now.checked_sub(&hat.time_received()).ok_or_else(Error::sys).context(ks_err!(
-                    "Overflow while computing Auth token validity. \
-                Validity cannot be established."
+                    "Overflow while computing Auth token validity. Validity cannot be established."
                 ))?;
 
             if token_age.seconds() > key_time_out {
-                return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED))
-                    .context(ks_err!("matching auth token is expired."));
+                return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!(
+                    concat!(
+                        "matching auth token (challenge={}, userId={}, authId={}, ",
+                        "authType={:#x}, timestamp={}ms) rcved={:?} ",
+                        "for sids {:?} type {:?} is expired ({}s old > timeout={}s)"
+                    ),
+                    hat.auth_token().challenge,
+                    hat.auth_token().userId,
+                    hat.auth_token().authenticatorId,
+                    hat.auth_token().authenticatorType.0,
+                    hat.auth_token().timestamp.milliSeconds,
+                    hat.time_received(),
+                    user_secure_ids,
+                    user_auth_type,
+                    token_age.seconds(),
+                    key_time_out
+                ));
             }
             let state = if requires_timestamp {
                 DeferredAuthState::TimeStampRequired(hat.auth_token().clone())
@@ -633,16 +653,12 @@
     /// Check if the device is locked for the given user. If there's no entry yet for the user,
     /// we assume that the device is locked
     fn is_device_locked(&self, user_id: i32) -> bool {
-        // unwrap here because there's no way this mutex guard can be poisoned and
-        // because there's no way to recover, even if it is poisoned.
         let set = self.device_unlocked_set.lock().unwrap();
         !set.contains(&user_id)
     }
 
     /// Sets the device locked status for the user. This method is called externally.
     pub fn set_device_locked(&self, user_id: i32, device_locked_status: bool) {
-        // unwrap here because there's no way this mutex guard can be poisoned and
-        // because there's no way to recover, even if it is poisoned.
         let mut set = self.device_unlocked_set.lock().unwrap();
         if device_locked_status {
             set.remove(&user_id);
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index cea4d6b..d57ba0c 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -34,10 +34,14 @@
     ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode,
 };
 use keystore2_selinux as selinux;
+use postprocessor_client::Error as PostProcessorError;
 use rkpd_client::Error as RkpdError;
 use std::cmp::PartialEq;
 use std::ffi::CString;
 
+#[cfg(test)]
+pub mod tests;
+
 /// This is the main Keystore error type. It wraps the Keystore `ResponseCode` generated
 /// from AIDL in the `Rc` variant and Keymint `ErrorCode` in the Km variant.
 #[derive(Debug, thiserror::Error, PartialEq, Eq)]
@@ -100,6 +104,14 @@
     }
 }
 
+impl From<PostProcessorError> for Error {
+    fn from(e: PostProcessorError) -> Self {
+        match e {
+            PostProcessorError(s) => Error::BinderTransaction(s),
+        }
+    }
+}
+
 /// Maps an `rkpd_client::Error` that is wrapped with an `anyhow::Error` to a keystore2 `Error`.
 pub fn wrapped_rkpd_error_to_ks_error(e: &anyhow::Error) -> Error {
     match e.downcast_ref::<RkpdError>() {
@@ -232,210 +244,3 @@
         },
     }
 }
-
-#[cfg(test)]
-pub mod tests {
-
-    use super::*;
-    use android_system_keystore2::binder::{
-        ExceptionCode, Result as BinderResult, Status as BinderStatus,
-    };
-    use anyhow::{anyhow, Context};
-
-    fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
-        Err(anyhow!(Error::Rc(rc))).context("nested nested rc")
-    }
-
-    fn nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
-        nested_nested_rc(rc).context("nested rc")
-    }
-
-    fn nested_nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
-        Err(anyhow!(Error::Km(ec))).context("nested nested ec")
-    }
-
-    fn nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
-        nested_nested_ec(ec).context("nested ec")
-    }
-
-    fn nested_nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
-        Ok(rc)
-    }
-
-    fn nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
-        nested_nested_ok(rc).context("nested ok")
-    }
-
-    fn nested_nested_selinux_perm() -> anyhow::Result<()> {
-        Err(anyhow!(selinux::Error::perm())).context("nested nexted selinux permission denied")
-    }
-
-    fn nested_selinux_perm() -> anyhow::Result<()> {
-        nested_nested_selinux_perm().context("nested selinux permission denied")
-    }
-
-    #[derive(Debug, thiserror::Error)]
-    enum TestError {
-        #[error("TestError::Fail")]
-        Fail = 0,
-    }
-
-    fn nested_nested_other_error() -> anyhow::Result<()> {
-        Err(anyhow!(TestError::Fail)).context("nested nested other error")
-    }
-
-    fn nested_other_error() -> anyhow::Result<()> {
-        nested_nested_other_error().context("nested other error")
-    }
-
-    fn binder_sse_error(sse: i32) -> BinderResult<()> {
-        Err(BinderStatus::new_service_specific_error(sse, None))
-    }
-
-    fn binder_exception(ex: ExceptionCode) -> BinderResult<()> {
-        Err(BinderStatus::new_exception(ex, None))
-    }
-
-    #[test]
-    fn keystore_error_test() -> anyhow::Result<(), String> {
-        android_logger::init_once(
-            android_logger::Config::default()
-                .with_tag("keystore_error_tests")
-                .with_max_level(log::LevelFilter::Debug),
-        );
-        // All Error::Rc(x) get mapped on a service specific error
-        // code of x.
-        for rc in ResponseCode::LOCKED.0..ResponseCode::BACKEND_BUSY.0 {
-            assert_eq!(
-                Result::<(), i32>::Err(rc),
-                nested_rc(ResponseCode(rc))
-                    .map_err(into_logged_binder)
-                    .map_err(|s| s.service_specific_error())
-            );
-        }
-
-        // All Keystore Error::Km(x) get mapped on a service
-        // specific error of x.
-        for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
-            assert_eq!(
-                Result::<(), i32>::Err(ec),
-                nested_ec(ErrorCode(ec))
-                    .map_err(into_logged_binder)
-                    .map_err(|s| s.service_specific_error())
-            );
-        }
-
-        // All Keymint errors x received through a Binder Result get mapped on
-        // a service specific error of x.
-        for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
-            assert_eq!(
-                Result::<(), i32>::Err(ec),
-                map_km_error(binder_sse_error(ec))
-                    .with_context(|| format!("Km error code: {}.", ec))
-                    .map_err(into_logged_binder)
-                    .map_err(|s| s.service_specific_error())
-            );
-        }
-
-        // map_km_error creates an Error::Binder variant storing
-        // ExceptionCode::SERVICE_SPECIFIC and the given
-        // service specific error.
-        let sse = map_km_error(binder_sse_error(1));
-        assert_eq!(Err(Error::Binder(ExceptionCode::SERVICE_SPECIFIC, 1)), sse);
-        // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
-        assert_eq!(
-            Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
-            sse.context("Non negative service specific error.")
-                .map_err(into_logged_binder)
-                .map_err(|s| ResponseCode(s.service_specific_error()))
-        );
-
-        // map_km_error creates a Error::Binder variant storing the given exception code.
-        let binder_exception = map_km_error(binder_exception(ExceptionCode::TRANSACTION_FAILED));
-        assert_eq!(Err(Error::Binder(ExceptionCode::TRANSACTION_FAILED, 0)), binder_exception);
-        // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
-        assert_eq!(
-            Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
-            binder_exception
-                .context("Binder Exception.")
-                .map_err(into_logged_binder)
-                .map_err(|s| ResponseCode(s.service_specific_error()))
-        );
-
-        // selinux::Error::Perm() needs to be mapped to ResponseCode::PERMISSION_DENIED
-        assert_eq!(
-            Result::<(), ResponseCode>::Err(ResponseCode::PERMISSION_DENIED),
-            nested_selinux_perm()
-                .map_err(into_logged_binder)
-                .map_err(|s| ResponseCode(s.service_specific_error()))
-        );
-
-        // All other errors get mapped on System Error.
-        assert_eq!(
-            Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
-            nested_other_error()
-                .map_err(into_logged_binder)
-                .map_err(|s| ResponseCode(s.service_specific_error()))
-        );
-
-        // Result::Ok variants get passed to the ok handler.
-        assert_eq!(
-            Ok(ResponseCode::LOCKED),
-            nested_ok(ResponseCode::LOCKED).map_err(into_logged_binder)
-        );
-        assert_eq!(
-            Ok(ResponseCode::SYSTEM_ERROR),
-            nested_ok(ResponseCode::SYSTEM_ERROR).map_err(into_logged_binder)
-        );
-
-        Ok(())
-    }
-
-    //Helper function to test whether error cases are handled as expected.
-    pub fn check_result_contains_error_string<T>(
-        result: anyhow::Result<T>,
-        expected_error_string: &str,
-    ) {
-        let error_str = format!(
-            "{:#?}",
-            result.err().unwrap_or_else(|| panic!("Expected the error: {}", expected_error_string))
-        );
-        assert!(
-            error_str.contains(expected_error_string),
-            "The string \"{}\" should contain \"{}\"",
-            error_str,
-            expected_error_string
-        );
-    }
-
-    #[test]
-    fn rkpd_error_is_in_sync_with_response_code() {
-        let error_mapping = [
-            (RkpdError::RequestCancelled, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
-            (RkpdError::GetRegistrationFailed, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
-            (
-                RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_UNKNOWN),
-                ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR,
-            ),
-            (
-                RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PERMANENT),
-                ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR,
-            ),
-            (
-                RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY),
-                ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY,
-            ),
-            (
-                RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH),
-                ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE,
-            ),
-            (RkpdError::StoreUpgradedKeyFailed, ResponseCode::SYSTEM_ERROR),
-            (RkpdError::RetryableTimeout, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
-            (RkpdError::Timeout, ResponseCode::SYSTEM_ERROR),
-        ];
-        for (rkpd_error, expected_response_code) in error_mapping {
-            let e: Error = rkpd_error.into();
-            assert_eq!(e, Error::Rc(expected_response_code));
-        }
-    }
-} // mod tests
diff --git a/keystore2/src/error/tests.rs b/keystore2/src/error/tests.rs
new file mode 100644
index 0000000..d50091b
--- /dev/null
+++ b/keystore2/src/error/tests.rs
@@ -0,0 +1,218 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Error handling tests.
+
+use super::*;
+use android_system_keystore2::binder::{
+    ExceptionCode, Result as BinderResult, Status as BinderStatus,
+};
+use anyhow::{anyhow, Context};
+
+fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
+    Err(anyhow!(Error::Rc(rc))).context("nested nested rc")
+}
+
+fn nested_rc(rc: ResponseCode) -> anyhow::Result<()> {
+    nested_nested_rc(rc).context("nested rc")
+}
+
+fn nested_nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
+    Err(anyhow!(Error::Km(ec))).context("nested nested ec")
+}
+
+fn nested_ec(ec: ErrorCode) -> anyhow::Result<()> {
+    nested_nested_ec(ec).context("nested ec")
+}
+
+fn nested_nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
+    Ok(rc)
+}
+
+fn nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> {
+    nested_nested_ok(rc).context("nested ok")
+}
+
+fn nested_nested_selinux_perm() -> anyhow::Result<()> {
+    Err(anyhow!(selinux::Error::perm())).context("nested nexted selinux permission denied")
+}
+
+fn nested_selinux_perm() -> anyhow::Result<()> {
+    nested_nested_selinux_perm().context("nested selinux permission denied")
+}
+
+#[derive(Debug, thiserror::Error)]
+enum TestError {
+    #[error("TestError::Fail")]
+    Fail = 0,
+}
+
+fn nested_nested_other_error() -> anyhow::Result<()> {
+    Err(anyhow!(TestError::Fail)).context("nested nested other error")
+}
+
+fn nested_other_error() -> anyhow::Result<()> {
+    nested_nested_other_error().context("nested other error")
+}
+
+fn binder_sse_error(sse: i32) -> BinderResult<()> {
+    Err(BinderStatus::new_service_specific_error(sse, None))
+}
+
+fn binder_exception(ex: ExceptionCode) -> BinderResult<()> {
+    Err(BinderStatus::new_exception(ex, None))
+}
+
+#[test]
+fn keystore_error_test() -> anyhow::Result<(), String> {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("keystore_error_tests")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+    // All Error::Rc(x) get mapped on a service specific error
+    // code of x.
+    for rc in ResponseCode::LOCKED.0..ResponseCode::BACKEND_BUSY.0 {
+        assert_eq!(
+            Result::<(), i32>::Err(rc),
+            nested_rc(ResponseCode(rc))
+                .map_err(into_logged_binder)
+                .map_err(|s| s.service_specific_error())
+        );
+    }
+
+    // All Keystore Error::Km(x) get mapped on a service
+    // specific error of x.
+    for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
+        assert_eq!(
+            Result::<(), i32>::Err(ec),
+            nested_ec(ErrorCode(ec))
+                .map_err(into_logged_binder)
+                .map_err(|s| s.service_specific_error())
+        );
+    }
+
+    // All Keymint errors x received through a Binder Result get mapped on
+    // a service specific error of x.
+    for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 {
+        assert_eq!(
+            Result::<(), i32>::Err(ec),
+            map_km_error(binder_sse_error(ec))
+                .with_context(|| format!("Km error code: {}.", ec))
+                .map_err(into_logged_binder)
+                .map_err(|s| s.service_specific_error())
+        );
+    }
+
+    // map_km_error creates an Error::Binder variant storing
+    // ExceptionCode::SERVICE_SPECIFIC and the given
+    // service specific error.
+    let sse = map_km_error(binder_sse_error(1));
+    assert_eq!(Err(Error::Binder(ExceptionCode::SERVICE_SPECIFIC, 1)), sse);
+    // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
+    assert_eq!(
+        Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
+        sse.context("Non negative service specific error.")
+            .map_err(into_logged_binder)
+            .map_err(|s| ResponseCode(s.service_specific_error()))
+    );
+
+    // map_km_error creates a Error::Binder variant storing the given exception code.
+    let binder_exception = map_km_error(binder_exception(ExceptionCode::TRANSACTION_FAILED));
+    assert_eq!(Err(Error::Binder(ExceptionCode::TRANSACTION_FAILED, 0)), binder_exception);
+    // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR.
+    assert_eq!(
+        Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
+        binder_exception
+            .context("Binder Exception.")
+            .map_err(into_logged_binder)
+            .map_err(|s| ResponseCode(s.service_specific_error()))
+    );
+
+    // selinux::Error::Perm() needs to be mapped to ResponseCode::PERMISSION_DENIED
+    assert_eq!(
+        Result::<(), ResponseCode>::Err(ResponseCode::PERMISSION_DENIED),
+        nested_selinux_perm()
+            .map_err(into_logged_binder)
+            .map_err(|s| ResponseCode(s.service_specific_error()))
+    );
+
+    // All other errors get mapped on System Error.
+    assert_eq!(
+        Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR),
+        nested_other_error()
+            .map_err(into_logged_binder)
+            .map_err(|s| ResponseCode(s.service_specific_error()))
+    );
+
+    // Result::Ok variants get passed to the ok handler.
+    assert_eq!(
+        Ok(ResponseCode::LOCKED),
+        nested_ok(ResponseCode::LOCKED).map_err(into_logged_binder)
+    );
+    assert_eq!(
+        Ok(ResponseCode::SYSTEM_ERROR),
+        nested_ok(ResponseCode::SYSTEM_ERROR).map_err(into_logged_binder)
+    );
+
+    Ok(())
+}
+
+//Helper function to test whether error cases are handled as expected.
+pub fn check_result_contains_error_string<T>(
+    result: anyhow::Result<T>,
+    expected_error_string: &str,
+) {
+    let error_str = format!(
+        "{:#?}",
+        result.err().unwrap_or_else(|| panic!("Expected the error: {}", expected_error_string))
+    );
+    assert!(
+        error_str.contains(expected_error_string),
+        "The string \"{}\" should contain \"{}\"",
+        error_str,
+        expected_error_string
+    );
+}
+
+#[test]
+fn rkpd_error_is_in_sync_with_response_code() {
+    let error_mapping = [
+        (RkpdError::RequestCancelled, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
+        (RkpdError::GetRegistrationFailed, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
+        (
+            RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_UNKNOWN),
+            ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR,
+        ),
+        (
+            RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PERMANENT),
+            ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR,
+        ),
+        (
+            RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY),
+            ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY,
+        ),
+        (
+            RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH),
+            ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE,
+        ),
+        (RkpdError::StoreUpgradedKeyFailed, ResponseCode::SYSTEM_ERROR),
+        (RkpdError::RetryableTimeout, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR),
+        (RkpdError::Timeout, ResponseCode::SYSTEM_ERROR),
+    ];
+    for (rkpd_error, expected_response_code) in error_mapping {
+        let e: Error = rkpd_error.into();
+        assert_eq!(e, Error::Rc(expected_response_code));
+    }
+}
diff --git a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
index fb4c9ad..62167fb 100644
--- a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
+++ b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
@@ -26,7 +26,7 @@
     hmac_sha256, parse_subject_from_certificate, Password, ZVec,
 };
 use keystore2_hal_names::get_hidl_instances;
-use keystore2_selinux::{check_access, getpidcon, setcon, Backend, Context, KeystoreKeyBackend};
+use keystore2_selinux::{check_access, setcon, Backend, Context, KeystoreKeyBackend};
 use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target};
 use std::{ffi::CString, sync::Arc};
 
@@ -108,9 +108,6 @@
     Backend {
         namespace: &'a str,
     },
-    GetPidCon {
-        pid: i32,
-    },
     CheckAccess {
         source: &'a [u8],
         target: &'a [u8],
@@ -216,9 +213,6 @@
                     let _res = backend.lookup(namespace);
                 }
             }
-            FuzzCommand::GetPidCon { pid } => {
-                let _res = getpidcon(pid);
-            }
             FuzzCommand::CheckAccess { source, target, tclass, perm } => {
                 let source = get_valid_cstring_data(source);
                 let target = get_valid_cstring_data(target);
diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs
index f2341e3..9741671 100644
--- a/keystore2/src/gc.rs
+++ b/keystore2/src/gc.rs
@@ -22,6 +22,7 @@
 use crate::{
     async_task,
     database::{KeystoreDB, SupersededBlob, Uuid},
+    globals,
     super_key::SuperKeyManager,
 };
 use anyhow::{Context, Result};
@@ -135,6 +136,17 @@
     /// Processes one key and then schedules another attempt until it runs out of blobs to delete.
     fn step(&mut self) {
         self.notified.store(0, Ordering::Relaxed);
+        if !globals::boot_completed() {
+            // Garbage collection involves a operation (`IKeyMintDevice::deleteKey()`) that cannot
+            // be rolled back in some cases (specifically, when the key is rollback-resistant), even
+            // if the Keystore database is restored to the version of an earlier userdata filesystem
+            // checkpoint.
+            //
+            // This means that we should not perform GC until boot has fully completed, and any
+            // in-progress OTA is definitely not going to be rolled back.
+            log::info!("skip GC as boot not completed");
+            return;
+        }
         if let Err(e) = self.process_one_key() {
             log::error!("Error trying to delete blob entry. {:?}", e);
         }
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index c7b495d..9ee2a1e 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -23,7 +23,7 @@
 use crate::legacy_blob::LegacyBlobLoader;
 use crate::legacy_importer::LegacyImporter;
 use crate::super_key::SuperKeyManager;
-use crate::utils::watchdog as wd;
+use crate::utils::{retry_get_interface, watchdog as wd};
 use crate::{
     database::KeystoreDB,
     database::Uuid,
@@ -46,8 +46,11 @@
 use anyhow::{Context, Result};
 use binder::FromIBinder;
 use binder::{get_declared_instances, is_declared};
-use lazy_static::lazy_static;
-use std::sync::{Arc, Mutex, RwLock};
+use rustutils::system_properties::PropertyWatcher;
+use std::sync::{
+    atomic::{AtomicBool, Ordering},
+    Arc, LazyLock, Mutex, RwLock,
+};
 use std::{cell::RefCell, sync::Once};
 use std::{collections::HashMap, path::Path, path::PathBuf};
 
@@ -62,21 +65,25 @@
 /// is run only once, as long as the ASYNC_TASK instance is the same. So only one additional
 /// database connection is created for the garbage collector worker.
 pub fn create_thread_local_db() -> KeystoreDB {
-    let db_path = DB_PATH.read().expect("Could not get the database directory.");
+    let db_path = DB_PATH.read().expect("Could not get the database directory");
 
-    let mut db = KeystoreDB::new(&db_path, Some(GC.clone())).expect("Failed to open database.");
+    let result = KeystoreDB::new(&db_path, Some(GC.clone()));
+    let mut db = match result {
+        Ok(db) => db,
+        Err(e) => {
+            log::error!("Failed to open Keystore database at {db_path:?}: {e:?}");
+            log::error!("Has /data been mounted correctly?");
+            panic!("Failed to open database for Keystore, cannot continue: {e:?}")
+        }
+    };
 
     DB_INIT.call_once(|| {
         log::info!("Touching Keystore 2.0 database for this first time since boot.");
         log::info!("Calling cleanup leftovers.");
-        let n = db.cleanup_leftovers().expect("Failed to cleanup database on startup.");
+        let n = db.cleanup_leftovers().expect("Failed to cleanup database on startup");
         if n != 0 {
             log::info!(
-                concat!(
-                    "Cleaned up {} failed entries. ",
-                    "This indicates keystore crashed during key generation."
-                ),
-                n
+                "Cleaned up {n} failed entries, indicating keystore crash on key generation"
             );
         }
     });
@@ -88,8 +95,7 @@
     /// same database multiple times is safe as long as each connection is
     /// used by only one thread. So we store one database connection per
     /// thread in this thread local key.
-    pub static DB: RefCell<KeystoreDB> =
-            RefCell::new(create_thread_local_db());
+    pub static DB: RefCell<KeystoreDB> = RefCell::new(create_thread_local_db());
 }
 
 struct DevicesMap<T: FromIBinder + ?Sized> {
@@ -136,45 +142,54 @@
     }
 }
 
-lazy_static! {
-    /// The path where keystore stores all its keys.
-    pub static ref DB_PATH: RwLock<PathBuf> = RwLock::new(
-        Path::new("/data/misc/keystore").to_path_buf());
-    /// Runtime database of unwrapped super keys.
-    pub static ref SUPER_KEY: Arc<RwLock<SuperKeyManager>> = Default::default();
-    /// Map of KeyMint devices.
-    static ref KEY_MINT_DEVICES: Mutex<DevicesMap<dyn IKeyMintDevice>> = Default::default();
-    /// Timestamp service.
-    static ref TIME_STAMP_DEVICE: Mutex<Option<Strong<dyn ISecureClock>>> = Default::default();
-    /// A single on-demand worker thread that handles deferred tasks with two different
-    /// priorities.
-    pub static ref ASYNC_TASK: Arc<AsyncTask> = Default::default();
-    /// Singleton for enforcements.
-    pub static ref ENFORCEMENTS: Enforcements = Default::default();
-    /// LegacyBlobLoader is initialized and exists globally.
-    /// The same directory used by the database is used by the LegacyBlobLoader as well.
-    pub static ref LEGACY_BLOB_LOADER: Arc<LegacyBlobLoader> = Arc::new(LegacyBlobLoader::new(
-        &DB_PATH.read().expect("Could not get the database path for legacy blob loader.")));
-    /// Legacy migrator. Atomically migrates legacy blobs to the database.
-    pub static ref LEGACY_IMPORTER: Arc<LegacyImporter> =
-        Arc::new(LegacyImporter::new(Arc::new(Default::default())));
-    /// Background thread which handles logging via statsd and logd
-    pub static ref LOGS_HANDLER: Arc<AsyncTask> = Default::default();
+/// The path where keystore stores all its keys.
+pub static DB_PATH: LazyLock<RwLock<PathBuf>> =
+    LazyLock::new(|| RwLock::new(Path::new("/data/misc/keystore").to_path_buf()));
+/// Runtime database of unwrapped super keys.
+pub static SUPER_KEY: LazyLock<Arc<RwLock<SuperKeyManager>>> = LazyLock::new(Default::default);
+/// Map of KeyMint devices.
+static KEY_MINT_DEVICES: LazyLock<Mutex<DevicesMap<dyn IKeyMintDevice>>> =
+    LazyLock::new(Default::default);
+/// Timestamp service.
+static TIME_STAMP_DEVICE: Mutex<Option<Strong<dyn ISecureClock>>> = Mutex::new(None);
+/// A single on-demand worker thread that handles deferred tasks with two different
+/// priorities.
+pub static ASYNC_TASK: LazyLock<Arc<AsyncTask>> = LazyLock::new(Default::default);
+/// Singleton for enforcements.
+pub static ENFORCEMENTS: LazyLock<Enforcements> = LazyLock::new(Default::default);
+/// LegacyBlobLoader is initialized and exists globally.
+/// The same directory used by the database is used by the LegacyBlobLoader as well.
+pub static LEGACY_BLOB_LOADER: LazyLock<Arc<LegacyBlobLoader>> = LazyLock::new(|| {
+    Arc::new(LegacyBlobLoader::new(
+        &DB_PATH.read().expect("Could not determine database path for legacy blob loader"),
+    ))
+});
+/// Legacy migrator. Atomically migrates legacy blobs to the database.
+pub static LEGACY_IMPORTER: LazyLock<Arc<LegacyImporter>> =
+    LazyLock::new(|| Arc::new(LegacyImporter::new(Arc::new(Default::default()))));
+/// Background thread which handles logging via statsd and logd
+pub static LOGS_HANDLER: LazyLock<Arc<AsyncTask>> = LazyLock::new(Default::default);
+/// DER-encoded module information returned by `getSupplementaryAttestationInfo(Tag.MODULE_HASH)`.
+pub static ENCODED_MODULE_INFO: RwLock<Option<Vec<u8>>> = RwLock::new(None);
 
-    static ref GC: Arc<Gc> = Arc::new(Gc::new_init_with(ASYNC_TASK.clone(), || {
+static GC: LazyLock<Arc<Gc>> = LazyLock::new(|| {
+    Arc::new(Gc::new_init_with(ASYNC_TASK.clone(), || {
         (
             Box::new(|uuid, blob| {
                 let km_dev = get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?;
-                let _wp = wd::watch("In invalidate key closure: calling deleteKey");
+                let _wp = wd::watch("invalidate key closure: calling IKeyMintDevice::deleteKey");
                 map_km_error(km_dev.deleteKey(blob))
                     .context(ks_err!("Trying to invalidate key blob."))
             }),
-            KeystoreDB::new(&DB_PATH.read().expect("Could not get the database directory."), None)
-                .expect("Failed to open database."),
+            KeystoreDB::new(
+                &DB_PATH.read().expect("Could not determine database path for GC"),
+                None,
+            )
+            .expect("Failed to open database"),
             SUPER_KEY.clone(),
         )
-    }));
-}
+    }))
+});
 
 /// Determine the service name for a KeyMint device of the given security level
 /// gotten by binder service from the device and determining what services
@@ -222,8 +237,12 @@
 
     let (keymint, hal_version) = if let Some(service_name) = service_name {
         let km: Strong<dyn IKeyMintDevice> =
-            map_binder_status_code(binder::get_interface(&service_name))
-                .context(ks_err!("Trying to connect to genuine KeyMint service."))?;
+            if SecurityLevel::TRUSTED_ENVIRONMENT == *security_level {
+                map_binder_status_code(retry_get_interface(&service_name))
+            } else {
+                map_binder_status_code(binder::get_interface(&service_name))
+            }
+            .context(ks_err!("Trying to connect to genuine KeyMint service."))?;
         // Map the HAL version code for KeyMint to be <AIDL version> * 100, so
         // - V1 is 100
         // - V2 is 200
@@ -258,17 +277,8 @@
     // If the KeyMint device is back-level, use a wrapper that intercepts and
     // emulates things that are not supported by the hardware.
     let keymint = match hal_version {
-        Some(300) => {
-            // Current KeyMint version: use as-is as v3 Keymint is current version
-            log::info!(
-                "KeyMint device is current version ({:?}) for security level: {:?}",
-                hal_version,
-                security_level
-            );
-            keymint
-        }
-        Some(200) => {
-            // Previous KeyMint version: use as-is as we don't have any software emulation of v3-specific KeyMint features.
+        Some(400) | Some(300) | Some(200) => {
+            // KeyMint v2+: use as-is (we don't have any software emulation of v3 or v4-specific KeyMint features).
             log::info!(
                 "KeyMint device is current version ({:?}) for security level: {:?}",
                 hal_version,
@@ -306,7 +316,7 @@
         }
     };
 
-    let wp = wd::watch("In connect_keymint: calling getHardwareInfo()");
+    let wp = wd::watch("connect_keymint: calling IKeyMintDevice::getHardwareInfo()");
     let mut hw_info =
         map_km_error(keymint.getHardwareInfo()).context(ks_err!("Failed to get hardware info."))?;
     drop(wp);
@@ -443,3 +453,40 @@
     .ok_or(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
     .context(ks_err!("Failed to get rpc for sec level {:?}", *security_level))
 }
+
+/// Whether boot is complete.
+static BOOT_COMPLETED: AtomicBool = AtomicBool::new(false);
+
+/// Indicate whether boot is complete.
+///
+/// This in turn indicates whether it is safe to make permanent changes to state.
+pub fn boot_completed() -> bool {
+    BOOT_COMPLETED.load(Ordering::Acquire)
+}
+
+/// Monitor the system property for boot complete.  This blocks and so needs to be run in a separate
+/// thread.
+pub fn await_boot_completed() {
+    // Use a fairly long watchdog timeout of 5 minutes here. This blocks until the device
+    // boots, which on a very slow device (e.g., emulator for a non-native architecture) can
+    // take minutes. Blocking here would be unexpected only if it never finishes.
+    let _wp = wd::watch_millis("await_boot_completed", 300_000);
+    log::info!("monitoring for sys.boot_completed=1");
+    while let Err(e) = watch_for_boot_completed() {
+        log::error!("failed to watch for boot_completed: {e:?}");
+        std::thread::sleep(std::time::Duration::from_secs(5));
+    }
+
+    BOOT_COMPLETED.store(true, Ordering::Release);
+    log::info!("wait_for_boot_completed done, triggering GC");
+
+    // Garbage collection may have been skipped until now, so trigger a check.
+    GC.notify_gc();
+}
+
+fn watch_for_boot_completed() -> Result<()> {
+    let mut w = PropertyWatcher::new("sys.boot_completed")
+        .context(ks_err!("PropertyWatcher::new failed"))?;
+    w.wait_for_value("1", None).context(ks_err!("Failed to wait for sys.boot_completed"))?;
+    Ok(())
+}
diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs
index bd45207..466fb50 100644
--- a/keystore2/src/key_parameter.rs
+++ b/keystore2/src/key_parameter.rs
@@ -111,6 +111,18 @@
 use serde::ser::Serializer;
 use serde::{Deserialize, Serialize};
 
+#[cfg(test)]
+mod generated_key_parameter_tests;
+
+#[cfg(test)]
+mod basic_tests;
+
+#[cfg(test)]
+mod storage_tests;
+
+#[cfg(test)]
+mod wire_tests;
+
 /// This trait is used to associate a primitive to any type that can be stored inside a
 /// KeyParameterValue, especially the AIDL enum types, e.g., keymint::{Algorithm, Digest, ...}.
 /// This allows for simplifying the macro rules, e.g., for reading from the SQL database.
@@ -1091,490 +1103,3 @@
         Authorization { securityLevel: self.security_level, keyParameter: self.value.into() }
     }
 }
-
-#[cfg(test)]
-mod generated_key_parameter_tests {
-    use super::*;
-    use android_hardware_security_keymint::aidl::android::hardware::security::keymint::TagType::TagType;
-
-    fn get_field_by_tag_type(tag: Tag) -> KmKeyParameterValue {
-        let tag_type = TagType((tag.0 as u32 & 0xF0000000) as i32);
-        match tag {
-            Tag::ALGORITHM => return KmKeyParameterValue::Algorithm(Default::default()),
-            Tag::BLOCK_MODE => return KmKeyParameterValue::BlockMode(Default::default()),
-            Tag::PADDING => return KmKeyParameterValue::PaddingMode(Default::default()),
-            Tag::DIGEST => return KmKeyParameterValue::Digest(Default::default()),
-            Tag::RSA_OAEP_MGF_DIGEST => return KmKeyParameterValue::Digest(Default::default()),
-            Tag::EC_CURVE => return KmKeyParameterValue::EcCurve(Default::default()),
-            Tag::ORIGIN => return KmKeyParameterValue::Origin(Default::default()),
-            Tag::PURPOSE => return KmKeyParameterValue::KeyPurpose(Default::default()),
-            Tag::USER_AUTH_TYPE => {
-                return KmKeyParameterValue::HardwareAuthenticatorType(Default::default())
-            }
-            Tag::HARDWARE_TYPE => return KmKeyParameterValue::SecurityLevel(Default::default()),
-            _ => {}
-        }
-        match tag_type {
-            TagType::INVALID => return KmKeyParameterValue::Invalid(Default::default()),
-            TagType::ENUM | TagType::ENUM_REP => {}
-            TagType::UINT | TagType::UINT_REP => {
-                return KmKeyParameterValue::Integer(Default::default())
-            }
-            TagType::ULONG | TagType::ULONG_REP => {
-                return KmKeyParameterValue::LongInteger(Default::default())
-            }
-            TagType::DATE => return KmKeyParameterValue::DateTime(Default::default()),
-            TagType::BOOL => return KmKeyParameterValue::BoolValue(Default::default()),
-            TagType::BIGNUM | TagType::BYTES => {
-                return KmKeyParameterValue::Blob(Default::default())
-            }
-            _ => {}
-        }
-        panic!("Unknown tag/tag_type: {:?} {:?}", tag, tag_type);
-    }
-
-    fn check_field_matches_tag_type(list_o_parameters: &[KmKeyParameter]) {
-        for kp in list_o_parameters.iter() {
-            match (&kp.value, get_field_by_tag_type(kp.tag)) {
-                (&KmKeyParameterValue::Algorithm(_), KmKeyParameterValue::Algorithm(_))
-                | (&KmKeyParameterValue::BlockMode(_), KmKeyParameterValue::BlockMode(_))
-                | (&KmKeyParameterValue::PaddingMode(_), KmKeyParameterValue::PaddingMode(_))
-                | (&KmKeyParameterValue::Digest(_), KmKeyParameterValue::Digest(_))
-                | (&KmKeyParameterValue::EcCurve(_), KmKeyParameterValue::EcCurve(_))
-                | (&KmKeyParameterValue::Origin(_), KmKeyParameterValue::Origin(_))
-                | (&KmKeyParameterValue::KeyPurpose(_), KmKeyParameterValue::KeyPurpose(_))
-                | (
-                    &KmKeyParameterValue::HardwareAuthenticatorType(_),
-                    KmKeyParameterValue::HardwareAuthenticatorType(_),
-                )
-                | (&KmKeyParameterValue::SecurityLevel(_), KmKeyParameterValue::SecurityLevel(_))
-                | (&KmKeyParameterValue::Invalid(_), KmKeyParameterValue::Invalid(_))
-                | (&KmKeyParameterValue::Integer(_), KmKeyParameterValue::Integer(_))
-                | (&KmKeyParameterValue::LongInteger(_), KmKeyParameterValue::LongInteger(_))
-                | (&KmKeyParameterValue::DateTime(_), KmKeyParameterValue::DateTime(_))
-                | (&KmKeyParameterValue::BoolValue(_), KmKeyParameterValue::BoolValue(_))
-                | (&KmKeyParameterValue::Blob(_), KmKeyParameterValue::Blob(_)) => {}
-                (actual, expected) => panic!(
-                    "Tag {:?} associated with variant {:?} expected {:?}",
-                    kp.tag, actual, expected
-                ),
-            }
-        }
-    }
-
-    #[test]
-    fn key_parameter_value_field_matches_tag_type() {
-        check_field_matches_tag_type(&KeyParameterValue::make_field_matches_tag_type_test_vector());
-    }
-
-    #[test]
-    fn key_parameter_serialization_test() {
-        let params = KeyParameterValue::make_key_parameter_defaults_vector();
-        let mut out_buffer: Vec<u8> = Default::default();
-        serde_cbor::to_writer(&mut out_buffer, &params)
-            .expect("Failed to serialize key parameters.");
-        let deserialized_params: Vec<KeyParameter> =
-            serde_cbor::from_reader(&mut out_buffer.as_slice())
-                .expect("Failed to deserialize key parameters.");
-        assert_eq!(params, deserialized_params);
-    }
-}
-
-#[cfg(test)]
-mod basic_tests {
-    use crate::key_parameter::*;
-
-    // Test basic functionality of KeyParameter.
-    #[test]
-    fn test_key_parameter() {
-        let key_parameter = KeyParameter::new(
-            KeyParameterValue::Algorithm(Algorithm::RSA),
-            SecurityLevel::STRONGBOX,
-        );
-
-        assert_eq!(key_parameter.get_tag(), Tag::ALGORITHM);
-
-        assert_eq!(
-            *key_parameter.key_parameter_value(),
-            KeyParameterValue::Algorithm(Algorithm::RSA)
-        );
-
-        assert_eq!(*key_parameter.security_level(), SecurityLevel::STRONGBOX);
-    }
-}
-
-/// The storage_tests module first tests the 'new_from_sql' method for KeyParameters of different
-/// data types and then tests 'to_sql' method for KeyParameters of those
-/// different data types. The five different data types for KeyParameter values are:
-/// i) enums of u32
-/// ii) u32
-/// iii) u64
-/// iv) Vec<u8>
-/// v) bool
-#[cfg(test)]
-mod storage_tests {
-    use crate::error::*;
-    use crate::key_parameter::*;
-    use anyhow::Result;
-    use rusqlite::types::ToSql;
-    use rusqlite::{params, Connection};
-
-    /// Test initializing a KeyParameter (with key parameter value corresponding to an enum of i32)
-    /// from a database table row.
-    #[test]
-    fn test_new_from_sql_enum_i32() -> Result<()> {
-        let db = init_db()?;
-        insert_into_keyparameter(
-            &db,
-            1,
-            Tag::ALGORITHM.0,
-            &Algorithm::RSA.0,
-            SecurityLevel::STRONGBOX.0,
-        )?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(Tag::ALGORITHM, key_param.get_tag());
-        assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA));
-        assert_eq!(*key_param.security_level(), SecurityLevel::STRONGBOX);
-        Ok(())
-    }
-
-    /// Test initializing a KeyParameter (with key parameter value which is of i32)
-    /// from a database table row.
-    #[test]
-    fn test_new_from_sql_i32() -> Result<()> {
-        let db = init_db()?;
-        insert_into_keyparameter(&db, 1, Tag::KEY_SIZE.0, &1024, SecurityLevel::STRONGBOX.0)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(Tag::KEY_SIZE, key_param.get_tag());
-        assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::KeySize(1024));
-        Ok(())
-    }
-
-    /// Test initializing a KeyParameter (with key parameter value which is of i64)
-    /// from a database table row.
-    #[test]
-    fn test_new_from_sql_i64() -> Result<()> {
-        let db = init_db()?;
-        // max value for i64, just to test corner cases
-        insert_into_keyparameter(
-            &db,
-            1,
-            Tag::RSA_PUBLIC_EXPONENT.0,
-            &(i64::MAX),
-            SecurityLevel::STRONGBOX.0,
-        )?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(Tag::RSA_PUBLIC_EXPONENT, key_param.get_tag());
-        assert_eq!(
-            *key_param.key_parameter_value(),
-            KeyParameterValue::RSAPublicExponent(i64::MAX)
-        );
-        Ok(())
-    }
-
-    /// Test initializing a KeyParameter (with key parameter value which is of bool)
-    /// from a database table row.
-    #[test]
-    fn test_new_from_sql_bool() -> Result<()> {
-        let db = init_db()?;
-        insert_into_keyparameter(&db, 1, Tag::CALLER_NONCE.0, &Null, SecurityLevel::STRONGBOX.0)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(Tag::CALLER_NONCE, key_param.get_tag());
-        assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::CallerNonce);
-        Ok(())
-    }
-
-    /// Test initializing a KeyParameter (with key parameter value which is of Vec<u8>)
-    /// from a database table row.
-    #[test]
-    fn test_new_from_sql_vec_u8() -> Result<()> {
-        let db = init_db()?;
-        let app_id = String::from("MyAppID");
-        let app_id_bytes = app_id.into_bytes();
-        insert_into_keyparameter(
-            &db,
-            1,
-            Tag::APPLICATION_ID.0,
-            &app_id_bytes,
-            SecurityLevel::STRONGBOX.0,
-        )?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(Tag::APPLICATION_ID, key_param.get_tag());
-        assert_eq!(
-            *key_param.key_parameter_value(),
-            KeyParameterValue::ApplicationID(app_id_bytes)
-        );
-        Ok(())
-    }
-
-    /// Test storing a KeyParameter (with key parameter value which corresponds to an enum of i32)
-    /// in the database
-    #[test]
-    fn test_to_sql_enum_i32() -> Result<()> {
-        let db = init_db()?;
-        let kp = KeyParameter::new(
-            KeyParameterValue::Algorithm(Algorithm::RSA),
-            SecurityLevel::STRONGBOX,
-        );
-        store_keyparameter(&db, 1, &kp)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(kp.get_tag(), key_param.get_tag());
-        assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
-        assert_eq!(kp.security_level(), key_param.security_level());
-        Ok(())
-    }
-
-    /// Test storing a KeyParameter (with key parameter value which is of i32) in the database
-    #[test]
-    fn test_to_sql_i32() -> Result<()> {
-        let db = init_db()?;
-        let kp = KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::STRONGBOX);
-        store_keyparameter(&db, 1, &kp)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(kp.get_tag(), key_param.get_tag());
-        assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
-        assert_eq!(kp.security_level(), key_param.security_level());
-        Ok(())
-    }
-
-    /// Test storing a KeyParameter (with key parameter value which is of i64) in the database
-    #[test]
-    fn test_to_sql_i64() -> Result<()> {
-        let db = init_db()?;
-        // max value for i64, just to test corner cases
-        let kp = KeyParameter::new(
-            KeyParameterValue::RSAPublicExponent(i64::MAX),
-            SecurityLevel::STRONGBOX,
-        );
-        store_keyparameter(&db, 1, &kp)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(kp.get_tag(), key_param.get_tag());
-        assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
-        assert_eq!(kp.security_level(), key_param.security_level());
-        Ok(())
-    }
-
-    /// Test storing a KeyParameter (with key parameter value which is of Vec<u8>) in the database
-    #[test]
-    fn test_to_sql_vec_u8() -> Result<()> {
-        let db = init_db()?;
-        let kp = KeyParameter::new(
-            KeyParameterValue::ApplicationID(String::from("MyAppID").into_bytes()),
-            SecurityLevel::STRONGBOX,
-        );
-        store_keyparameter(&db, 1, &kp)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(kp.get_tag(), key_param.get_tag());
-        assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
-        assert_eq!(kp.security_level(), key_param.security_level());
-        Ok(())
-    }
-
-    /// Test storing a KeyParameter (with key parameter value which is of i32) in the database
-    #[test]
-    fn test_to_sql_bool() -> Result<()> {
-        let db = init_db()?;
-        let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX);
-        store_keyparameter(&db, 1, &kp)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(kp.get_tag(), key_param.get_tag());
-        assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
-        assert_eq!(kp.security_level(), key_param.security_level());
-        Ok(())
-    }
-
-    #[test]
-    /// Test Tag::Invalid
-    fn test_invalid_tag() -> Result<()> {
-        let db = init_db()?;
-        insert_into_keyparameter(&db, 1, 0, &123, 1)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(Tag::INVALID, key_param.get_tag());
-        Ok(())
-    }
-
-    #[test]
-    fn test_non_existing_enum_variant() -> Result<()> {
-        let db = init_db()?;
-        insert_into_keyparameter(&db, 1, 100, &123, 1)?;
-        let key_param = query_from_keyparameter(&db)?;
-        assert_eq!(Tag::INVALID, key_param.get_tag());
-        Ok(())
-    }
-
-    #[test]
-    fn test_invalid_conversion_from_sql() -> Result<()> {
-        let db = init_db()?;
-        insert_into_keyparameter(&db, 1, Tag::ALGORITHM.0, &Null, 1)?;
-        tests::check_result_contains_error_string(
-            query_from_keyparameter(&db),
-            "Failed to read sql data for tag: ALGORITHM.",
-        );
-        Ok(())
-    }
-
-    /// Helper method to init database table for key parameter
-    fn init_db() -> Result<Connection> {
-        let db = Connection::open_in_memory().context("Failed to initialize sqlite connection.")?;
-        db.execute("ATTACH DATABASE ? as 'persistent';", params![""])
-            .context("Failed to attach databases.")?;
-        db.execute(
-            "CREATE TABLE IF NOT EXISTS persistent.keyparameter (
-                                keyentryid INTEGER,
-                                tag INTEGER,
-                                data ANY,
-                                security_level INTEGER);",
-            [],
-        )
-        .context("Failed to initialize \"keyparameter\" table.")?;
-        Ok(db)
-    }
-
-    /// Helper method to insert an entry into key parameter table, with individual parameters
-    fn insert_into_keyparameter<T: ToSql>(
-        db: &Connection,
-        key_id: i64,
-        tag: i32,
-        value: &T,
-        security_level: i32,
-    ) -> Result<()> {
-        db.execute(
-            "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
-                VALUES(?, ?, ?, ?);",
-            params![key_id, tag, *value, security_level],
-        )?;
-        Ok(())
-    }
-
-    /// Helper method to store a key parameter instance.
-    fn store_keyparameter(db: &Connection, key_id: i64, kp: &KeyParameter) -> Result<()> {
-        db.execute(
-            "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
-                VALUES(?, ?, ?, ?);",
-            params![key_id, kp.get_tag().0, kp.key_parameter_value(), kp.security_level().0],
-        )?;
-        Ok(())
-    }
-
-    /// Helper method to query a row from keyparameter table
-    fn query_from_keyparameter(db: &Connection) -> Result<KeyParameter> {
-        let mut stmt =
-            db.prepare("SELECT tag, data, security_level FROM persistent.keyparameter")?;
-        let mut rows = stmt.query([])?;
-        let row = rows.next()?.unwrap();
-        KeyParameter::new_from_sql(
-            Tag(row.get(0)?),
-            &SqlField::new(1, row),
-            SecurityLevel(row.get(2)?),
-        )
-    }
-}
-
-/// The wire_tests module tests the 'convert_to_wire' and 'convert_from_wire' methods for
-/// KeyParameter, for the four different types used in KmKeyParameter, in addition to Invalid
-/// key parameter.
-/// i) bool
-/// ii) integer
-/// iii) longInteger
-/// iv) blob
-#[cfg(test)]
-mod wire_tests {
-    use crate::key_parameter::*;
-    /// unit tests for to conversions
-    #[test]
-    fn test_convert_to_wire_invalid() {
-        let kp = KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::STRONGBOX);
-        assert_eq!(
-            KmKeyParameter { tag: Tag::INVALID, value: KmKeyParameterValue::Invalid(0) },
-            kp.value.into()
-        );
-    }
-    #[test]
-    fn test_convert_to_wire_bool() {
-        let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX);
-        assert_eq!(
-            KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) },
-            kp.value.into()
-        );
-    }
-    #[test]
-    fn test_convert_to_wire_integer() {
-        let kp = KeyParameter::new(
-            KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT),
-            SecurityLevel::STRONGBOX,
-        );
-        assert_eq!(
-            KmKeyParameter {
-                tag: Tag::PURPOSE,
-                value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT)
-            },
-            kp.value.into()
-        );
-    }
-    #[test]
-    fn test_convert_to_wire_long_integer() {
-        let kp =
-            KeyParameter::new(KeyParameterValue::UserSecureID(i64::MAX), SecurityLevel::STRONGBOX);
-        assert_eq!(
-            KmKeyParameter {
-                tag: Tag::USER_SECURE_ID,
-                value: KmKeyParameterValue::LongInteger(i64::MAX)
-            },
-            kp.value.into()
-        );
-    }
-    #[test]
-    fn test_convert_to_wire_blob() {
-        let kp = KeyParameter::new(
-            KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()),
-            SecurityLevel::STRONGBOX,
-        );
-        assert_eq!(
-            KmKeyParameter {
-                tag: Tag::CONFIRMATION_TOKEN,
-                value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes())
-            },
-            kp.value.into()
-        );
-    }
-
-    /// unit tests for from conversion
-    #[test]
-    fn test_convert_from_wire_invalid() {
-        let aidl_kp = KmKeyParameter { tag: Tag::INVALID, ..Default::default() };
-        assert_eq!(KeyParameterValue::Invalid, aidl_kp.into());
-    }
-    #[test]
-    fn test_convert_from_wire_bool() {
-        let aidl_kp =
-            KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) };
-        assert_eq!(KeyParameterValue::CallerNonce, aidl_kp.into());
-    }
-    #[test]
-    fn test_convert_from_wire_integer() {
-        let aidl_kp = KmKeyParameter {
-            tag: Tag::PURPOSE,
-            value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT),
-        };
-        assert_eq!(KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), aidl_kp.into());
-    }
-    #[test]
-    fn test_convert_from_wire_long_integer() {
-        let aidl_kp = KmKeyParameter {
-            tag: Tag::USER_SECURE_ID,
-            value: KmKeyParameterValue::LongInteger(i64::MAX),
-        };
-        assert_eq!(KeyParameterValue::UserSecureID(i64::MAX), aidl_kp.into());
-    }
-    #[test]
-    fn test_convert_from_wire_blob() {
-        let aidl_kp = KmKeyParameter {
-            tag: Tag::CONFIRMATION_TOKEN,
-            value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes()),
-        };
-        assert_eq!(
-            KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()),
-            aidl_kp.into()
-        );
-    }
-}
diff --git a/keystore2/src/key_parameter/basic_tests.rs b/keystore2/src/key_parameter/basic_tests.rs
new file mode 100644
index 0000000..2bb3724
--- /dev/null
+++ b/keystore2/src/key_parameter/basic_tests.rs
@@ -0,0 +1,28 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::key_parameter::*;
+
+// Test basic functionality of KeyParameter.
+#[test]
+fn test_key_parameter() {
+    let key_parameter =
+        KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::RSA), SecurityLevel::STRONGBOX);
+
+    assert_eq!(key_parameter.get_tag(), Tag::ALGORITHM);
+
+    assert_eq!(*key_parameter.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA));
+
+    assert_eq!(*key_parameter.security_level(), SecurityLevel::STRONGBOX);
+}
diff --git a/keystore2/src/key_parameter/generated_key_parameter_tests.rs b/keystore2/src/key_parameter/generated_key_parameter_tests.rs
new file mode 100644
index 0000000..a5c0a8b
--- /dev/null
+++ b/keystore2/src/key_parameter/generated_key_parameter_tests.rs
@@ -0,0 +1,95 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::*;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::TagType::TagType;
+
+fn get_field_by_tag_type(tag: Tag) -> KmKeyParameterValue {
+    let tag_type = TagType((tag.0 as u32 & 0xF0000000) as i32);
+    match tag {
+        Tag::ALGORITHM => return KmKeyParameterValue::Algorithm(Default::default()),
+        Tag::BLOCK_MODE => return KmKeyParameterValue::BlockMode(Default::default()),
+        Tag::PADDING => return KmKeyParameterValue::PaddingMode(Default::default()),
+        Tag::DIGEST => return KmKeyParameterValue::Digest(Default::default()),
+        Tag::RSA_OAEP_MGF_DIGEST => return KmKeyParameterValue::Digest(Default::default()),
+        Tag::EC_CURVE => return KmKeyParameterValue::EcCurve(Default::default()),
+        Tag::ORIGIN => return KmKeyParameterValue::Origin(Default::default()),
+        Tag::PURPOSE => return KmKeyParameterValue::KeyPurpose(Default::default()),
+        Tag::USER_AUTH_TYPE => {
+            return KmKeyParameterValue::HardwareAuthenticatorType(Default::default())
+        }
+        Tag::HARDWARE_TYPE => return KmKeyParameterValue::SecurityLevel(Default::default()),
+        _ => {}
+    }
+    match tag_type {
+        TagType::INVALID => return KmKeyParameterValue::Invalid(Default::default()),
+        TagType::ENUM | TagType::ENUM_REP => {}
+        TagType::UINT | TagType::UINT_REP => {
+            return KmKeyParameterValue::Integer(Default::default())
+        }
+        TagType::ULONG | TagType::ULONG_REP => {
+            return KmKeyParameterValue::LongInteger(Default::default())
+        }
+        TagType::DATE => return KmKeyParameterValue::DateTime(Default::default()),
+        TagType::BOOL => return KmKeyParameterValue::BoolValue(Default::default()),
+        TagType::BIGNUM | TagType::BYTES => return KmKeyParameterValue::Blob(Default::default()),
+        _ => {}
+    }
+    panic!("Unknown tag/tag_type: {:?} {:?}", tag, tag_type);
+}
+
+fn check_field_matches_tag_type(list_o_parameters: &[KmKeyParameter]) {
+    for kp in list_o_parameters.iter() {
+        match (&kp.value, get_field_by_tag_type(kp.tag)) {
+            (&KmKeyParameterValue::Algorithm(_), KmKeyParameterValue::Algorithm(_))
+            | (&KmKeyParameterValue::BlockMode(_), KmKeyParameterValue::BlockMode(_))
+            | (&KmKeyParameterValue::PaddingMode(_), KmKeyParameterValue::PaddingMode(_))
+            | (&KmKeyParameterValue::Digest(_), KmKeyParameterValue::Digest(_))
+            | (&KmKeyParameterValue::EcCurve(_), KmKeyParameterValue::EcCurve(_))
+            | (&KmKeyParameterValue::Origin(_), KmKeyParameterValue::Origin(_))
+            | (&KmKeyParameterValue::KeyPurpose(_), KmKeyParameterValue::KeyPurpose(_))
+            | (
+                &KmKeyParameterValue::HardwareAuthenticatorType(_),
+                KmKeyParameterValue::HardwareAuthenticatorType(_),
+            )
+            | (&KmKeyParameterValue::SecurityLevel(_), KmKeyParameterValue::SecurityLevel(_))
+            | (&KmKeyParameterValue::Invalid(_), KmKeyParameterValue::Invalid(_))
+            | (&KmKeyParameterValue::Integer(_), KmKeyParameterValue::Integer(_))
+            | (&KmKeyParameterValue::LongInteger(_), KmKeyParameterValue::LongInteger(_))
+            | (&KmKeyParameterValue::DateTime(_), KmKeyParameterValue::DateTime(_))
+            | (&KmKeyParameterValue::BoolValue(_), KmKeyParameterValue::BoolValue(_))
+            | (&KmKeyParameterValue::Blob(_), KmKeyParameterValue::Blob(_)) => {}
+            (actual, expected) => panic!(
+                "Tag {:?} associated with variant {:?} expected {:?}",
+                kp.tag, actual, expected
+            ),
+        }
+    }
+}
+
+#[test]
+fn key_parameter_value_field_matches_tag_type() {
+    check_field_matches_tag_type(&KeyParameterValue::make_field_matches_tag_type_test_vector());
+}
+
+#[test]
+fn key_parameter_serialization_test() {
+    let params = KeyParameterValue::make_key_parameter_defaults_vector();
+    let mut out_buffer: Vec<u8> = Default::default();
+    serde_cbor::to_writer(&mut out_buffer, &params).expect("Failed to serialize key parameters.");
+    let deserialized_params: Vec<KeyParameter> =
+        serde_cbor::from_reader(&mut out_buffer.as_slice())
+            .expect("Failed to deserialize key parameters.");
+    assert_eq!(params, deserialized_params);
+}
diff --git a/keystore2/src/key_parameter/storage_tests.rs b/keystore2/src/key_parameter/storage_tests.rs
new file mode 100644
index 0000000..38a57e4
--- /dev/null
+++ b/keystore2/src/key_parameter/storage_tests.rs
@@ -0,0 +1,263 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! The storage_tests module first tests the 'new_from_sql' method for KeyParameters of different
+//! data types and then tests 'to_sql' method for KeyParameters of those
+//! different data types. The five different data types for KeyParameter values are:
+//! i) enums of u32
+//! ii) u32
+//! iii) u64
+//! iv) Vec<u8>
+//! v) bool
+
+use crate::error::*;
+use crate::key_parameter::*;
+use anyhow::Result;
+use rusqlite::types::ToSql;
+use rusqlite::{params, Connection};
+
+/// Test initializing a KeyParameter (with key parameter value corresponding to an enum of i32)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_enum_i32() -> Result<()> {
+    let db = init_db()?;
+    insert_into_keyparameter(
+        &db,
+        1,
+        Tag::ALGORITHM.0,
+        &Algorithm::RSA.0,
+        SecurityLevel::STRONGBOX.0,
+    )?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(Tag::ALGORITHM, key_param.get_tag());
+    assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA));
+    assert_eq!(*key_param.security_level(), SecurityLevel::STRONGBOX);
+    Ok(())
+}
+
+/// Test initializing a KeyParameter (with key parameter value which is of i32)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_i32() -> Result<()> {
+    let db = init_db()?;
+    insert_into_keyparameter(&db, 1, Tag::KEY_SIZE.0, &1024, SecurityLevel::STRONGBOX.0)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(Tag::KEY_SIZE, key_param.get_tag());
+    assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::KeySize(1024));
+    Ok(())
+}
+
+/// Test initializing a KeyParameter (with key parameter value which is of i64)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_i64() -> Result<()> {
+    let db = init_db()?;
+    // max value for i64, just to test corner cases
+    insert_into_keyparameter(
+        &db,
+        1,
+        Tag::RSA_PUBLIC_EXPONENT.0,
+        &(i64::MAX),
+        SecurityLevel::STRONGBOX.0,
+    )?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(Tag::RSA_PUBLIC_EXPONENT, key_param.get_tag());
+    assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::RSAPublicExponent(i64::MAX));
+    Ok(())
+}
+
+/// Test initializing a KeyParameter (with key parameter value which is of bool)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_bool() -> Result<()> {
+    let db = init_db()?;
+    insert_into_keyparameter(&db, 1, Tag::CALLER_NONCE.0, &Null, SecurityLevel::STRONGBOX.0)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(Tag::CALLER_NONCE, key_param.get_tag());
+    assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::CallerNonce);
+    Ok(())
+}
+
+/// Test initializing a KeyParameter (with key parameter value which is of Vec<u8>)
+/// from a database table row.
+#[test]
+fn test_new_from_sql_vec_u8() -> Result<()> {
+    let db = init_db()?;
+    let app_id = String::from("MyAppID");
+    let app_id_bytes = app_id.into_bytes();
+    insert_into_keyparameter(
+        &db,
+        1,
+        Tag::APPLICATION_ID.0,
+        &app_id_bytes,
+        SecurityLevel::STRONGBOX.0,
+    )?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(Tag::APPLICATION_ID, key_param.get_tag());
+    assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::ApplicationID(app_id_bytes));
+    Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which corresponds to an enum of i32)
+/// in the database
+#[test]
+fn test_to_sql_enum_i32() -> Result<()> {
+    let db = init_db()?;
+    let kp =
+        KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::RSA), SecurityLevel::STRONGBOX);
+    store_keyparameter(&db, 1, &kp)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(kp.get_tag(), key_param.get_tag());
+    assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+    assert_eq!(kp.security_level(), key_param.security_level());
+    Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which is of i32) in the database
+#[test]
+fn test_to_sql_i32() -> Result<()> {
+    let db = init_db()?;
+    let kp = KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::STRONGBOX);
+    store_keyparameter(&db, 1, &kp)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(kp.get_tag(), key_param.get_tag());
+    assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+    assert_eq!(kp.security_level(), key_param.security_level());
+    Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which is of i64) in the database
+#[test]
+fn test_to_sql_i64() -> Result<()> {
+    let db = init_db()?;
+    // max value for i64, just to test corner cases
+    let kp =
+        KeyParameter::new(KeyParameterValue::RSAPublicExponent(i64::MAX), SecurityLevel::STRONGBOX);
+    store_keyparameter(&db, 1, &kp)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(kp.get_tag(), key_param.get_tag());
+    assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+    assert_eq!(kp.security_level(), key_param.security_level());
+    Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which is of Vec<u8>) in the database
+#[test]
+fn test_to_sql_vec_u8() -> Result<()> {
+    let db = init_db()?;
+    let kp = KeyParameter::new(
+        KeyParameterValue::ApplicationID(String::from("MyAppID").into_bytes()),
+        SecurityLevel::STRONGBOX,
+    );
+    store_keyparameter(&db, 1, &kp)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(kp.get_tag(), key_param.get_tag());
+    assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+    assert_eq!(kp.security_level(), key_param.security_level());
+    Ok(())
+}
+
+/// Test storing a KeyParameter (with key parameter value which is of i32) in the database
+#[test]
+fn test_to_sql_bool() -> Result<()> {
+    let db = init_db()?;
+    let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX);
+    store_keyparameter(&db, 1, &kp)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(kp.get_tag(), key_param.get_tag());
+    assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value());
+    assert_eq!(kp.security_level(), key_param.security_level());
+    Ok(())
+}
+
+#[test]
+/// Test Tag::Invalid
+fn test_invalid_tag() -> Result<()> {
+    let db = init_db()?;
+    insert_into_keyparameter(&db, 1, 0, &123, 1)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(Tag::INVALID, key_param.get_tag());
+    Ok(())
+}
+
+#[test]
+fn test_non_existing_enum_variant() -> Result<()> {
+    let db = init_db()?;
+    insert_into_keyparameter(&db, 1, 100, &123, 1)?;
+    let key_param = query_from_keyparameter(&db)?;
+    assert_eq!(Tag::INVALID, key_param.get_tag());
+    Ok(())
+}
+
+#[test]
+fn test_invalid_conversion_from_sql() -> Result<()> {
+    let db = init_db()?;
+    insert_into_keyparameter(&db, 1, Tag::ALGORITHM.0, &Null, 1)?;
+    tests::check_result_contains_error_string(
+        query_from_keyparameter(&db),
+        "Failed to read sql data for tag: ALGORITHM.",
+    );
+    Ok(())
+}
+
+/// Helper method to init database table for key parameter
+fn init_db() -> Result<Connection> {
+    let db = Connection::open_in_memory().context("Failed to initialize sqlite connection.")?;
+    db.execute("ATTACH DATABASE ? as 'persistent';", params![""])
+        .context("Failed to attach databases.")?;
+    db.execute(
+        "CREATE TABLE IF NOT EXISTS persistent.keyparameter (
+                                keyentryid INTEGER,
+                                tag INTEGER,
+                                data ANY,
+                                security_level INTEGER);",
+        [],
+    )
+    .context("Failed to initialize \"keyparameter\" table.")?;
+    Ok(db)
+}
+
+/// Helper method to insert an entry into key parameter table, with individual parameters
+fn insert_into_keyparameter<T: ToSql>(
+    db: &Connection,
+    key_id: i64,
+    tag: i32,
+    value: &T,
+    security_level: i32,
+) -> Result<()> {
+    db.execute(
+        "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
+                VALUES(?, ?, ?, ?);",
+        params![key_id, tag, *value, security_level],
+    )?;
+    Ok(())
+}
+
+/// Helper method to store a key parameter instance.
+fn store_keyparameter(db: &Connection, key_id: i64, kp: &KeyParameter) -> Result<()> {
+    db.execute(
+        "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
+                VALUES(?, ?, ?, ?);",
+        params![key_id, kp.get_tag().0, kp.key_parameter_value(), kp.security_level().0],
+    )?;
+    Ok(())
+}
+
+/// Helper method to query a row from keyparameter table
+fn query_from_keyparameter(db: &Connection) -> Result<KeyParameter> {
+    let mut stmt = db.prepare("SELECT tag, data, security_level FROM persistent.keyparameter")?;
+    let mut rows = stmt.query([])?;
+    let row = rows.next()?.unwrap();
+    KeyParameter::new_from_sql(Tag(row.get(0)?), &SqlField::new(1, row), SecurityLevel(row.get(2)?))
+}
diff --git a/keystore2/src/key_parameter/wire_tests.rs b/keystore2/src/key_parameter/wire_tests.rs
new file mode 100644
index 0000000..278b766
--- /dev/null
+++ b/keystore2/src/key_parameter/wire_tests.rs
@@ -0,0 +1,119 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! The wire_tests module tests the 'convert_to_wire' and 'convert_from_wire' methods for
+//! KeyParameter, for the four different types used in KmKeyParameter, in addition to Invalid
+//! key parameter.
+//! i) bool
+//! ii) integer
+//! iii) longInteger
+//! iv) blob
+
+use crate::key_parameter::*;
+/// unit tests for to conversions
+#[test]
+fn test_convert_to_wire_invalid() {
+    let kp = KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::STRONGBOX);
+    assert_eq!(
+        KmKeyParameter { tag: Tag::INVALID, value: KmKeyParameterValue::Invalid(0) },
+        kp.value.into()
+    );
+}
+#[test]
+fn test_convert_to_wire_bool() {
+    let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX);
+    assert_eq!(
+        KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) },
+        kp.value.into()
+    );
+}
+#[test]
+fn test_convert_to_wire_integer() {
+    let kp = KeyParameter::new(
+        KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT),
+        SecurityLevel::STRONGBOX,
+    );
+    assert_eq!(
+        KmKeyParameter {
+            tag: Tag::PURPOSE,
+            value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT)
+        },
+        kp.value.into()
+    );
+}
+#[test]
+fn test_convert_to_wire_long_integer() {
+    let kp = KeyParameter::new(KeyParameterValue::UserSecureID(i64::MAX), SecurityLevel::STRONGBOX);
+    assert_eq!(
+        KmKeyParameter {
+            tag: Tag::USER_SECURE_ID,
+            value: KmKeyParameterValue::LongInteger(i64::MAX)
+        },
+        kp.value.into()
+    );
+}
+#[test]
+fn test_convert_to_wire_blob() {
+    let kp = KeyParameter::new(
+        KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()),
+        SecurityLevel::STRONGBOX,
+    );
+    assert_eq!(
+        KmKeyParameter {
+            tag: Tag::CONFIRMATION_TOKEN,
+            value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes())
+        },
+        kp.value.into()
+    );
+}
+
+/// unit tests for from conversion
+#[test]
+fn test_convert_from_wire_invalid() {
+    let aidl_kp = KmKeyParameter { tag: Tag::INVALID, ..Default::default() };
+    assert_eq!(KeyParameterValue::Invalid, aidl_kp.into());
+}
+#[test]
+fn test_convert_from_wire_bool() {
+    let aidl_kp =
+        KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) };
+    assert_eq!(KeyParameterValue::CallerNonce, aidl_kp.into());
+}
+#[test]
+fn test_convert_from_wire_integer() {
+    let aidl_kp = KmKeyParameter {
+        tag: Tag::PURPOSE,
+        value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT),
+    };
+    assert_eq!(KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), aidl_kp.into());
+}
+#[test]
+fn test_convert_from_wire_long_integer() {
+    let aidl_kp = KmKeyParameter {
+        tag: Tag::USER_SECURE_ID,
+        value: KmKeyParameterValue::LongInteger(i64::MAX),
+    };
+    assert_eq!(KeyParameterValue::UserSecureID(i64::MAX), aidl_kp.into());
+}
+#[test]
+fn test_convert_from_wire_blob() {
+    let aidl_kp = KmKeyParameter {
+        tag: Tag::CONFIRMATION_TOKEN,
+        value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes()),
+    };
+    assert_eq!(
+        KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()),
+        aidl_kp.into()
+    );
+}
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index 178b36c..008e6fe 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -93,6 +93,7 @@
 
     ENFORCEMENTS.install_confirmation_token_receiver(confirmation_token_receiver);
 
+    std::thread::spawn(keystore2::globals::await_boot_completed);
     entropy::register_feeder();
     shared_secret_negotiation::perform_shared_secret_negotiation();
 
diff --git a/keystore2/src/km_compat.rs b/keystore2/src/km_compat.rs
index 5e3bdfa..95e9294 100644
--- a/keystore2/src/km_compat.rs
+++ b/keystore2/src/km_compat.rs
@@ -214,6 +214,12 @@
     fn sendRootOfTrust(&self, root_of_trust: &[u8]) -> binder::Result<()> {
         self.real.sendRootOfTrust(root_of_trust)
     }
+    fn setAdditionalAttestationInfo(
+        &self,
+        additional_attestation_info: &[KeyParameter],
+    ) -> binder::Result<()> {
+        self.real.setAdditionalAttestationInfo(additional_attestation_info)
+    }
 
     // For methods that emit keyblobs, check whether the underlying real device
     // supports the relevant parameters, and forward to the appropriate device.
diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp
index e9ff1ff..7a6ef4a 100644
--- a/keystore2/src/km_compat/km_compat.cpp
+++ b/keystore2/src/km_compat/km_compat.cpp
@@ -839,6 +839,11 @@
     return convertErrorCode(KMV1::ErrorCode::UNIMPLEMENTED);
 }
 
+ScopedAStatus KeyMintDevice::setAdditionalAttestationInfo(
+    const std::vector<KeyParameter>& /* additionalAttestationInfo */) {
+    return convertErrorCode(KMV1::ErrorCode::UNIMPLEMENTED);
+}
+
 ScopedAStatus KeyMintOperation::updateAad(const std::vector<uint8_t>& input,
                                           const std::optional<HardwareAuthToken>& optAuthToken,
                                           const std::optional<TimeStampToken>& optTimeStampToken) {
diff --git a/keystore2/src/km_compat/km_compat.h b/keystore2/src/km_compat/km_compat.h
index c4bcdaa..71f7fbe 100644
--- a/keystore2/src/km_compat/km_compat.h
+++ b/keystore2/src/km_compat/km_compat.h
@@ -147,6 +147,9 @@
                                  std::vector<uint8_t>* rootOfTrust);
     ScopedAStatus sendRootOfTrust(const std::vector<uint8_t>& rootOfTrust);
 
+    ScopedAStatus
+    setAdditionalAttestationInfo(const std::vector<KeyParameter>& additionalAttestationInfo);
+
     // These are public to allow testing code to use them directly.
     // This class should not be used publicly anyway.
     std::variant<std::vector<Certificate>, KMV1_ErrorCode>
diff --git a/keystore2/src/km_compat/km_compat_type_conversion.h b/keystore2/src/km_compat/km_compat_type_conversion.h
index 5db7e3d..d6a2dcc 100644
--- a/keystore2/src/km_compat/km_compat_type_conversion.h
+++ b/keystore2/src/km_compat/km_compat_type_conversion.h
@@ -750,8 +750,12 @@
     case KMV1::Tag::CERTIFICATE_SUBJECT:
     case KMV1::Tag::CERTIFICATE_NOT_BEFORE:
     case KMV1::Tag::CERTIFICATE_NOT_AFTER:
+        // These tags do not exist in KM < KeyMint 1.
+        break;
     case KMV1::Tag::ATTESTATION_ID_SECOND_IMEI:
-        // These tags do not exist in KM < KeyMint 1.0.
+        // This tag doesn't exist in KM < KeyMint 3.
+    case KMV1::Tag::MODULE_HASH:
+        // This tag doesn't exist in KM < KeyMint 4.
         break;
     case KMV1::Tag::MAX_BOOT_LEVEL:
         // Does not exist in API level 30 or below.
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index 2bb7f27..e05e686 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -36,6 +36,9 @@
 
 const SUPPORTED_LEGACY_BLOB_VERSION: u8 = 3;
 
+#[cfg(test)]
+mod tests;
+
 mod flags {
     /// This flag is deprecated. It is here to support keys that have been written with this flag
     /// set, but we don't create any new keys with this flag.
@@ -958,7 +961,7 @@
 
     fn make_user_path_name(&self, user_id: u32) -> PathBuf {
         let mut path = self.path.clone();
-        path.push(&format!("user_{}", user_id));
+        path.push(format!("user_{}", user_id));
         path
     }
 
@@ -1645,675 +1648,3 @@
         Ok(())
     }
 }
-
-#[cfg(test)]
-mod test {
-    #![allow(dead_code)]
-    use super::*;
-    use crate::legacy_blob::test_utils::legacy_blob_test_vectors::*;
-    use crate::legacy_blob::test_utils::*;
-    use anyhow::{anyhow, Result};
-    use keystore2_crypto::aes_gcm_decrypt;
-    use keystore2_test_utils::TempDir;
-    use rand::Rng;
-    use std::convert::TryInto;
-    use std::ops::Deref;
-    use std::string::FromUtf8Error;
-
-    #[test]
-    fn decode_encode_alias_test() {
-        static ALIAS: &str = "#({}test[])😗";
-        static ENCODED_ALIAS: &str = "+S+X{}test[]+Y.`-O-H-G";
-        // Second multi byte out of range ------v
-        static ENCODED_ALIAS_ERROR1: &str = "+S+{}test[]+Y";
-        // Incomplete multi byte ------------------------v
-        static ENCODED_ALIAS_ERROR2: &str = "+S+X{}test[]+";
-        // Our encoding: ".`-O-H-G"
-        // is UTF-8: 0xF0 0x9F 0x98 0x97
-        // is UNICODE: U+1F617
-        // is 😗
-        // But +H below is a valid encoding for 0x18 making this sequence invalid UTF-8.
-        static ENCODED_ALIAS_ERROR_UTF8: &str = ".`-O+H-G";
-
-        assert_eq!(ENCODED_ALIAS, &LegacyBlobLoader::encode_alias(ALIAS));
-        assert_eq!(ALIAS, &LegacyBlobLoader::decode_alias(ENCODED_ALIAS).unwrap());
-        assert_eq!(
-            Some(&Error::BadEncoding),
-            LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR1)
-                .unwrap_err()
-                .root_cause()
-                .downcast_ref::<Error>()
-        );
-        assert_eq!(
-            Some(&Error::BadEncoding),
-            LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR2)
-                .unwrap_err()
-                .root_cause()
-                .downcast_ref::<Error>()
-        );
-        assert!(LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR_UTF8)
-            .unwrap_err()
-            .root_cause()
-            .downcast_ref::<FromUtf8Error>()
-            .is_some());
-
-        for _i in 0..100 {
-            // Any valid UTF-8 string should be en- and decoded without loss.
-            let alias_str = rand::thread_rng().gen::<[char; 20]>().iter().collect::<String>();
-            let random_alias = alias_str.as_bytes();
-            let encoded = LegacyBlobLoader::encode_alias(&alias_str);
-            let decoded = match LegacyBlobLoader::decode_alias(&encoded) {
-                Ok(d) => d,
-                Err(_) => panic!("random_alias: {:x?}\nencoded {}", random_alias, encoded),
-            };
-            assert_eq!(random_alias.to_vec(), decoded.bytes().collect::<Vec<u8>>());
-        }
-    }
-
-    #[test]
-    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());
-        assert!(!blob.is_critical_to_device_encryption());
-        assert_eq!(blob.value(), &BlobValue::Generic([0xde, 0xed, 0xbe, 0xef].to_vec()));
-
-        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());
-        assert!(!blob.is_critical_to_device_encryption());
-        assert_eq!(
-            blob.value(),
-            &BlobValue::Decrypted(REAL_LEGACY_BLOB_PAYLOAD.try_into().unwrap())
-        );
-        Ok(())
-    }
-
-    #[test]
-    fn read_aes_gcm_encrypted_key_blob_test() {
-        let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
-            &mut &*AES_GCM_ENCRYPTED_BLOB,
-            |d, iv, tag, salt, key_size| {
-                assert_eq!(salt, None);
-                assert_eq!(key_size, None);
-                assert_eq!(
-                    iv,
-                    &[
-                        0xbd, 0xdb, 0x8d, 0x69, 0x72, 0x56, 0xf0, 0xf5, 0xa4, 0x02, 0x88, 0x7f,
-                        0x00, 0x00, 0x00, 0x00,
-                    ]
-                );
-                assert_eq!(
-                    tag,
-                    &[
-                        0x50, 0xd9, 0x97, 0x95, 0x37, 0x6e, 0x28, 0x6a, 0x28, 0x9d, 0x51, 0xb9,
-                        0xb9, 0xe0, 0x0b, 0xc3
-                    ][..]
-                );
-                aes_gcm_decrypt(d, iv, tag, AES_KEY).context("Trying to decrypt blob.")
-            },
-        )
-        .unwrap();
-        assert!(blob.is_encrypted());
-        assert!(!blob.is_fallback());
-        assert!(!blob.is_strongbox());
-        assert!(!blob.is_critical_to_device_encryption());
-
-        assert_eq!(blob.value(), &BlobValue::Decrypted(DECRYPTED_PAYLOAD.try_into().unwrap()));
-    }
-
-    #[test]
-    fn read_golden_key_blob_too_short_test() {
-        let error =
-            LegacyBlobLoader::new_from_stream_decrypt_with(&mut &BLOB[0..15], |_, _, _, _, _| {
-                Err(anyhow!("should not be called"))
-            })
-            .unwrap_err();
-        assert_eq!(Some(&Error::BadLen), error.root_cause().downcast_ref::<Error>());
-    }
-
-    #[test]
-    fn test_is_empty() {
-        let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir.");
-        let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
-        assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty."));
-
-        let _db = crate::database::KeystoreDB::new(temp_dir.path(), None)
-            .expect("Failed to open database.");
-
-        assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty."));
-
-        std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0.");
-
-        assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty."));
-
-        std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10.");
-
-        assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
-
-        std::fs::remove_dir_all(&*temp_dir.build().push("user_0"))
-            .expect("Failed to remove user_0.");
-
-        assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
-
-        std::fs::remove_dir_all(&*temp_dir.build().push("user_10"))
-            .expect("Failed to remove user_10.");
-
-        assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again."));
-    }
-
-    #[test]
-    fn test_legacy_blobs() -> anyhow::Result<()> {
-        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).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 legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
-        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", &None)?
-        {
-            assert_eq!(flags, 4);
-            //assert_eq!(value, BlobValue::Encrypted(..));
-            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, "non_authbound", &None)?
-        {
-            assert_eq!(flags, 0);
-            assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
-            assert_eq!(&cert[..], LOADED_CERT_NON_AUTHBOUND);
-            assert_eq!(&chain[..], LOADED_CACERT_NON_AUTHBOUND);
-        } else {
-            panic!("");
-        }
-
-        legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
-        legacy_blob_loader
-            .remove_keystore_entry(10223, "non_authbound")
-            .expect("This should succeed.");
-
-        assert_eq!(
-            (None, None, None),
-            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", &None)?
-        );
-
-        // The database should not be empty due to the super key.
-        assert!(!legacy_blob_loader.is_empty()?);
-        assert!(!legacy_blob_loader.is_empty_user(0)?);
-
-        // The database should be considered empty for user 1.
-        assert!(legacy_blob_loader.is_empty_user(1)?);
-
-        legacy_blob_loader.remove_super_key(0);
-
-        // Now it should be empty.
-        assert!(legacy_blob_loader.is_empty_user(0)?);
-        assert!(legacy_blob_loader.is_empty()?);
-
-        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_pbkdf2(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,
-            KEY_PARAMETERS,
-        )
-        .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_pbkdf2(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,
-            LOADED_CERT_AUTHBOUND,
-        )
-        .unwrap();
-        make_encrypted_ca_cert_file(
-            &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
-            &super_key,
-            LOADED_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_cache()
-                )),
-                Some(LOADED_CERT_AUTHBOUND.to_vec()),
-                Some(LOADED_CACERT_AUTHBOUND.to_vec())
-            )
-        );
-
-        legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
-
-        assert_eq!(
-            (None, None, None),
-            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
-        );
-
-        // The database should not be empty due to the super key.
-        assert!(!legacy_blob_loader.is_empty().unwrap());
-        assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
-
-        // The database should be considered empty for user 1.
-        assert!(legacy_blob_loader.is_empty_user(1).unwrap());
-
-        legacy_blob_loader.remove_super_key(0);
-
-        // Now it should be empty.
-        assert!(legacy_blob_loader.is_empty_user(0).unwrap());
-        assert!(legacy_blob_loader.is_empty().unwrap());
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_in_place_key_migration() -> anyhow::Result<()> {
-        let temp_dir = TempDir::new("test_in_place_key_migration").unwrap();
-        std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
-
-        let pw: Password = PASSWORD.into();
-        let pw_key = TestKey(pw.derive_key_pbkdf2(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,
-            LOADED_CERT_AUTHBOUND,
-        )
-        .unwrap();
-        make_encrypted_ca_cert_file(
-            &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
-            &super_key,
-            LOADED_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)
-        );
-
-        let super_key: Option<Arc<dyn AesGcm>> = Some(super_key);
-
-        assert_eq!(
-            legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(),
-            (
-                Some((
-                    Blob {
-                        flags: 4,
-                        value: BlobValue::Encrypted {
-                            data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
-                            iv: USRPKEY_AUTHBOUND_IV.to_vec(),
-                            tag: USRPKEY_AUTHBOUND_TAG.to_vec()
-                        }
-                    },
-                    structured_test_params_cache()
-                )),
-                Some(LOADED_CERT_AUTHBOUND.to_vec()),
-                Some(LOADED_CACERT_AUTHBOUND.to_vec())
-            )
-        );
-
-        legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap();
-
-        assert_eq!(
-            legacy_blob_loader
-                .load_by_uid_alias(10224, "boundauth", &None)
-                .unwrap_err()
-                .root_cause()
-                .downcast_ref::<Error>(),
-            Some(&Error::LockedComponent)
-        );
-
-        assert_eq!(
-            legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(),
-            (
-                Some((
-                    Blob {
-                        flags: 4,
-                        value: BlobValue::Encrypted {
-                            data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
-                            iv: USRPKEY_AUTHBOUND_IV.to_vec(),
-                            tag: USRPKEY_AUTHBOUND_TAG.to_vec()
-                        }
-                    },
-                    structured_test_params_cache()
-                )),
-                Some(LOADED_CERT_AUTHBOUND.to_vec()),
-                Some(LOADED_CACERT_AUTHBOUND.to_vec())
-            )
-        );
-
-        legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed.");
-
-        assert_eq!(
-            (None, None, None),
-            legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap()
-        );
-
-        // The database should not be empty due to the super key.
-        assert!(!legacy_blob_loader.is_empty().unwrap());
-        assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
-
-        // The database should be considered empty for user 1.
-        assert!(legacy_blob_loader.is_empty_user(1).unwrap());
-
-        legacy_blob_loader.remove_super_key(0);
-
-        // Now it should be empty.
-        assert!(legacy_blob_loader.is_empty_user(0).unwrap());
-        assert!(legacy_blob_loader.is_empty().unwrap());
-
-        Ok(())
-    }
-
-    #[test]
-    fn list_non_existing_user() -> Result<()> {
-        let temp_dir = TempDir::new("list_non_existing_user").unwrap();
-        let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
-        assert!(legacy_blob_loader.list_user(20)?.is_empty());
-
-        Ok(())
-    }
-
-    #[test]
-    fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> {
-        let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap();
-        let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
-
-        assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty());
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_move_keystore_entry() {
-        let temp_dir = TempDir::new("test_move_keystore_entry").unwrap();
-        std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
-
-        const SOME_CONTENT: &[u8] = b"some content";
-        const ANOTHER_CONTENT: &[u8] = b"another content";
-        const SOME_FILENAME: &str = "some_file";
-        const ANOTHER_FILENAME: &str = "another_file";
-
-        std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT)
-            .unwrap();
-
-        std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT)
-            .unwrap();
-
-        // Non existent source id silently ignored.
-        assert!(LegacyBlobLoader::move_keystore_file_if_exists(
-            1,
-            2,
-            "non_existent",
-            ANOTHER_FILENAME,
-            "ignored",
-            |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
-        )
-        .is_ok());
-
-        // Content of another_file has not changed.
-        let another_content =
-            std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
-        assert_eq!(&another_content, ANOTHER_CONTENT);
-
-        // Check that some_file still exists.
-        assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
-        // Existing target files are silently overwritten.
-
-        assert!(LegacyBlobLoader::move_keystore_file_if_exists(
-            1,
-            2,
-            SOME_FILENAME,
-            ANOTHER_FILENAME,
-            "ignored",
-            |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
-        )
-        .is_ok());
-
-        // Content of another_file is now "some content".
-        let another_content =
-            std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
-        assert_eq!(&another_content, SOME_CONTENT);
-
-        // Check that some_file no longer exists.
-        assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
-    }
-}
diff --git a/keystore2/src/legacy_blob/tests.rs b/keystore2/src/legacy_blob/tests.rs
new file mode 100644
index 0000000..53fe03f
--- /dev/null
+++ b/keystore2/src/legacy_blob/tests.rs
@@ -0,0 +1,676 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Tests for legacy keyblob processing.
+
+#![allow(dead_code)]
+use super::*;
+use crate::legacy_blob::test_utils::legacy_blob_test_vectors::*;
+use crate::legacy_blob::test_utils::*;
+use anyhow::{anyhow, Result};
+use keystore2_crypto::aes_gcm_decrypt;
+use keystore2_test_utils::TempDir;
+use rand::Rng;
+use std::convert::TryInto;
+use std::ops::Deref;
+use std::string::FromUtf8Error;
+
+#[test]
+fn decode_encode_alias_test() {
+    static ALIAS: &str = "#({}test[])😗";
+    static ENCODED_ALIAS: &str = "+S+X{}test[]+Y.`-O-H-G";
+    // Second multi byte out of range ------v
+    static ENCODED_ALIAS_ERROR1: &str = "+S+{}test[]+Y";
+    // Incomplete multi byte ------------------------v
+    static ENCODED_ALIAS_ERROR2: &str = "+S+X{}test[]+";
+    // Our encoding: ".`-O-H-G"
+    // is UTF-8: 0xF0 0x9F 0x98 0x97
+    // is UNICODE: U+1F617
+    // is 😗
+    // But +H below is a valid encoding for 0x18 making this sequence invalid UTF-8.
+    static ENCODED_ALIAS_ERROR_UTF8: &str = ".`-O+H-G";
+
+    assert_eq!(ENCODED_ALIAS, &LegacyBlobLoader::encode_alias(ALIAS));
+    assert_eq!(ALIAS, &LegacyBlobLoader::decode_alias(ENCODED_ALIAS).unwrap());
+    assert_eq!(
+        Some(&Error::BadEncoding),
+        LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR1)
+            .unwrap_err()
+            .root_cause()
+            .downcast_ref::<Error>()
+    );
+    assert_eq!(
+        Some(&Error::BadEncoding),
+        LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR2)
+            .unwrap_err()
+            .root_cause()
+            .downcast_ref::<Error>()
+    );
+    assert!(LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR_UTF8)
+        .unwrap_err()
+        .root_cause()
+        .downcast_ref::<FromUtf8Error>()
+        .is_some());
+
+    for _i in 0..100 {
+        // Any valid UTF-8 string should be en- and decoded without loss.
+        let alias_str = rand::thread_rng().gen::<[char; 20]>().iter().collect::<String>();
+        let random_alias = alias_str.as_bytes();
+        let encoded = LegacyBlobLoader::encode_alias(&alias_str);
+        let decoded = match LegacyBlobLoader::decode_alias(&encoded) {
+            Ok(d) => d,
+            Err(_) => panic!("random_alias: {:x?}\nencoded {}", random_alias, encoded),
+        };
+        assert_eq!(random_alias.to_vec(), decoded.bytes().collect::<Vec<u8>>());
+    }
+}
+
+#[test]
+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());
+    assert!(!blob.is_critical_to_device_encryption());
+    assert_eq!(blob.value(), &BlobValue::Generic([0xde, 0xed, 0xbe, 0xef].to_vec()));
+
+    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());
+    assert!(!blob.is_critical_to_device_encryption());
+    assert_eq!(blob.value(), &BlobValue::Decrypted(REAL_LEGACY_BLOB_PAYLOAD.try_into().unwrap()));
+    Ok(())
+}
+
+#[test]
+fn read_aes_gcm_encrypted_key_blob_test() {
+    let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
+        &mut &*AES_GCM_ENCRYPTED_BLOB,
+        |d, iv, tag, salt, key_size| {
+            assert_eq!(salt, None);
+            assert_eq!(key_size, None);
+            assert_eq!(
+                iv,
+                &[
+                    0xbd, 0xdb, 0x8d, 0x69, 0x72, 0x56, 0xf0, 0xf5, 0xa4, 0x02, 0x88, 0x7f, 0x00,
+                    0x00, 0x00, 0x00,
+                ]
+            );
+            assert_eq!(
+                tag,
+                &[
+                    0x50, 0xd9, 0x97, 0x95, 0x37, 0x6e, 0x28, 0x6a, 0x28, 0x9d, 0x51, 0xb9, 0xb9,
+                    0xe0, 0x0b, 0xc3
+                ][..]
+            );
+            aes_gcm_decrypt(d, iv, tag, AES_KEY).context("Trying to decrypt blob.")
+        },
+    )
+    .unwrap();
+    assert!(blob.is_encrypted());
+    assert!(!blob.is_fallback());
+    assert!(!blob.is_strongbox());
+    assert!(!blob.is_critical_to_device_encryption());
+
+    assert_eq!(blob.value(), &BlobValue::Decrypted(DECRYPTED_PAYLOAD.try_into().unwrap()));
+}
+
+#[test]
+fn read_golden_key_blob_too_short_test() {
+    let error =
+        LegacyBlobLoader::new_from_stream_decrypt_with(&mut &BLOB[0..15], |_, _, _, _, _| {
+            Err(anyhow!("should not be called"))
+        })
+        .unwrap_err();
+    assert_eq!(Some(&Error::BadLen), error.root_cause().downcast_ref::<Error>());
+}
+
+#[test]
+fn test_is_empty() {
+    let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir.");
+    let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+    assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty."));
+
+    let _db =
+        crate::database::KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database.");
+
+    assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty."));
+
+    std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0.");
+
+    assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty."));
+
+    std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10.");
+
+    assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
+
+    std::fs::remove_dir_all(&*temp_dir.build().push("user_0")).expect("Failed to remove user_0.");
+
+    assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
+
+    std::fs::remove_dir_all(&*temp_dir.build().push("user_10")).expect("Failed to remove user_10.");
+
+    assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again."));
+}
+
+#[test]
+fn test_legacy_blobs() -> anyhow::Result<()> {
+    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).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 legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+    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", &None)?
+    {
+        assert_eq!(flags, 4);
+        //assert_eq!(value, BlobValue::Encrypted(..));
+        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, "non_authbound", &None)?
+    {
+        assert_eq!(flags, 0);
+        assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
+        assert_eq!(&cert[..], LOADED_CERT_NON_AUTHBOUND);
+        assert_eq!(&chain[..], LOADED_CACERT_NON_AUTHBOUND);
+    } else {
+        panic!("");
+    }
+
+    legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+    legacy_blob_loader.remove_keystore_entry(10223, "non_authbound").expect("This should succeed.");
+
+    assert_eq!(
+        (None, None, None),
+        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", &None)?
+    );
+
+    // The database should not be empty due to the super key.
+    assert!(!legacy_blob_loader.is_empty()?);
+    assert!(!legacy_blob_loader.is_empty_user(0)?);
+
+    // The database should be considered empty for user 1.
+    assert!(legacy_blob_loader.is_empty_user(1)?);
+
+    legacy_blob_loader.remove_super_key(0);
+
+    // Now it should be empty.
+    assert!(legacy_blob_loader.is_empty_user(0)?);
+    assert!(legacy_blob_loader.is_empty()?);
+
+    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_pbkdf2(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,
+        KEY_PARAMETERS,
+    )
+    .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_pbkdf2(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,
+        LOADED_CERT_AUTHBOUND,
+    )
+    .unwrap();
+    make_encrypted_ca_cert_file(
+        &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+        &super_key,
+        LOADED_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_cache()
+            )),
+            Some(LOADED_CERT_AUTHBOUND.to_vec()),
+            Some(LOADED_CACERT_AUTHBOUND.to_vec())
+        )
+    );
+
+    legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+
+    assert_eq!(
+        (None, None, None),
+        legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
+    );
+
+    // The database should not be empty due to the super key.
+    assert!(!legacy_blob_loader.is_empty().unwrap());
+    assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+    // The database should be considered empty for user 1.
+    assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+    legacy_blob_loader.remove_super_key(0);
+
+    // Now it should be empty.
+    assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+    assert!(legacy_blob_loader.is_empty().unwrap());
+
+    Ok(())
+}
+
+#[test]
+fn test_in_place_key_migration() -> anyhow::Result<()> {
+    let temp_dir = TempDir::new("test_in_place_key_migration").unwrap();
+    std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+    let pw: Password = PASSWORD.into();
+    let pw_key = TestKey(pw.derive_key_pbkdf2(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,
+        LOADED_CERT_AUTHBOUND,
+    )
+    .unwrap();
+    make_encrypted_ca_cert_file(
+        &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+        &super_key,
+        LOADED_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)
+    );
+
+    let super_key: Option<Arc<dyn AesGcm>> = Some(super_key);
+
+    assert_eq!(
+        legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(),
+        (
+            Some((
+                Blob {
+                    flags: 4,
+                    value: BlobValue::Encrypted {
+                        data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+                        iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+                        tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+                    }
+                },
+                structured_test_params_cache()
+            )),
+            Some(LOADED_CERT_AUTHBOUND.to_vec()),
+            Some(LOADED_CACERT_AUTHBOUND.to_vec())
+        )
+    );
+
+    legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap();
+
+    assert_eq!(
+        legacy_blob_loader
+            .load_by_uid_alias(10224, "boundauth", &None)
+            .unwrap_err()
+            .root_cause()
+            .downcast_ref::<Error>(),
+        Some(&Error::LockedComponent)
+    );
+
+    assert_eq!(
+        legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(),
+        (
+            Some((
+                Blob {
+                    flags: 4,
+                    value: BlobValue::Encrypted {
+                        data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+                        iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+                        tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+                    }
+                },
+                structured_test_params_cache()
+            )),
+            Some(LOADED_CERT_AUTHBOUND.to_vec()),
+            Some(LOADED_CACERT_AUTHBOUND.to_vec())
+        )
+    );
+
+    legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed.");
+
+    assert_eq!(
+        (None, None, None),
+        legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap()
+    );
+
+    // The database should not be empty due to the super key.
+    assert!(!legacy_blob_loader.is_empty().unwrap());
+    assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+    // The database should be considered empty for user 1.
+    assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+    legacy_blob_loader.remove_super_key(0);
+
+    // Now it should be empty.
+    assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+    assert!(legacy_blob_loader.is_empty().unwrap());
+
+    Ok(())
+}
+
+#[test]
+fn list_non_existing_user() -> Result<()> {
+    let temp_dir = TempDir::new("list_non_existing_user").unwrap();
+    let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+    assert!(legacy_blob_loader.list_user(20)?.is_empty());
+
+    Ok(())
+}
+
+#[test]
+fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> {
+    let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap();
+    let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+    assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty());
+
+    Ok(())
+}
+
+#[test]
+fn test_move_keystore_entry() {
+    let temp_dir = TempDir::new("test_move_keystore_entry").unwrap();
+    std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+    const SOME_CONTENT: &[u8] = b"some content";
+    const ANOTHER_CONTENT: &[u8] = b"another content";
+    const SOME_FILENAME: &str = "some_file";
+    const ANOTHER_FILENAME: &str = "another_file";
+
+    std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT).unwrap();
+
+    std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT)
+        .unwrap();
+
+    // Non existent source id silently ignored.
+    assert!(LegacyBlobLoader::move_keystore_file_if_exists(
+        1,
+        2,
+        "non_existent",
+        ANOTHER_FILENAME,
+        "ignored",
+        |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
+    )
+    .is_ok());
+
+    // Content of another_file has not changed.
+    let another_content =
+        std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
+    assert_eq!(&another_content, ANOTHER_CONTENT);
+
+    // Check that some_file still exists.
+    assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
+    // Existing target files are silently overwritten.
+
+    assert!(LegacyBlobLoader::move_keystore_file_if_exists(
+        1,
+        2,
+        SOME_FILENAME,
+        ANOTHER_FILENAME,
+        "ignored",
+        |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
+    )
+    .is_ok());
+
+    // Content of another_file is now "some content".
+    let another_content =
+        std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
+    assert_eq!(&another_content, SOME_CONTENT);
+
+    // Check that some_file no longer exists.
+    assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
+}
diff --git a/keystore2/src/legacy_importer.rs b/keystore2/src/legacy_importer.rs
index 045f848..0d8dc4a 100644
--- a/keystore2/src/legacy_importer.rs
+++ b/keystore2/src/legacy_importer.rs
@@ -786,7 +786,7 @@
                 .context(ks_err!("Trying to load legacy blob."))?;
 
             // Determine if the key needs special handling to be deleted.
-            let (need_gc, is_super_encrypted) = km_blob_params
+            let (need_gc, is_super_encrypted, is_de_critical) = km_blob_params
                 .as_ref()
                 .map(|(blob, params)| {
                     let params = match params {
@@ -798,13 +798,18 @@
                             KeyParameterValue::RollbackResistance == *kp.key_parameter_value()
                         }),
                         blob.is_encrypted(),
+                        blob.is_critical_to_device_encryption(),
                     )
                 })
-                .unwrap_or((false, false));
+                .unwrap_or((false, false, false));
 
             if keep_non_super_encrypted_keys && !is_super_encrypted {
                 continue;
             }
+            if uid == rustutils::users::AID_SYSTEM && is_de_critical {
+                log::info!("skip deletion of system key '{alias}' which is DE-critical");
+                continue;
+            }
 
             if need_gc {
                 let mark_deleted = match km_blob_params
@@ -923,7 +928,7 @@
         blob,
         &[],
         |blob| {
-            let _wd = wd::watch("Calling GetKeyCharacteristics.");
+            let _wd = wd::watch("get_key_characteristics_without_app_data: calling IKeyMintDevice::getKeyCharacteristics");
             map_km_error(km_dev.getKeyCharacteristics(blob, &[], &[]))
         },
         |_| Ok(()),
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index 43d99d1..7b6ea68 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -19,16 +19,19 @@
 use crate::error::map_km_error;
 use crate::error::Error;
 use crate::globals::get_keymint_device;
-use crate::globals::{DB, LEGACY_IMPORTER, SUPER_KEY};
+use crate::globals::{DB, ENCODED_MODULE_INFO, LEGACY_IMPORTER, SUPER_KEY};
 use crate::ks_err;
 use crate::permission::{KeyPerm, KeystorePerm};
 use crate::super_key::SuperKeyManager;
 use crate::utils::{
-    check_get_app_uids_affected_by_sid_permissions, check_key_permission,
+    check_dump_permission, check_get_app_uids_affected_by_sid_permissions, 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, SecurityLevel::SecurityLevel,
+    ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
+};
+use apex_aidl_interface::aidl::android::apex::{
+    IApexService::IApexService,
 };
 use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::{
     BnKeystoreMaintenance, IKeystoreMaintenance,
@@ -36,14 +39,47 @@
 use android_security_maintenance::binder::{
     BinderFeatures, Interface, Result as BinderResult, Strong, ThreadState,
 };
+use android_security_metrics::aidl::android::security::metrics::{
+    KeystoreAtomPayload::KeystoreAtomPayload::StorageStats
+};
 use android_system_keystore2::aidl::android::system::keystore2::KeyDescriptor::KeyDescriptor;
 use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
-use anyhow::{Context, Result};
+use anyhow::{anyhow, Context, Result};
+use binder::wait_for_interface;
+use bssl_crypto::digest;
+use der::{DerOrd, Encode, asn1::OctetString, asn1::SetOfVec, Sequence};
 use keystore2_crypto::Password;
+use rustutils::system_properties::PropertyWatcher;
+use std::cmp::Ordering;
 
 /// Reexport Domain for the benefit of DeleteListener
 pub use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
 
+#[cfg(test)]
+mod tests;
+
+/// Version number of KeyMint V4.
+pub const KEYMINT_V4: i32 = 400;
+
+/// Module information structure for DER-encoding.
+#[derive(Sequence, Debug, PartialEq, Eq)]
+struct ModuleInfo {
+    name: OctetString,
+    version: i64,
+}
+
+impl DerOrd for ModuleInfo {
+    // DER mandates "encodings of the component values of a set-of value shall appear in ascending
+    // order". `der_cmp` serves as a proxy for determining that ordering (though why the `der` crate
+    // requires this is unclear). Essentially, we just need to compare the `name` lengths, and then
+    // if those are equal, the `name`s themselves. (No need to consider `version`s since there can't
+    // be more than one `ModuleInfo` with the same `name` in the set-of `ModuleInfo`s.) We rely on
+    // `OctetString`'s `der_cmp` to do the aforementioned comparison.
+    fn der_cmp(&self, other: &Self) -> std::result::Result<Ordering, der::Error> {
+        self.name.der_cmp(&other.name)
+    }
+}
+
 /// The Maintenance module takes a delete listener argument which observes user and namespace
 /// deletion events.
 pub trait DeleteListener {
@@ -136,19 +172,35 @@
             .context(ks_err!("While invoking the delete listener."))
     }
 
-    fn call_with_watchdog<F>(sec_level: SecurityLevel, name: &'static str, op: &F) -> Result<()>
+    fn call_with_watchdog<F>(
+        sec_level: SecurityLevel,
+        name: &'static str,
+        op: &F,
+        min_version: Option<i32>,
+    ) -> Result<()>
     where
         F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>,
     {
-        let (km_dev, _, _) =
+        let (km_dev, hw_info, _) =
             get_keymint_device(&sec_level).context(ks_err!("getting keymint device"))?;
 
-        let _wp = wd::watch_millis_with("In call_with_watchdog", 500, (sec_level, name));
+        if let Some(min_version) = min_version {
+            if hw_info.versionNumber < min_version {
+                log::info!("skipping {name} for {sec_level:?} since its keymint version {} is less than the minimum required version {min_version}", hw_info.versionNumber);
+                return Ok(());
+            }
+        }
+
+        let _wp = wd::watch_millis_with("Maintenance::call_with_watchdog", 500, (sec_level, name));
         map_km_error(op(km_dev)).with_context(|| ks_err!("calling {}", name))?;
         Ok(())
     }
 
-    fn call_on_all_security_levels<F>(name: &'static str, op: F) -> Result<()>
+    fn call_on_all_security_levels<F>(
+        name: &'static str,
+        op: F,
+        min_version: Option<i32>,
+    ) -> Result<()>
     where
         F: Fn(Strong<dyn IKeyMintDevice>) -> binder::Result<()>,
     {
@@ -157,19 +209,29 @@
             (SecurityLevel::STRONGBOX, "STRONGBOX"),
         ];
         sec_levels.iter().try_fold((), |_result, (sec_level, sec_level_string)| {
-            let curr_result = Maintenance::call_with_watchdog(*sec_level, name, &op);
+            let curr_result = Maintenance::call_with_watchdog(*sec_level, name, &op, min_version);
             match curr_result {
                 Ok(()) => log::info!(
                     "Call to {} succeeded for security level {}.",
                     name,
                     &sec_level_string
                 ),
-                Err(ref e) => log::error!(
-                    "Call to {} failed for security level {}: {}.",
-                    name,
-                    &sec_level_string,
-                    e
-                ),
+                Err(ref e) => {
+                    if *sec_level == SecurityLevel::STRONGBOX
+                        && e.downcast_ref::<Error>()
+                            == Some(&Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
+                    {
+                        log::info!("Call to {} failed for StrongBox as it is not available", name);
+                        return Ok(());
+                    } else {
+                        log::error!(
+                            "Call to {} failed for security level {}: {}.",
+                            name,
+                            &sec_level_string,
+                            e
+                        )
+                    }
+                }
             }
             curr_result
         })
@@ -185,7 +247,61 @@
         {
             log::error!("SUPER_KEY.set_up_boot_level_cache failed:\n{:?}\n:(", e);
         }
-        Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded())
+
+        if keystore2_flags::attest_modules() {
+            std::thread::spawn(move || {
+                Self::watch_apex_info()
+                    .unwrap_or_else(|e| log::error!("watch_apex_info failed: {e:?}"));
+            });
+        } else {
+            rustutils::system_properties::write("keystore.module_hash.sent", "true")
+                .context(ks_err!("failed to set keystore.module_hash.sent to true"))?;
+        }
+        Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded(), None)
+    }
+
+    /// Watch the `apexd.status` system property, and read apex module information once
+    /// it is `activated`.
+    ///
+    /// Blocks waiting for system property changes, so must be run in its own thread.
+    fn watch_apex_info() -> Result<()> {
+        let apex_prop = "apexd.status";
+        log::info!("start monitoring '{apex_prop}' property");
+        let mut w =
+            PropertyWatcher::new(apex_prop).context(ks_err!("PropertyWatcher::new failed"))?;
+        loop {
+            let value = w.read(|_name, value| Ok(value.to_string()));
+            log::info!("property '{apex_prop}' is now '{value:?}'");
+            if matches!(value.as_deref(), Ok("activated")) {
+                let modules =
+                    Self::read_apex_info().context(ks_err!("failed to read apex info"))?;
+                Self::set_module_info(modules).context(ks_err!("failed to set module info"))?;
+                rustutils::system_properties::write("keystore.module_hash.sent", "true")
+                    .context(ks_err!("failed to set keystore.module_hash.sent to true"))?;
+                break;
+            }
+            log::info!("await a change to '{apex_prop}'...");
+            w.wait(None).context(ks_err!("property wait failed"))?;
+            log::info!("await a change to '{apex_prop}'...notified");
+        }
+        Ok(())
+    }
+
+    fn read_apex_info() -> Result<Vec<ModuleInfo>> {
+        let _wp = wd::watch("read_apex_info via IApexService.getActivePackages()");
+        let apexd: Strong<dyn IApexService> =
+            wait_for_interface("apexservice").context("failed to AIDL connect to apexd")?;
+        let packages = apexd.getActivePackages().context("failed to retrieve active packages")?;
+        packages
+            .into_iter()
+            .map(|pkg| {
+                log::info!("apex modules += {} version {}", pkg.moduleName, pkg.versionCode);
+                let name = OctetString::new(pkg.moduleName.as_bytes()).map_err(|e| {
+                    anyhow!("failed to convert '{}' to OCTET_STRING: {e:?}", pkg.moduleName)
+                })?;
+                Ok(ModuleInfo { name, version: pkg.versionCode })
+            })
+            .collect()
     }
 
     fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
@@ -241,7 +357,7 @@
             .context(ks_err!("Checking permission"))?;
         log::info!("In delete_all_keys.");
 
-        Maintenance::call_on_all_security_levels("deleteAllKeys", |dev| dev.deleteAllKeys())
+        Maintenance::call_on_all_security_levels("deleteAllKeys", |dev| dev.deleteAllKeys(), None)
     }
 
     fn get_app_uids_affected_by_sid(
@@ -255,9 +371,162 @@
         DB.with(|db| db.borrow_mut().get_app_uids_affected_by_sid(user_id, secure_user_id))
             .context(ks_err!("Failed to get app UIDs affected by SID"))
     }
+
+    fn dump_state(&self, f: &mut dyn std::io::Write) -> std::io::Result<()> {
+        writeln!(f, "keystore2 running")?;
+        writeln!(f)?;
+
+        // Display underlying device information
+        for sec_level in &[SecurityLevel::TRUSTED_ENVIRONMENT, SecurityLevel::STRONGBOX] {
+            let Ok((_dev, hw_info, uuid)) = get_keymint_device(sec_level) else { continue };
+
+            writeln!(f, "Device info for {sec_level:?} with {uuid:?}")?;
+            writeln!(f, "  HAL version:              {}", hw_info.versionNumber)?;
+            writeln!(f, "  Implementation name:      {}", hw_info.keyMintName)?;
+            writeln!(f, "  Implementation author:    {}", hw_info.keyMintAuthorName)?;
+            writeln!(f, "  Timestamp token required: {}", hw_info.timestampTokenRequired)?;
+        }
+        writeln!(f)?;
+
+        // Display module attestation information
+        {
+            let info = ENCODED_MODULE_INFO.read().unwrap();
+            if let Some(info) = info.as_ref() {
+                writeln!(f, "Attested module information (DER-encoded):")?;
+                writeln!(f, "  {}", hex::encode(info))?;
+                writeln!(f)?;
+            } else {
+                writeln!(f, "Attested module information not set")?;
+                writeln!(f)?;
+            }
+        }
+
+        // Display database size information.
+        match crate::metrics_store::pull_storage_stats() {
+            Ok(atoms) => {
+                writeln!(f, "Database size information (in bytes):")?;
+                for atom in atoms {
+                    if let StorageStats(stats) = &atom.payload {
+                        let stype = format!("{:?}", stats.storage_type);
+                        if stats.unused_size == 0 {
+                            writeln!(f, "  {:<40}: {:>12}", stype, stats.size)?;
+                        } else {
+                            writeln!(
+                                f,
+                                "  {:<40}: {:>12} (unused {})",
+                                stype, stats.size, stats.unused_size
+                            )?;
+                        }
+                    }
+                }
+            }
+            Err(e) => {
+                writeln!(f, "Failed to retrieve storage stats: {e:?}")?;
+            }
+        }
+        writeln!(f)?;
+
+        // Display database config information.
+        writeln!(f, "Database configuration:")?;
+        DB.with(|db| -> std::io::Result<()> {
+            let pragma_str = |f: &mut dyn std::io::Write, name| -> std::io::Result<()> {
+                let mut db = db.borrow_mut();
+                let value: String = db
+                    .pragma(name)
+                    .unwrap_or_else(|e| format!("unknown value for '{name}', failed: {e:?}"));
+                writeln!(f, "  {name} = {value}")
+            };
+            let pragma_i32 = |f: &mut dyn std::io::Write, name| -> std::io::Result<()> {
+                let mut db = db.borrow_mut();
+                let value: i32 = db.pragma(name).unwrap_or_else(|e| {
+                    log::error!("unknown value for '{name}', failed: {e:?}");
+                    -1
+                });
+                writeln!(f, "  {name} = {value}")
+            };
+            pragma_i32(f, "auto_vacuum")?;
+            pragma_str(f, "journal_mode")?;
+            pragma_i32(f, "journal_size_limit")?;
+            pragma_i32(f, "synchronous")?;
+            pragma_i32(f, "schema_version")?;
+            pragma_i32(f, "user_version")?;
+            Ok(())
+        })?;
+        writeln!(f)?;
+
+        // Display accumulated metrics.
+        writeln!(f, "Metrics information:")?;
+        writeln!(f)?;
+        write!(f, "{:?}", *crate::metrics_store::METRICS_STORE)?;
+        writeln!(f)?;
+
+        // Reminder: any additional information added to the `dump_state()` output needs to be
+        // careful not to include confidential information (e.g. key material).
+
+        Ok(())
+    }
+
+    fn set_module_info(module_info: Vec<ModuleInfo>) -> Result<()> {
+        log::info!("set_module_info with {} modules", module_info.len());
+        let encoding = Self::encode_module_info(module_info)
+            .map_err(|e| anyhow!({ e }))
+            .context(ks_err!("Failed to encode module_info"))?;
+        let hash = digest::Sha256::hash(&encoding).to_vec();
+
+        {
+            let mut saved = ENCODED_MODULE_INFO.write().unwrap();
+            if let Some(saved_encoding) = &*saved {
+                if *saved_encoding == encoding {
+                    log::warn!(
+                        "Module info already set, ignoring repeated attempt to set same info."
+                    );
+                    return Ok(());
+                }
+                return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!(
+                    "Failed to set module info as it is already set to a different value."
+                ));
+            }
+            *saved = Some(encoding);
+        }
+
+        let kps =
+            vec![KeyParameter { tag: Tag::MODULE_HASH, value: KeyParameterValue::Blob(hash) }];
+
+        Maintenance::call_on_all_security_levels(
+            "setAdditionalAttestationInfo",
+            |dev| dev.setAdditionalAttestationInfo(&kps),
+            Some(KEYMINT_V4),
+        )
+    }
+
+    fn encode_module_info(module_info: Vec<ModuleInfo>) -> Result<Vec<u8>, der::Error> {
+        SetOfVec::<ModuleInfo>::from_iter(module_info.into_iter())?.to_der()
+    }
 }
 
-impl Interface for Maintenance {}
+impl Interface for Maintenance {
+    fn dump(
+        &self,
+        f: &mut dyn std::io::Write,
+        _args: &[&std::ffi::CStr],
+    ) -> Result<(), binder::StatusCode> {
+        if !keystore2_flags::enable_dump() {
+            log::info!("skipping dump() as flag not enabled");
+            return Ok(());
+        }
+        log::info!("dump()");
+        let _wp = wd::watch("IKeystoreMaintenance::dump");
+        check_dump_permission().map_err(|_e| {
+            log::error!("dump permission denied");
+            binder::StatusCode::PERMISSION_DENIED
+        })?;
+
+        self.dump_state(f).map_err(|e| {
+            log::error!("dump_state failed: {e:?}");
+            binder::StatusCode::UNKNOWN_ERROR
+        })
+    }
+}
 
 impl IKeystoreMaintenance for Maintenance {
     fn onUserAdded(&self, user_id: i32) -> BinderResult<()> {
@@ -313,7 +582,7 @@
     }
 
     fn deleteAllKeys(&self) -> BinderResult<()> {
-        log::warn!("deleteAllKeys()");
+        log::warn!("deleteAllKeys() invoked, indicating initial setup or post-factory reset");
         let _wp = wd::watch("IKeystoreMaintenance::deleteAllKeys");
         Self::delete_all_keys().map_err(into_logged_binder)
     }
diff --git a/keystore2/src/maintenance/tests.rs b/keystore2/src/maintenance/tests.rs
new file mode 100644
index 0000000..fbafa73
--- /dev/null
+++ b/keystore2/src/maintenance/tests.rs
@@ -0,0 +1,182 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Maintenance tests.
+use super::*;
+use der::ErrorKind;
+
+#[test]
+fn test_encode_module_info_empty() {
+    let expected = vec![0x31, 0x00];
+    assert_eq!(expected, Maintenance::encode_module_info(Vec::new()).unwrap());
+}
+
+#[test]
+fn test_encode_module_info_same_name() {
+    // Same versions
+    let module_info: Vec<ModuleInfo> = vec![
+        ModuleInfo {
+            name: OctetString::new("com.android.os.statsd".to_string()).unwrap(),
+            version: 25,
+        },
+        ModuleInfo {
+            name: OctetString::new("com.android.os.statsd".to_string()).unwrap(),
+            version: 25,
+        },
+    ];
+    let actual = Maintenance::encode_module_info(module_info);
+    assert!(actual.is_err());
+    assert_eq!(ErrorKind::SetDuplicate, actual.unwrap_err().kind());
+
+    // Different versions
+    let module_info: Vec<ModuleInfo> = vec![
+        ModuleInfo {
+            name: OctetString::new("com.android.os.statsd".to_string()).unwrap(),
+            version: 3,
+        },
+        ModuleInfo {
+            name: OctetString::new("com.android.os.statsd".to_string()).unwrap(),
+            version: 789,
+        },
+    ];
+    let actual = Maintenance::encode_module_info(module_info);
+    assert!(actual.is_err());
+    assert_eq!(ErrorKind::SetDuplicate, actual.unwrap_err().kind());
+}
+
+#[test]
+fn test_encode_module_info_same_name_length() {
+    let module_info: Vec<ModuleInfo> = vec![
+        ModuleInfo { name: OctetString::new("com.android.wifi".to_string()).unwrap(), version: 2 },
+        ModuleInfo { name: OctetString::new("com.android.virt".to_string()).unwrap(), version: 1 },
+    ];
+    let actual = Maintenance::encode_module_info(module_info).unwrap();
+    let expected = hex::decode(concat!(
+        "312e",                             // SET OF, len 46
+        "3015",                             // SEQUENCE, len 21
+        "0410",                             // OCTET STRING, len 16
+        "636f6d2e616e64726f69642e76697274", // "com.android.virt"
+        "020101",                           // INTEGER len 1 value 1
+        "3015",                             // SEQUENCE, len 21
+        "0410",                             // OCTET STRING, len 16
+        "636f6d2e616e64726f69642e77696669", // "com.android.wifi"
+        "020102",                           // INTEGER len 1 value 2
+    ))
+    .unwrap();
+    assert_eq!(expected, actual);
+}
+
+#[test]
+fn test_encode_module_info_version_irrelevant() {
+    // Versions of the modules are irrelevant for determining encoding order since differing names
+    // guarantee a unique ascending order. See Maintenance::ModuleInfo::der_cmp for more detail.
+    let module_info: Vec<ModuleInfo> = vec![
+        ModuleInfo {
+            name: OctetString::new("com.android.extservices".to_string()).unwrap(),
+            version: 1,
+        },
+        ModuleInfo { name: OctetString::new("com.android.adbd".to_string()).unwrap(), version: 14 },
+    ];
+    let actual = Maintenance::encode_module_info(module_info).unwrap();
+    let expected = hex::decode(concat!(
+        "3135",                                           // SET OF, len 53
+        "3015",                                           // SEQUENCE, len 21
+        "0410",                                           // OCTET STRING, len 16
+        "636f6d2e616e64726f69642e61646264",               // "com.android.abdb"
+        "02010e",                                         // INTEGER len 2 value 14
+        "301c",                                           // SEQUENCE, len 28
+        "0417",                                           // OCTET STRING, len 23
+        "636f6d2e616e64726f69642e6578747365727669636573", // "com.android.extservices"
+        "020101",                                         // INTEGER len 1 value 1
+    ))
+    .unwrap();
+    assert_eq!(expected, actual);
+}
+
+#[test]
+fn test_encode_module_info_alphaordering_irrelevant() {
+    // Character ordering of the names of modules is irrelevant for determining encoding order since
+    // differing name lengths guarantee a unique ascending order. See
+    // Maintenance::ModuleInfo::der_cmp for more detail.
+    let module_info: Vec<ModuleInfo> = vec![
+        ModuleInfo {
+            name: OctetString::new("com.android.crashrecovery".to_string()).unwrap(),
+            version: 3,
+        },
+        ModuleInfo { name: OctetString::new("com.android.rkpd".to_string()).unwrap(), version: 8 },
+    ];
+    let actual = Maintenance::encode_module_info(module_info).unwrap();
+    let expected = hex::decode(concat!(
+        "3137",                                               // SET OF, len 55
+        "3015",                                               // SEQUENCE, len 21
+        "0410",                                               // OCTET STRING, len 16
+        "636f6d2e616e64726f69642e726b7064",                   // "com.android.rkpd"
+        "020108",                                             // INTEGER len 1 value 8
+        "301e",                                               // SEQUENCE, len 30
+        "0419",                                               // OCTET STRING, len 25
+        "636f6d2e616e64726f69642e63726173687265636f76657279", // "com.android.crashrecovery"
+        "020103",                                             // INTEGER len 1 value 3
+    ))
+    .unwrap();
+    assert_eq!(expected, actual);
+}
+
+#[test]
+fn test_encode_module_info() {
+    // Collection of `ModuleInfo`s from a few of the other test_encode_module_info_* tests
+    let module_info: Vec<ModuleInfo> = vec![
+        ModuleInfo { name: OctetString::new("com.android.rkpd".to_string()).unwrap(), version: 8 },
+        ModuleInfo {
+            name: OctetString::new("com.android.extservices".to_string()).unwrap(),
+            version: 1,
+        },
+        ModuleInfo {
+            name: OctetString::new("com.android.crashrecovery".to_string()).unwrap(),
+            version: 3,
+        },
+        ModuleInfo { name: OctetString::new("com.android.wifi".to_string()).unwrap(), version: 2 },
+        ModuleInfo { name: OctetString::new("com.android.virt".to_string()).unwrap(), version: 1 },
+        ModuleInfo { name: OctetString::new("com.android.adbd".to_string()).unwrap(), version: 14 },
+    ];
+    let actual = Maintenance::encode_module_info(module_info).unwrap();
+    let expected = hex::decode(concat!(
+        "31819a",                                             // SET OF, len 154
+        "3015",                                               // SEQUENCE, len 21
+        "0410",                                               // OCTET STRING, len 16
+        "636f6d2e616e64726f69642e61646264",                   // "com.android.abdb"
+        "02010e",                                             // INTEGER len 2 value 14
+        "3015",                                               // SEQUENCE, len 21
+        "0410",                                               // OCTET STRING, len 16
+        "636f6d2e616e64726f69642e726b7064",                   // "com.android.rkpd"
+        "020108",                                             // INTEGER len 1 value 8
+        "3015",                                               // SEQUENCE, len 21
+        "0410",                                               // OCTET STRING, len 16
+        "636f6d2e616e64726f69642e76697274",                   // "com.android.virt"
+        "020101",                                             // INTEGER len 1 value 1
+        "3015",                                               // SEQUENCE, len 21
+        "0410",                                               // OCTET STRING, len 16
+        "636f6d2e616e64726f69642e77696669",                   // "com.android.wifi"
+        "020102",                                             // INTEGER len 1 value 2
+        "301c",                                               // SEQUENCE, len 28
+        "0417",                                               // OCTET STRING, len 23
+        "636f6d2e616e64726f69642e6578747365727669636573",     // "com.android.extservices"
+        "020101",                                             // INTEGER len 1 value 1
+        "301e",                                               // SEQUENCE, len 30
+        "0419",                                               // OCTET STRING, len 25
+        "636f6d2e616e64726f69642e63726173687265636f76657279", // "com.android.crashrecovery"
+        "020103",                                             // INTEGER len 1 value 3
+    ))
+    .unwrap();
+    assert_eq!(expected, actual);
+}
diff --git a/keystore2/src/metrics.rs b/keystore2/src/metrics.rs
index 4757739..b884809 100644
--- a/keystore2/src/metrics.rs
+++ b/keystore2/src/metrics.rs
@@ -51,7 +51,7 @@
 
 impl IKeystoreMetrics for Metrics {
     fn pullMetrics(&self, atom_id: AtomID) -> BinderResult<Vec<KeystoreAtom>> {
-        let _wp = wd::watch("IKeystoreMetrics::pullMetrics");
+        let _wp = wd::watch_millis_with("IKeystoreMetrics::pullMetrics", 500, atom_id);
         self.pull_metrics(atom_id).map_err(into_logged_binder)
     }
 }
diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs
index 5a76d04..30c5973 100644
--- a/keystore2/src/metrics_store.rs
+++ b/keystore2/src/metrics_store.rs
@@ -22,6 +22,7 @@
 use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
 use crate::ks_err;
 use crate::operation::Outcome;
+use crate::utils::watchdog as wd;
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
     HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin,
@@ -44,18 +45,18 @@
     SecurityLevel::SecurityLevel as MetricsSecurityLevel, Storage::Storage as MetricsStorage,
 };
 use anyhow::{anyhow, Context, Result};
-use lazy_static::lazy_static;
 use std::collections::HashMap;
-use std::sync::Mutex;
+use std::sync::{LazyLock, Mutex};
+
+#[cfg(test)]
+mod tests;
 
 // Note: Crash events are recorded at keystore restarts, based on the assumption that keystore only
 // gets restarted after a crash, during a boot cycle.
 const KEYSTORE_CRASH_COUNT_PROPERTY: &str = "keystore.crash_count";
 
-lazy_static! {
-    /// Singleton for MetricsStore.
-    pub static ref METRICS_STORE: MetricsStore = Default::default();
-}
+/// Singleton for MetricsStore.
+pub static METRICS_STORE: LazyLock<MetricsStore> = LazyLock::new(Default::default);
 
 /// MetricsStore stores the <atom object, count> as <key, value> in the inner hash map,
 /// indexed by the atom id, in the outer hash map.
@@ -72,6 +73,26 @@
     metrics_store: Mutex<HashMap<AtomID, HashMap<KeystoreAtomPayload, i32>>>,
 }
 
+impl std::fmt::Debug for MetricsStore {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        let store = self.metrics_store.lock().unwrap();
+        let mut atom_ids: Vec<&AtomID> = store.keys().collect();
+        atom_ids.sort();
+        for atom_id in atom_ids {
+            writeln!(f, "  {} : [", atom_id.show())?;
+            let data = store.get(atom_id).unwrap();
+            let mut payloads: Vec<&KeystoreAtomPayload> = data.keys().collect();
+            payloads.sort();
+            for payload in payloads {
+                let count = data.get(payload).unwrap();
+                writeln!(f, "    {} => count={count}", payload.show())?;
+            }
+            writeln!(f, "  ]")?;
+        }
+        Ok(())
+    }
+}
+
 impl MetricsStore {
     /// There are some atoms whose maximum cardinality exceeds the cardinality limits tolerated
     /// by statsd. Statsd tolerates cardinality between 200-300. Therefore, the in-memory storage
@@ -85,13 +106,15 @@
     /// empty vector.
     pub fn get_atoms(&self, atom_id: AtomID) -> Result<Vec<KeystoreAtom>> {
         // StorageStats is an original pulled atom (i.e. not a pushed atom converted to a
-        // pulledd atom). Therefore, it is handled separately.
+        // pulled atom). Therefore, it is handled separately.
         if AtomID::STORAGE_STATS == atom_id {
+            let _wp = wd::watch("MetricsStore::get_atoms calling pull_storage_stats");
             return pull_storage_stats();
         }
 
         // Process keystore crash stats.
         if AtomID::CRASH_STATS == atom_id {
+            let _wp = wd::watch("MetricsStore::get_atoms calling read_keystore_crash_count");
             return match read_keystore_crash_count()? {
                 Some(count) => Ok(vec![KeystoreAtom {
                     payload: KeystoreAtomPayload::CrashStats(CrashStats {
@@ -103,8 +126,6 @@
             };
         }
 
-        // It is safe to call unwrap here since the lock can not be poisoned based on its usage
-        // in this module and the lock is not acquired in the same thread before.
         let metrics_store_guard = self.metrics_store.lock().unwrap();
         metrics_store_guard.get(&atom_id).map_or(Ok(Vec::<KeystoreAtom>::new()), |atom_count_map| {
             Ok(atom_count_map
@@ -116,8 +137,6 @@
 
     /// Insert an atom object to the metrics_store indexed by the atom ID.
     fn insert_atom(&self, atom_id: AtomID, atom: KeystoreAtomPayload) {
-        // It is ok to unwrap here since the mutex cannot be poisoned according to the way it is
-        // used in this module. And the lock is not acquired by this thread before.
         let mut metrics_store_guard = self.metrics_store.lock().unwrap();
         let atom_count_map = metrics_store_guard.entry(atom_id).or_default();
         if atom_count_map.len() < MetricsStore::SINGLE_ATOM_STORE_MAX_SIZE {
@@ -189,7 +208,7 @@
     };
 
     let mut key_creation_with_auth_info = KeyCreationWithAuthInfo {
-        user_auth_type: MetricsHardwareAuthenticatorType::AUTH_TYPE_UNSPECIFIED,
+        user_auth_type: MetricsHardwareAuthenticatorType::NO_AUTH_TYPE,
         log10_auth_key_timeout_seconds: -1,
         security_level: MetricsSecurityLevel::SECURITY_LEVEL_UNSPECIFIED,
     };
@@ -242,6 +261,12 @@
                     HardwareAuthenticatorType::FINGERPRINT => {
                         MetricsHardwareAuthenticatorType::FINGERPRINT
                     }
+                    a if a.0
+                        == HardwareAuthenticatorType::PASSWORD.0
+                            | HardwareAuthenticatorType::FINGERPRINT.0 =>
+                    {
+                        MetricsHardwareAuthenticatorType::PASSWORD_OR_FINGERPRINT
+                    }
                     HardwareAuthenticatorType::ANY => MetricsHardwareAuthenticatorType::ANY,
                     _ => MetricsHardwareAuthenticatorType::AUTH_TYPE_UNSPECIFIED,
                 }
@@ -519,7 +544,7 @@
     }
 }
 
-fn pull_storage_stats() -> Result<Vec<KeystoreAtom>> {
+pub(crate) fn pull_storage_stats() -> Result<Vec<KeystoreAtom>> {
     let mut atom_vec: Vec<KeystoreAtom> = Vec::new();
     let mut append = |stat| {
         match stat {
@@ -680,3 +705,281 @@
     ///Bit position in the KeyPurpose bitmap for Attest Key.
     ATTEST_KEY_BIT_POS = 7,
 }
+
+/// The various metrics-related types are not defined in this crate, so the orphan
+/// trait rule means that `std::fmt::Debug` cannot be implemented for them.
+/// Instead, create our own local trait that generates a debug string for a type.
+trait Summary {
+    fn show(&self) -> String;
+}
+
+/// Implement the [`Summary`] trait for AIDL-derived pseudo-enums, mapping named enum values to
+/// specified short names, all padded with spaces to the specified width (to allow improved
+/// readability when printed in a group).
+macro_rules! impl_summary_enum {
+    {  $enum:ident, $width:literal, $( $variant:ident => $short:literal ),+ $(,)? } => {
+        impl Summary for $enum{
+            fn show(&self) -> String {
+                match self.0 {
+                    $(
+                        x if x == Self::$variant.0 => format!(concat!("{:",
+                                                                      stringify!($width),
+                                                                      "}"),
+                                                              $short),
+                    )*
+                    v => format!("Unknown({})", v),
+                }
+            }
+        }
+    }
+}
+
+impl_summary_enum!(AtomID, 14,
+    STORAGE_STATS => "STORAGE",
+    KEYSTORE2_ATOM_WITH_OVERFLOW => "OVERFLOW",
+    KEY_CREATION_WITH_GENERAL_INFO => "KEYGEN_GENERAL",
+    KEY_CREATION_WITH_AUTH_INFO => "KEYGEN_AUTH",
+    KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO => "KEYGEN_MODES",
+    KEY_OPERATION_WITH_PURPOSE_AND_MODES_INFO => "KEYOP_MODES",
+    KEY_OPERATION_WITH_GENERAL_INFO => "KEYOP_GENERAL",
+    RKP_ERROR_STATS => "RKP_ERR",
+    CRASH_STATS => "CRASH",
+);
+
+impl_summary_enum!(MetricsStorage, 28,
+    STORAGE_UNSPECIFIED => "UNSPECIFIED",
+    KEY_ENTRY => "KEY_ENTRY",
+    KEY_ENTRY_ID_INDEX => "KEY_ENTRY_ID_IDX" ,
+    KEY_ENTRY_DOMAIN_NAMESPACE_INDEX => "KEY_ENTRY_DOMAIN_NS_IDX" ,
+    BLOB_ENTRY => "BLOB_ENTRY",
+    BLOB_ENTRY_KEY_ENTRY_ID_INDEX => "BLOB_ENTRY_KEY_ENTRY_ID_IDX" ,
+    KEY_PARAMETER => "KEY_PARAMETER",
+    KEY_PARAMETER_KEY_ENTRY_ID_INDEX => "KEY_PARAM_KEY_ENTRY_ID_IDX" ,
+    KEY_METADATA => "KEY_METADATA",
+    KEY_METADATA_KEY_ENTRY_ID_INDEX => "KEY_META_KEY_ENTRY_ID_IDX" ,
+    GRANT => "GRANT",
+    AUTH_TOKEN => "AUTH_TOKEN",
+    BLOB_METADATA => "BLOB_METADATA",
+    BLOB_METADATA_BLOB_ENTRY_ID_INDEX => "BLOB_META_BLOB_ENTRY_ID_IDX" ,
+    METADATA => "METADATA",
+    DATABASE => "DATABASE",
+    LEGACY_STORAGE => "LEGACY_STORAGE",
+);
+
+impl_summary_enum!(MetricsAlgorithm, 4,
+    ALGORITHM_UNSPECIFIED => "NONE",
+    RSA => "RSA",
+    EC => "EC",
+    AES => "AES",
+    TRIPLE_DES => "DES",
+    HMAC => "HMAC",
+);
+
+impl_summary_enum!(MetricsEcCurve, 5,
+    EC_CURVE_UNSPECIFIED => "NONE",
+    P_224 => "P-224",
+    P_256 => "P-256",
+    P_384 => "P-384",
+    P_521 => "P-521",
+    CURVE_25519 => "25519",
+);
+
+impl_summary_enum!(MetricsKeyOrigin, 10,
+    ORIGIN_UNSPECIFIED => "UNSPEC",
+    GENERATED => "GENERATED",
+    DERIVED => "DERIVED",
+    IMPORTED => "IMPORTED",
+    RESERVED => "RESERVED",
+    SECURELY_IMPORTED => "SEC-IMPORT",
+);
+
+impl_summary_enum!(MetricsSecurityLevel, 9,
+    SECURITY_LEVEL_UNSPECIFIED => "UNSPEC",
+    SECURITY_LEVEL_SOFTWARE => "SOFTWARE",
+    SECURITY_LEVEL_TRUSTED_ENVIRONMENT => "TEE",
+    SECURITY_LEVEL_STRONGBOX => "STRONGBOX",
+    SECURITY_LEVEL_KEYSTORE => "KEYSTORE",
+);
+
+impl_summary_enum!(MetricsHardwareAuthenticatorType, 8,
+    AUTH_TYPE_UNSPECIFIED => "UNSPEC",
+    NONE => "NONE",
+    PASSWORD => "PASSWD",
+    FINGERPRINT => "FPRINT",
+    PASSWORD_OR_FINGERPRINT => "PW_OR_FP",
+    ANY => "ANY",
+    NO_AUTH_TYPE => "NOAUTH",
+);
+
+impl_summary_enum!(MetricsPurpose, 7,
+    KEY_PURPOSE_UNSPECIFIED => "UNSPEC",
+    ENCRYPT => "ENCRYPT",
+    DECRYPT => "DECRYPT",
+    SIGN => "SIGN",
+    VERIFY => "VERIFY",
+    WRAP_KEY => "WRAPKEY",
+    AGREE_KEY => "AGREEKY",
+    ATTEST_KEY => "ATTESTK",
+);
+
+impl_summary_enum!(MetricsOutcome, 7,
+    OUTCOME_UNSPECIFIED => "UNSPEC",
+    DROPPED => "DROPPED",
+    SUCCESS => "SUCCESS",
+    ABORT => "ABORT",
+    PRUNED => "PRUNED",
+    ERROR => "ERROR",
+);
+
+impl_summary_enum!(MetricsRkpError, 6,
+    RKP_ERROR_UNSPECIFIED => "UNSPEC",
+    OUT_OF_KEYS => "OOKEYS",
+    FALL_BACK_DURING_HYBRID => "FALLBK",
+);
+
+/// Convert an argument into a corresponding format clause.  (This is needed because
+/// macro expansion text for repeated inputs needs to mention one of the repeated
+/// inputs.)
+macro_rules! format_clause {
+    {  $ignored:ident } => { "{}" }
+}
+
+/// Generate code to print a string corresponding to a bitmask, where the given
+/// enum identifies which bits mean what.  If additional bits (not included in
+/// the enum variants) are set, include the whole bitmask in the output so no
+/// information is lost.
+macro_rules! show_enum_bitmask {
+    {  $v:expr, $enum:ident, $( $variant:ident => $short:literal ),+ $(,)? } => {
+        {
+            let v: i32 = $v;
+            let mut displayed_mask = 0i32;
+            $(
+                displayed_mask |= 1 << $enum::$variant as i32;
+            )*
+            let undisplayed_mask = !displayed_mask;
+            let undisplayed = v & undisplayed_mask;
+            let extra = if undisplayed == 0 {
+                "".to_string()
+            } else {
+                format!("(full:{v:#010x})")
+            };
+            format!(
+                concat!( $( format_clause!($variant), )* "{}"),
+                $(
+                    if v & 1 << $enum::$variant as i32 != 0 { $short } else { "-" },
+                )*
+                extra
+            )
+        }
+    }
+}
+
+fn show_purpose(v: i32) -> String {
+    show_enum_bitmask!(v, KeyPurposeBitPosition,
+        ATTEST_KEY_BIT_POS => "A",
+        AGREE_KEY_BIT_POS => "G",
+        WRAP_KEY_BIT_POS => "W",
+        VERIFY_BIT_POS => "V",
+        SIGN_BIT_POS => "S",
+        DECRYPT_BIT_POS => "D",
+        ENCRYPT_BIT_POS => "E",
+    )
+}
+
+fn show_padding(v: i32) -> String {
+    show_enum_bitmask!(v, PaddingModeBitPosition,
+        PKCS7_BIT_POS => "7",
+        RSA_PKCS1_1_5_SIGN_BIT_POS => "S",
+        RSA_PKCS1_1_5_ENCRYPT_BIT_POS => "E",
+        RSA_PSS_BIT_POS => "P",
+        RSA_OAEP_BIT_POS => "O",
+        NONE_BIT_POSITION => "N",
+    )
+}
+
+fn show_digest(v: i32) -> String {
+    show_enum_bitmask!(v, DigestBitPosition,
+        SHA_2_512_BIT_POS => "5",
+        SHA_2_384_BIT_POS => "3",
+        SHA_2_256_BIT_POS => "2",
+        SHA_2_224_BIT_POS => "4",
+        SHA_1_BIT_POS => "1",
+        MD5_BIT_POS => "M",
+        NONE_BIT_POSITION => "N",
+    )
+}
+
+fn show_blockmode(v: i32) -> String {
+    show_enum_bitmask!(v, BlockModeBitPosition,
+        GCM_BIT_POS => "G",
+        CTR_BIT_POS => "T",
+        CBC_BIT_POS => "C",
+        ECB_BIT_POS => "E",
+    )
+}
+
+impl Summary for KeystoreAtomPayload {
+    fn show(&self) -> String {
+        match self {
+            KeystoreAtomPayload::StorageStats(v) => {
+                format!("{} sz={} unused={}", v.storage_type.show(), v.size, v.unused_size)
+            }
+            KeystoreAtomPayload::KeyCreationWithGeneralInfo(v) => {
+                format!(
+                    "{} ksz={:>4} crv={} {} rc={:4} attest? {}",
+                    v.algorithm.show(),
+                    v.key_size,
+                    v.ec_curve.show(),
+                    v.key_origin.show(),
+                    v.error_code,
+                    if v.attestation_requested { "Y" } else { "N" }
+                )
+            }
+            KeystoreAtomPayload::KeyCreationWithAuthInfo(v) => {
+                format!(
+                    "auth={} log(time)={:3} sec={}",
+                    v.user_auth_type.show(),
+                    v.log10_auth_key_timeout_seconds,
+                    v.security_level.show()
+                )
+            }
+            KeystoreAtomPayload::KeyCreationWithPurposeAndModesInfo(v) => {
+                format!(
+                    "{} purpose={} padding={} digest={} blockmode={}",
+                    v.algorithm.show(),
+                    show_purpose(v.purpose_bitmap),
+                    show_padding(v.padding_mode_bitmap),
+                    show_digest(v.digest_bitmap),
+                    show_blockmode(v.block_mode_bitmap),
+                )
+            }
+            KeystoreAtomPayload::KeyOperationWithGeneralInfo(v) => {
+                format!(
+                    "{} {:>8} upgraded? {} sec={}",
+                    v.outcome.show(),
+                    v.error_code,
+                    if v.key_upgraded { "Y" } else { "N" },
+                    v.security_level.show()
+                )
+            }
+            KeystoreAtomPayload::KeyOperationWithPurposeAndModesInfo(v) => {
+                format!(
+                    "{} padding={} digest={} blockmode={}",
+                    v.purpose.show(),
+                    show_padding(v.padding_mode_bitmap),
+                    show_digest(v.digest_bitmap),
+                    show_blockmode(v.block_mode_bitmap)
+                )
+            }
+            KeystoreAtomPayload::RkpErrorStats(v) => {
+                format!("{} sec={}", v.rkpError.show(), v.security_level.show())
+            }
+            KeystoreAtomPayload::CrashStats(v) => {
+                format!("count={}", v.count_of_crash_events)
+            }
+            KeystoreAtomPayload::Keystore2AtomWithOverflow(v) => {
+                format!("atom={}", v.atom_id.show())
+            }
+        }
+    }
+}
diff --git a/keystore2/src/metrics_store/tests.rs b/keystore2/src/metrics_store/tests.rs
new file mode 100644
index 0000000..95d4a01
--- /dev/null
+++ b/keystore2/src/metrics_store/tests.rs
@@ -0,0 +1,160 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::metrics_store::*;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+    HardwareAuthenticatorType::HardwareAuthenticatorType as AuthType, KeyParameter::KeyParameter,
+    KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
+};
+use android_security_metrics::aidl::android::security::metrics::{
+    HardwareAuthenticatorType::HardwareAuthenticatorType as MetricsAuthType,
+    SecurityLevel::SecurityLevel as MetricsSecurityLevel,
+};
+
+#[test]
+fn test_enum_show() {
+    let algo = MetricsAlgorithm::RSA;
+    assert_eq!("RSA ", algo.show());
+    let algo = MetricsAlgorithm(42);
+    assert_eq!("Unknown(42)", algo.show());
+}
+
+#[test]
+fn test_enum_bitmask_show() {
+    let mut modes = 0i32;
+    compute_block_mode_bitmap(&mut modes, BlockMode::ECB);
+    compute_block_mode_bitmap(&mut modes, BlockMode::CTR);
+
+    assert_eq!(show_blockmode(modes), "-T-E");
+
+    // Add some bits not covered by the enum of valid bit positions.
+    modes |= 0xa0;
+    assert_eq!(show_blockmode(modes), "-T-E(full:0x000000aa)");
+    modes |= 0x300;
+    assert_eq!(show_blockmode(modes), "-T-E(full:0x000003aa)");
+}
+
+fn create_key_param_with_auth_type(auth_type: AuthType) -> KeyParameter {
+    KeyParameter {
+        tag: Tag::USER_AUTH_TYPE,
+        value: KeyParameterValue::HardwareAuthenticatorType(auth_type),
+    }
+}
+
+#[test]
+fn test_user_auth_type() {
+    let test_cases = [
+        (vec![], MetricsAuthType::NO_AUTH_TYPE),
+        (vec![AuthType::NONE], MetricsAuthType::NONE),
+        (vec![AuthType::PASSWORD], MetricsAuthType::PASSWORD),
+        (vec![AuthType::FINGERPRINT], MetricsAuthType::FINGERPRINT),
+        (
+            vec![AuthType(AuthType::PASSWORD.0 | AuthType::FINGERPRINT.0)],
+            MetricsAuthType::PASSWORD_OR_FINGERPRINT,
+        ),
+        (vec![AuthType::ANY], MetricsAuthType::ANY),
+        // 7 is the "next" undefined HardwareAuthenticatorType enum tag number, so
+        // force this test to fail and be updated if someone adds a new enum value.
+        (vec![AuthType(7)], MetricsAuthType::AUTH_TYPE_UNSPECIFIED),
+        (vec![AuthType(123)], MetricsAuthType::AUTH_TYPE_UNSPECIFIED),
+        (
+            // In practice, Tag::USER_AUTH_TYPE isn't a repeatable tag. It's allowed
+            // to appear once for auth-bound keys and contains the binary OR of the
+            // applicable auth types. However, this test case repeats the tag more
+            // than once in order to unit test the logic that constructs the atom.
+            vec![AuthType::ANY, AuthType(123), AuthType::PASSWORD],
+            // The last auth type wins.
+            MetricsAuthType::PASSWORD,
+        ),
+    ];
+    for (auth_types, expected) in test_cases {
+        let key_params: Vec<_> =
+            auth_types.iter().map(|a| create_key_param_with_auth_type(*a)).collect();
+        let (_, atom_with_auth_info, _) = process_key_creation_event_stats(
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+            &key_params,
+            &Ok(()),
+        );
+        assert!(matches!(
+            atom_with_auth_info,
+            KeystoreAtomPayload::KeyCreationWithAuthInfo(a) if a.user_auth_type == expected
+        ));
+    }
+}
+
+fn create_key_param_with_auth_timeout(timeout: i32) -> KeyParameter {
+    KeyParameter { tag: Tag::AUTH_TIMEOUT, value: KeyParameterValue::Integer(timeout) }
+}
+
+#[test]
+fn test_log_auth_timeout_seconds() {
+    let test_cases = [
+        (vec![], -1),
+        (vec![-1], 0),
+        // The metrics code computes the value of this field for a timeout `t` with
+        // `f32::log10(t as f32) as i32`. The result of f32::log10(0 as f32) is `-inf`.
+        // Casting this to i32 means it gets "rounded" to i32::MIN, which is -2147483648.
+        (vec![0], -2147483648),
+        (vec![1], 0),
+        (vec![9], 0),
+        (vec![10], 1),
+        (vec![999], 2),
+        (
+            // In practice, Tag::AUTH_TIMEOUT isn't a repeatable tag. It's allowed to
+            // appear once for auth-bound keys. However, this test case repeats the
+            // tag more than once in order to unit test the logic that constructs the
+            // atom.
+            vec![1, 0, 10],
+            // The last timeout wins.
+            1,
+        ),
+    ];
+    for (timeouts, expected) in test_cases {
+        let key_params: Vec<_> =
+            timeouts.iter().map(|t| create_key_param_with_auth_timeout(*t)).collect();
+        let (_, atom_with_auth_info, _) = process_key_creation_event_stats(
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+            &key_params,
+            &Ok(()),
+        );
+        assert!(matches!(
+            atom_with_auth_info,
+            KeystoreAtomPayload::KeyCreationWithAuthInfo(a)
+                if a.log10_auth_key_timeout_seconds == expected
+        ));
+    }
+}
+
+#[test]
+fn test_security_level() {
+    let test_cases = [
+        (SecurityLevel::SOFTWARE, MetricsSecurityLevel::SECURITY_LEVEL_SOFTWARE),
+        (
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+            MetricsSecurityLevel::SECURITY_LEVEL_TRUSTED_ENVIRONMENT,
+        ),
+        (SecurityLevel::STRONGBOX, MetricsSecurityLevel::SECURITY_LEVEL_STRONGBOX),
+        (SecurityLevel::KEYSTORE, MetricsSecurityLevel::SECURITY_LEVEL_KEYSTORE),
+        (SecurityLevel(123), MetricsSecurityLevel::SECURITY_LEVEL_UNSPECIFIED),
+    ];
+    for (security_level, expected) in test_cases {
+        let (_, atom_with_auth_info, _) =
+            process_key_creation_event_stats(security_level, &[], &Ok(()));
+        assert!(matches!(
+            atom_with_auth_info,
+            KeystoreAtomPayload::KeyCreationWithAuthInfo(a)
+                if a.security_level == expected
+        ));
+    }
+}
diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs
index 7d988e1..c11c1f4 100644
--- a/keystore2/src/operation.rs
+++ b/keystore2/src/operation.rs
@@ -31,6 +31,7 @@
 //!  * `abort` is called.
 //!  * The operation gets dropped.
 //!  * The operation gets pruned.
+//!
 //! `Operation` has an `Outcome` member. While the outcome is `Outcome::Unknown`,
 //! the operation is active and in a good state. Any of the above conditions may
 //! change the outcome to one of the defined outcomes Success, Abort, Dropped,
@@ -286,7 +287,7 @@
         }
         *locked_outcome = Outcome::Pruned;
 
-        let _wp = wd::watch("In Operation::prune: calling abort()");
+        let _wp = wd::watch("Operation::prune: calling IKeyMintOperation::abort()");
 
         // We abort the operation. If there was an error we log it but ignore it.
         if let Err(e) = map_km_error(self.km_op.abort()) {
@@ -307,9 +308,8 @@
         locked_outcome: &mut Outcome,
         err: Result<T, Error>,
     ) -> Result<T, Error> {
-        match &err {
-            Err(e) => *locked_outcome = Outcome::ErrorCode(error_to_serialized_error(e)),
-            Ok(_) => (),
+        if let Err(e) = &err {
+            *locked_outcome = Outcome::ErrorCode(error_to_serialized_error(e))
         }
         err
     }
@@ -362,7 +362,7 @@
             .context(ks_err!("Trying to get auth tokens."))?;
 
         self.update_outcome(&mut outcome, {
-            let _wp = wd::watch("Operation::update_aad: calling updateAad");
+            let _wp = wd::watch("Operation::update_aad: calling IKeyMintOperation::updateAad");
             map_km_error(self.km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref()))
         })
         .context(ks_err!("Update failed."))?;
@@ -386,7 +386,7 @@
 
         let output = self
             .update_outcome(&mut outcome, {
-                let _wp = wd::watch("Operation::update: calling update");
+                let _wp = wd::watch("Operation::update: calling IKeyMintOperation::update");
                 map_km_error(self.km_op.update(input, hat.as_ref(), tst.as_ref()))
             })
             .context(ks_err!("Update failed."))?;
@@ -416,7 +416,7 @@
 
         let output = self
             .update_outcome(&mut outcome, {
-                let _wp = wd::watch("Operation::finish: calling finish");
+                let _wp = wd::watch("Operation::finish: calling IKeyMintOperation::finish");
                 map_km_error(self.km_op.finish(
                     input,
                     signature,
@@ -447,7 +447,7 @@
         *locked_outcome = outcome;
 
         {
-            let _wp = wd::watch("Operation::abort: calling abort");
+            let _wp = wd::watch("Operation::abort: calling IKeyMintOperation::abort");
             map_km_error(self.km_op.abort()).context(ks_err!("KeyMint::abort failed."))
         }
     }
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index 982bc82..023774f 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -26,11 +26,11 @@
 };
 use anyhow::Context as AnyhowContext;
 use keystore2_selinux as selinux;
-use lazy_static::lazy_static;
 use selinux::{implement_class, Backend, ClassPermission};
 use std::cmp::PartialEq;
 use std::convert::From;
 use std::ffi::CStr;
+use std::sync::LazyLock;
 
 // Replace getcon with a mock in the test situation
 #[cfg(not(test))]
@@ -38,12 +38,13 @@
 #[cfg(test)]
 use tests::test_getcon as getcon;
 
-lazy_static! {
-    // Panicking here is allowed because keystore cannot function without this backend
-    // and it would happen early and indicate a gross misconfiguration of the device.
-    static ref KEYSTORE2_KEY_LABEL_BACKEND: selinux::KeystoreKeyBackend =
-            selinux::KeystoreKeyBackend::new().unwrap();
-}
+#[cfg(test)]
+mod tests;
+
+// Panicking here is allowed because keystore cannot function without this backend
+// and it would happen early and indicate a gross misconfiguration of the device.
+static KEYSTORE2_KEY_LABEL_BACKEND: LazyLock<selinux::KeystoreKeyBackend> =
+    LazyLock::new(|| selinux::KeystoreKeyBackend::new().unwrap());
 
 fn lookup_keystore2_key_context(namespace: i64) -> anyhow::Result<selinux::Context> {
     KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string())
@@ -281,12 +282,19 @@
 ///                      SELinux keystore key backend, and the result is used
 ///                      as target context.
 pub fn check_grant_permission(
+    caller_uid: u32,
     caller_ctx: &CStr,
     access_vec: KeyPermSet,
     key: &KeyDescriptor,
 ) -> anyhow::Result<()> {
     let target_context = match key.domain {
-        Domain::APP => getcon().context("check_grant_permission: getcon failed.")?,
+        Domain::APP => {
+            if caller_uid as i64 != key.nspace {
+                return Err(selinux::Error::perm())
+                    .context("Trying to access key without ownership.");
+            }
+            getcon().context("check_grant_permission: getcon failed.")?
+        }
         Domain::SELINUX => lookup_keystore2_key_context(key.nspace)
             .context("check_grant_permission: Domain::SELINUX: Failed to lookup namespace.")?,
         _ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)),
@@ -397,433 +405,3 @@
 
     selinux::check_permission(caller_ctx, &target_context, perm)
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use anyhow::anyhow;
-    use anyhow::Result;
-    use keystore2_selinux::*;
-
-    const ALL_PERMS: KeyPermSet = key_perm_set![
-        KeyPerm::ManageBlob,
-        KeyPerm::Delete,
-        KeyPerm::UseDevId,
-        KeyPerm::ReqForcedOp,
-        KeyPerm::GenUniqueId,
-        KeyPerm::Grant,
-        KeyPerm::GetInfo,
-        KeyPerm::Rebind,
-        KeyPerm::Update,
-        KeyPerm::Use,
-        KeyPerm::ConvertStorageKeyToEphemeral,
-    ];
-
-    const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![
-        KeyPerm::Delete,
-        KeyPerm::UseDevId,
-        // No KeyPerm::Grant
-        KeyPerm::GetInfo,
-        KeyPerm::Rebind,
-        KeyPerm::Update,
-        KeyPerm::Use,
-    ];
-
-    const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![
-        KeyPerm::ManageBlob,
-        KeyPerm::Delete,
-        KeyPerm::UseDevId,
-        KeyPerm::ReqForcedOp,
-        KeyPerm::GenUniqueId,
-        // No KeyPerm::Grant
-        KeyPerm::GetInfo,
-        KeyPerm::Rebind,
-        KeyPerm::Update,
-        KeyPerm::Use,
-        KeyPerm::ConvertStorageKeyToEphemeral,
-    ];
-
-    const UNPRIV_PERMS: KeyPermSet = key_perm_set![
-        KeyPerm::Delete,
-        KeyPerm::GetInfo,
-        KeyPerm::Rebind,
-        KeyPerm::Update,
-        KeyPerm::Use,
-    ];
-
-    /// The su_key namespace as defined in su.te and keystore_key_contexts of the
-    /// SePolicy (system/sepolicy).
-    const SU_KEY_NAMESPACE: i32 = 0;
-    /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
-    /// SePolicy (system/sepolicy).
-    const SHELL_KEY_NAMESPACE: i32 = 1;
-
-    pub fn test_getcon() -> Result<Context> {
-        Context::new("u:object_r:keystore:s0")
-    }
-
-    // This macro evaluates the given expression and checks that
-    // a) evaluated to Result::Err() and that
-    // b) the wrapped error is selinux::Error::perm() (permission denied).
-    // We use a macro here because a function would mask which invocation caused the failure.
-    //
-    // TODO b/164121720 Replace this macro with a function when `track_caller` is available.
-    macro_rules! assert_perm_failed {
-        ($test_function:expr) => {
-            let result = $test_function;
-            assert!(result.is_err(), "Permission check should have failed.");
-            assert_eq!(
-                Some(&selinux::Error::perm()),
-                result.err().unwrap().root_cause().downcast_ref::<selinux::Error>()
-            );
-        };
-    }
-
-    fn check_context() -> Result<(selinux::Context, i32, bool)> {
-        // Calling the non mocked selinux::getcon here intended.
-        let context = selinux::getcon()?;
-        match context.to_str().unwrap() {
-            "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
-            "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
-            c => Err(anyhow!(format!(
-                "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
-                c
-            ))),
-        }
-    }
-
-    #[test]
-    fn check_keystore_permission_test() -> Result<()> {
-        let system_server_ctx = Context::new("u:r:system_server:s0")?;
-        assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok());
-        assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok());
-        assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Lock).is_ok());
-        assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Reset).is_ok());
-        assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Unlock).is_ok());
-        assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangeUser).is_ok());
-        assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok());
-        assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok());
-        let shell_ctx = Context::new("u:r:shell:s0")?;
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth));
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs));
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::List));
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Lock));
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Reset));
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Unlock));
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangeUser));
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword));
-        assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID));
-        Ok(())
-    }
-
-    #[test]
-    fn check_grant_permission_app() -> Result<()> {
-        let system_server_ctx = Context::new("u:r:system_server:s0")?;
-        let shell_ctx = Context::new("u:r:shell:s0")?;
-        let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
-        check_grant_permission(&system_server_ctx, SYSTEM_SERVER_PERMISSIONS_NO_GRANT, &key)
-            .expect("Grant permission check failed.");
-
-        // attempts to grant the grant permission must always fail even when privileged.
-        assert_perm_failed!(check_grant_permission(
-            &system_server_ctx,
-            KeyPerm::Grant.into(),
-            &key
-        ));
-        // unprivileged grant attempts always fail. shell does not have the grant permission.
-        assert_perm_failed!(check_grant_permission(&shell_ctx, UNPRIV_PERMS, &key));
-        Ok(())
-    }
-
-    #[test]
-    fn check_grant_permission_selinux() -> Result<()> {
-        let (sctx, namespace, is_su) = check_context()?;
-        let key = KeyDescriptor {
-            domain: Domain::SELINUX,
-            nspace: namespace as i64,
-            alias: None,
-            blob: None,
-        };
-        if is_su {
-            assert!(check_grant_permission(&sctx, NOT_GRANT_PERMS, &key).is_ok());
-            // attempts to grant the grant permission must always fail even when privileged.
-            assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::Grant.into(), &key));
-        } else {
-            // unprivileged grant attempts always fail. shell does not have the grant permission.
-            assert_perm_failed!(check_grant_permission(&sctx, UNPRIV_PERMS, &key));
-        }
-        Ok(())
-    }
-
-    #[test]
-    fn check_key_permission_domain_grant() -> Result<()> {
-        let key = KeyDescriptor { domain: Domain::GRANT, nspace: 0, alias: None, blob: None };
-
-        assert_perm_failed!(check_key_permission(
-            0,
-            &selinux::Context::new("ignored").unwrap(),
-            KeyPerm::Grant,
-            &key,
-            &Some(UNPRIV_PERMS)
-        ));
-
-        check_key_permission(
-            0,
-            &selinux::Context::new("ignored").unwrap(),
-            KeyPerm::Use,
-            &key,
-            &Some(ALL_PERMS),
-        )
-    }
-
-    #[test]
-    fn check_key_permission_domain_app() -> Result<()> {
-        let system_server_ctx = Context::new("u:r:system_server:s0")?;
-        let shell_ctx = Context::new("u:r:shell:s0")?;
-        let gmscore_app = Context::new("u:r:gmscore_app:s0")?;
-
-        let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
-
-        assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Use, &key, &None).is_ok());
-        assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Delete, &key, &None).is_ok());
-        assert!(check_key_permission(0, &system_server_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
-        assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Rebind, &key, &None).is_ok());
-        assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Update, &key, &None).is_ok());
-        assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Grant, &key, &None).is_ok());
-        assert!(check_key_permission(0, &system_server_ctx, KeyPerm::UseDevId, &key, &None).is_ok());
-        assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &key, &None).is_ok());
-
-        assert!(check_key_permission(0, &shell_ctx, KeyPerm::Use, &key, &None).is_ok());
-        assert!(check_key_permission(0, &shell_ctx, KeyPerm::Delete, &key, &None).is_ok());
-        assert!(check_key_permission(0, &shell_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
-        assert!(check_key_permission(0, &shell_ctx, KeyPerm::Rebind, &key, &None).is_ok());
-        assert!(check_key_permission(0, &shell_ctx, KeyPerm::Update, &key, &None).is_ok());
-        assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::Grant, &key, &None));
-        assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ReqForcedOp, &key, &None));
-        assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None));
-        assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None));
-        assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &key, &None));
-
-        // Also make sure that the permission fails if the caller is not the owner.
-        assert_perm_failed!(check_key_permission(
-            1, // the owner is 0
-            &system_server_ctx,
-            KeyPerm::Use,
-            &key,
-            &None
-        ));
-        // Unless there was a grant.
-        assert!(check_key_permission(
-            1,
-            &system_server_ctx,
-            KeyPerm::Use,
-            &key,
-            &Some(key_perm_set![KeyPerm::Use])
-        )
-        .is_ok());
-        // But fail if the grant did not cover the requested permission.
-        assert_perm_failed!(check_key_permission(
-            1,
-            &system_server_ctx,
-            KeyPerm::Use,
-            &key,
-            &Some(key_perm_set![KeyPerm::GetInfo])
-        ));
-
-        Ok(())
-    }
-
-    #[test]
-    fn check_key_permission_domain_selinux() -> Result<()> {
-        let (sctx, namespace, is_su) = check_context()?;
-        let key = KeyDescriptor {
-            domain: Domain::SELINUX,
-            nspace: namespace as i64,
-            alias: None,
-            blob: None,
-        };
-
-        assert!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None).is_ok());
-        assert!(check_key_permission(0, &sctx, KeyPerm::Delete, &key, &None).is_ok());
-        assert!(check_key_permission(0, &sctx, KeyPerm::GetInfo, &key, &None).is_ok());
-        assert!(check_key_permission(0, &sctx, KeyPerm::Rebind, &key, &None).is_ok());
-        assert!(check_key_permission(0, &sctx, KeyPerm::Update, &key, &None).is_ok());
-
-        if is_su {
-            assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok());
-            assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok());
-            assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok());
-            assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok());
-            assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok());
-        } else {
-            assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None));
-            assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None));
-            assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None));
-            assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None));
-            assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None));
-        }
-        Ok(())
-    }
-
-    #[test]
-    fn check_key_permission_domain_blob() -> Result<()> {
-        let (sctx, namespace, is_su) = check_context()?;
-        let key = KeyDescriptor {
-            domain: Domain::BLOB,
-            nspace: namespace as i64,
-            alias: None,
-            blob: None,
-        };
-
-        if is_su {
-            check_key_permission(0, &sctx, KeyPerm::Use, &key, &None)
-        } else {
-            assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None));
-            Ok(())
-        }
-    }
-
-    #[test]
-    fn check_key_permission_domain_key_id() -> Result<()> {
-        let key = KeyDescriptor { domain: Domain::KEY_ID, nspace: 0, alias: None, blob: None };
-
-        assert_eq!(
-            Some(&KsError::sys()),
-            check_key_permission(
-                0,
-                &selinux::Context::new("ignored").unwrap(),
-                KeyPerm::Use,
-                &key,
-                &None
-            )
-            .err()
-            .unwrap()
-            .root_cause()
-            .downcast_ref::<KsError>()
-        );
-        Ok(())
-    }
-
-    #[test]
-    fn key_perm_set_all_test() {
-        let v = key_perm_set![
-            KeyPerm::ManageBlob,
-            KeyPerm::Delete,
-            KeyPerm::UseDevId,
-            KeyPerm::ReqForcedOp,
-            KeyPerm::GenUniqueId,
-            KeyPerm::Grant,
-            KeyPerm::GetInfo,
-            KeyPerm::Rebind,
-            KeyPerm::Update,
-            KeyPerm::Use // Test if the macro accepts missing comma at the end of the list.
-        ];
-        let mut i = v.into_iter();
-        assert_eq!(i.next().unwrap().name(), "delete");
-        assert_eq!(i.next().unwrap().name(), "gen_unique_id");
-        assert_eq!(i.next().unwrap().name(), "get_info");
-        assert_eq!(i.next().unwrap().name(), "grant");
-        assert_eq!(i.next().unwrap().name(), "manage_blob");
-        assert_eq!(i.next().unwrap().name(), "rebind");
-        assert_eq!(i.next().unwrap().name(), "req_forced_op");
-        assert_eq!(i.next().unwrap().name(), "update");
-        assert_eq!(i.next().unwrap().name(), "use");
-        assert_eq!(i.next().unwrap().name(), "use_dev_id");
-        assert_eq!(None, i.next());
-    }
-    #[test]
-    fn key_perm_set_sparse_test() {
-        let v = key_perm_set![
-            KeyPerm::ManageBlob,
-            KeyPerm::ReqForcedOp,
-            KeyPerm::GenUniqueId,
-            KeyPerm::Update,
-            KeyPerm::Use, // Test if macro accepts the comma at the end of the list.
-        ];
-        let mut i = v.into_iter();
-        assert_eq!(i.next().unwrap().name(), "gen_unique_id");
-        assert_eq!(i.next().unwrap().name(), "manage_blob");
-        assert_eq!(i.next().unwrap().name(), "req_forced_op");
-        assert_eq!(i.next().unwrap().name(), "update");
-        assert_eq!(i.next().unwrap().name(), "use");
-        assert_eq!(None, i.next());
-    }
-    #[test]
-    fn key_perm_set_empty_test() {
-        let v = key_perm_set![];
-        let mut i = v.into_iter();
-        assert_eq!(None, i.next());
-    }
-    #[test]
-    fn key_perm_set_include_subset_test() {
-        let v1 = key_perm_set![
-            KeyPerm::ManageBlob,
-            KeyPerm::Delete,
-            KeyPerm::UseDevId,
-            KeyPerm::ReqForcedOp,
-            KeyPerm::GenUniqueId,
-            KeyPerm::Grant,
-            KeyPerm::GetInfo,
-            KeyPerm::Rebind,
-            KeyPerm::Update,
-            KeyPerm::Use,
-        ];
-        let v2 = key_perm_set![
-            KeyPerm::ManageBlob,
-            KeyPerm::Delete,
-            KeyPerm::Rebind,
-            KeyPerm::Update,
-            KeyPerm::Use,
-        ];
-        assert!(v1.includes(v2));
-        assert!(!v2.includes(v1));
-    }
-    #[test]
-    fn key_perm_set_include_equal_test() {
-        let v1 = key_perm_set![
-            KeyPerm::ManageBlob,
-            KeyPerm::Delete,
-            KeyPerm::Rebind,
-            KeyPerm::Update,
-            KeyPerm::Use,
-        ];
-        let v2 = key_perm_set![
-            KeyPerm::ManageBlob,
-            KeyPerm::Delete,
-            KeyPerm::Rebind,
-            KeyPerm::Update,
-            KeyPerm::Use,
-        ];
-        assert!(v1.includes(v2));
-        assert!(v2.includes(v1));
-    }
-    #[test]
-    fn key_perm_set_include_overlap_test() {
-        let v1 = key_perm_set![
-            KeyPerm::ManageBlob,
-            KeyPerm::Delete,
-            KeyPerm::Grant, // only in v1
-            KeyPerm::Rebind,
-            KeyPerm::Update,
-            KeyPerm::Use,
-        ];
-        let v2 = key_perm_set![
-            KeyPerm::ManageBlob,
-            KeyPerm::Delete,
-            KeyPerm::ReqForcedOp, // only in v2
-            KeyPerm::Rebind,
-            KeyPerm::Update,
-            KeyPerm::Use,
-        ];
-        assert!(!v1.includes(v2));
-        assert!(!v2.includes(v1));
-    }
-    #[test]
-    fn key_perm_set_include_no_overlap_test() {
-        let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,];
-        let v2 =
-            key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,];
-        assert!(!v1.includes(v2));
-        assert!(!v2.includes(v1));
-    }
-}
diff --git a/keystore2/src/permission/tests.rs b/keystore2/src/permission/tests.rs
new file mode 100644
index 0000000..68c9b74
--- /dev/null
+++ b/keystore2/src/permission/tests.rs
@@ -0,0 +1,430 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Access control tests.
+
+use super::*;
+use crate::key_perm_set;
+use anyhow::anyhow;
+use anyhow::Result;
+use keystore2_selinux::*;
+
+const ALL_PERMS: KeyPermSet = key_perm_set![
+    KeyPerm::ManageBlob,
+    KeyPerm::Delete,
+    KeyPerm::UseDevId,
+    KeyPerm::ReqForcedOp,
+    KeyPerm::GenUniqueId,
+    KeyPerm::Grant,
+    KeyPerm::GetInfo,
+    KeyPerm::Rebind,
+    KeyPerm::Update,
+    KeyPerm::Use,
+    KeyPerm::ConvertStorageKeyToEphemeral,
+];
+
+const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![
+    KeyPerm::Delete,
+    KeyPerm::UseDevId,
+    // No KeyPerm::Grant
+    KeyPerm::GetInfo,
+    KeyPerm::Rebind,
+    KeyPerm::Update,
+    KeyPerm::Use,
+];
+
+const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![
+    KeyPerm::ManageBlob,
+    KeyPerm::Delete,
+    KeyPerm::UseDevId,
+    KeyPerm::ReqForcedOp,
+    KeyPerm::GenUniqueId,
+    // No KeyPerm::Grant
+    KeyPerm::GetInfo,
+    KeyPerm::Rebind,
+    KeyPerm::Update,
+    KeyPerm::Use,
+    KeyPerm::ConvertStorageKeyToEphemeral,
+];
+
+const UNPRIV_PERMS: KeyPermSet = key_perm_set![
+    KeyPerm::Delete,
+    KeyPerm::GetInfo,
+    KeyPerm::Rebind,
+    KeyPerm::Update,
+    KeyPerm::Use,
+];
+
+/// The su_key namespace as defined in su.te and keystore_key_contexts of the
+/// SePolicy (system/sepolicy).
+const SU_KEY_NAMESPACE: i32 = 0;
+/// The shell_key namespace as defined in shell.te and keystore_key_contexts of the
+/// SePolicy (system/sepolicy).
+const SHELL_KEY_NAMESPACE: i32 = 1;
+
+pub fn test_getcon() -> Result<Context> {
+    Context::new("u:object_r:keystore:s0")
+}
+
+// This macro evaluates the given expression and checks that
+// a) evaluated to Result::Err() and that
+// b) the wrapped error is selinux::Error::perm() (permission denied).
+// We use a macro here because a function would mask which invocation caused the failure.
+//
+// TODO b/164121720 Replace this macro with a function when `track_caller` is available.
+macro_rules! assert_perm_failed {
+    ($test_function:expr) => {
+        let result = $test_function;
+        assert!(result.is_err(), "Permission check should have failed.");
+        assert_eq!(
+            Some(&selinux::Error::perm()),
+            result.err().unwrap().root_cause().downcast_ref::<selinux::Error>()
+        );
+    };
+}
+
+fn check_context() -> Result<(selinux::Context, i32, bool)> {
+    // Calling the non mocked selinux::getcon here intended.
+    let context = selinux::getcon()?;
+    match context.to_str().unwrap() {
+        "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)),
+        "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)),
+        c => Err(anyhow!(format!(
+            "This test must be run as \"su\" or \"shell\". Current context: \"{}\"",
+            c
+        ))),
+    }
+}
+
+#[test]
+fn check_keystore_permission_test() -> Result<()> {
+    let system_server_ctx = Context::new("u:r:system_server:s0")?;
+    assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok());
+    assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok());
+    assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Lock).is_ok());
+    assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Reset).is_ok());
+    assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Unlock).is_ok());
+    assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangeUser).is_ok());
+    assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok());
+    assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok());
+    let shell_ctx = Context::new("u:r:shell:s0")?;
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth));
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs));
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::List));
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Lock));
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Reset));
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Unlock));
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangeUser));
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword));
+    assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID));
+    Ok(())
+}
+
+#[test]
+fn check_grant_permission_app() -> Result<()> {
+    let system_server_ctx = Context::new("u:r:system_server:s0")?;
+    let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
+    check_grant_permission(0, &system_server_ctx, SYSTEM_SERVER_PERMISSIONS_NO_GRANT, &key)
+        .expect("Grant permission check failed.");
+
+    // attempts to grant the grant permission must always fail even when privileged.
+    assert_perm_failed!(check_grant_permission(0, &system_server_ctx, KeyPerm::Grant.into(), &key));
+    Ok(())
+}
+
+#[test]
+fn check_grant_permission_selinux() -> Result<()> {
+    let (sctx, namespace, is_su) = check_context()?;
+    let key = KeyDescriptor {
+        domain: Domain::SELINUX,
+        nspace: namespace as i64,
+        alias: None,
+        blob: None,
+    };
+    if is_su {
+        assert!(check_grant_permission(0, &sctx, NOT_GRANT_PERMS, &key).is_ok());
+        // attempts to grant the grant permission must always fail even when privileged.
+        assert_perm_failed!(check_grant_permission(0, &sctx, KeyPerm::Grant.into(), &key));
+    } else {
+        // unprivileged grant attempts always fail. shell does not have the grant permission.
+        assert_perm_failed!(check_grant_permission(0, &sctx, UNPRIV_PERMS, &key));
+    }
+    Ok(())
+}
+
+#[test]
+fn check_key_permission_domain_grant() -> Result<()> {
+    let key = KeyDescriptor { domain: Domain::GRANT, nspace: 0, alias: None, blob: None };
+
+    assert_perm_failed!(check_key_permission(
+        0,
+        &selinux::Context::new("ignored").unwrap(),
+        KeyPerm::Grant,
+        &key,
+        &Some(UNPRIV_PERMS)
+    ));
+
+    check_key_permission(
+        0,
+        &selinux::Context::new("ignored").unwrap(),
+        KeyPerm::Use,
+        &key,
+        &Some(ALL_PERMS),
+    )
+}
+
+#[test]
+fn check_key_permission_domain_app() -> Result<()> {
+    let system_server_ctx = Context::new("u:r:system_server:s0")?;
+    let shell_ctx = Context::new("u:r:shell:s0")?;
+    let gmscore_app = Context::new("u:r:gmscore_app:s0")?;
+
+    let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
+
+    assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Use, &key, &None).is_ok());
+    assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Delete, &key, &None).is_ok());
+    assert!(check_key_permission(0, &system_server_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
+    assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Rebind, &key, &None).is_ok());
+    assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Update, &key, &None).is_ok());
+    assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Grant, &key, &None).is_ok());
+    assert!(check_key_permission(0, &system_server_ctx, KeyPerm::UseDevId, &key, &None).is_ok());
+    assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &key, &None).is_ok());
+
+    assert!(check_key_permission(0, &shell_ctx, KeyPerm::Use, &key, &None).is_ok());
+    assert!(check_key_permission(0, &shell_ctx, KeyPerm::Delete, &key, &None).is_ok());
+    assert!(check_key_permission(0, &shell_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
+    assert!(check_key_permission(0, &shell_ctx, KeyPerm::Rebind, &key, &None).is_ok());
+    assert!(check_key_permission(0, &shell_ctx, KeyPerm::Update, &key, &None).is_ok());
+    assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ReqForcedOp, &key, &None));
+    assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None));
+    assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None));
+    assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &key, &None));
+
+    // Also make sure that the permission fails if the caller is not the owner.
+    assert_perm_failed!(check_key_permission(
+        1, // the owner is 0
+        &system_server_ctx,
+        KeyPerm::Use,
+        &key,
+        &None
+    ));
+    // Unless there was a grant.
+    assert!(check_key_permission(
+        1,
+        &system_server_ctx,
+        KeyPerm::Use,
+        &key,
+        &Some(key_perm_set![KeyPerm::Use])
+    )
+    .is_ok());
+    // But fail if the grant did not cover the requested permission.
+    assert_perm_failed!(check_key_permission(
+        1,
+        &system_server_ctx,
+        KeyPerm::Use,
+        &key,
+        &Some(key_perm_set![KeyPerm::GetInfo])
+    ));
+
+    Ok(())
+}
+
+#[test]
+fn check_key_permission_domain_selinux() -> Result<()> {
+    let (sctx, namespace, is_su) = check_context()?;
+    let key = KeyDescriptor {
+        domain: Domain::SELINUX,
+        nspace: namespace as i64,
+        alias: None,
+        blob: None,
+    };
+
+    assert!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None).is_ok());
+    assert!(check_key_permission(0, &sctx, KeyPerm::Delete, &key, &None).is_ok());
+    assert!(check_key_permission(0, &sctx, KeyPerm::GetInfo, &key, &None).is_ok());
+    assert!(check_key_permission(0, &sctx, KeyPerm::Rebind, &key, &None).is_ok());
+    assert!(check_key_permission(0, &sctx, KeyPerm::Update, &key, &None).is_ok());
+
+    if is_su {
+        assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok());
+        assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok());
+        assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok());
+        assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok());
+        assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok());
+    } else {
+        assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None));
+        assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None));
+        assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None));
+        assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None));
+        assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None));
+    }
+    Ok(())
+}
+
+#[test]
+fn check_key_permission_domain_blob() -> Result<()> {
+    let (sctx, namespace, is_su) = check_context()?;
+    let key =
+        KeyDescriptor { domain: Domain::BLOB, nspace: namespace as i64, alias: None, blob: None };
+
+    if is_su {
+        check_key_permission(0, &sctx, KeyPerm::Use, &key, &None)
+    } else {
+        assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None));
+        Ok(())
+    }
+}
+
+#[test]
+fn check_key_permission_domain_key_id() -> Result<()> {
+    let key = KeyDescriptor { domain: Domain::KEY_ID, nspace: 0, alias: None, blob: None };
+
+    assert_eq!(
+        Some(&KsError::sys()),
+        check_key_permission(
+            0,
+            &selinux::Context::new("ignored").unwrap(),
+            KeyPerm::Use,
+            &key,
+            &None
+        )
+        .err()
+        .unwrap()
+        .root_cause()
+        .downcast_ref::<KsError>()
+    );
+    Ok(())
+}
+
+#[test]
+fn key_perm_set_all_test() {
+    let v = key_perm_set![
+        KeyPerm::ManageBlob,
+        KeyPerm::Delete,
+        KeyPerm::UseDevId,
+        KeyPerm::ReqForcedOp,
+        KeyPerm::GenUniqueId,
+        KeyPerm::Grant,
+        KeyPerm::GetInfo,
+        KeyPerm::Rebind,
+        KeyPerm::Update,
+        KeyPerm::Use // Test if the macro accepts missing comma at the end of the list.
+    ];
+    let mut i = v.into_iter();
+    assert_eq!(i.next().unwrap().name(), "delete");
+    assert_eq!(i.next().unwrap().name(), "gen_unique_id");
+    assert_eq!(i.next().unwrap().name(), "get_info");
+    assert_eq!(i.next().unwrap().name(), "grant");
+    assert_eq!(i.next().unwrap().name(), "manage_blob");
+    assert_eq!(i.next().unwrap().name(), "rebind");
+    assert_eq!(i.next().unwrap().name(), "req_forced_op");
+    assert_eq!(i.next().unwrap().name(), "update");
+    assert_eq!(i.next().unwrap().name(), "use");
+    assert_eq!(i.next().unwrap().name(), "use_dev_id");
+    assert_eq!(None, i.next());
+}
+#[test]
+fn key_perm_set_sparse_test() {
+    let v = key_perm_set![
+        KeyPerm::ManageBlob,
+        KeyPerm::ReqForcedOp,
+        KeyPerm::GenUniqueId,
+        KeyPerm::Update,
+        KeyPerm::Use, // Test if macro accepts the comma at the end of the list.
+    ];
+    let mut i = v.into_iter();
+    assert_eq!(i.next().unwrap().name(), "gen_unique_id");
+    assert_eq!(i.next().unwrap().name(), "manage_blob");
+    assert_eq!(i.next().unwrap().name(), "req_forced_op");
+    assert_eq!(i.next().unwrap().name(), "update");
+    assert_eq!(i.next().unwrap().name(), "use");
+    assert_eq!(None, i.next());
+}
+#[test]
+fn key_perm_set_empty_test() {
+    let v = key_perm_set![];
+    let mut i = v.into_iter();
+    assert_eq!(None, i.next());
+}
+#[test]
+fn key_perm_set_include_subset_test() {
+    let v1 = key_perm_set![
+        KeyPerm::ManageBlob,
+        KeyPerm::Delete,
+        KeyPerm::UseDevId,
+        KeyPerm::ReqForcedOp,
+        KeyPerm::GenUniqueId,
+        KeyPerm::Grant,
+        KeyPerm::GetInfo,
+        KeyPerm::Rebind,
+        KeyPerm::Update,
+        KeyPerm::Use,
+    ];
+    let v2 = key_perm_set![
+        KeyPerm::ManageBlob,
+        KeyPerm::Delete,
+        KeyPerm::Rebind,
+        KeyPerm::Update,
+        KeyPerm::Use,
+    ];
+    assert!(v1.includes(v2));
+    assert!(!v2.includes(v1));
+}
+#[test]
+fn key_perm_set_include_equal_test() {
+    let v1 = key_perm_set![
+        KeyPerm::ManageBlob,
+        KeyPerm::Delete,
+        KeyPerm::Rebind,
+        KeyPerm::Update,
+        KeyPerm::Use,
+    ];
+    let v2 = key_perm_set![
+        KeyPerm::ManageBlob,
+        KeyPerm::Delete,
+        KeyPerm::Rebind,
+        KeyPerm::Update,
+        KeyPerm::Use,
+    ];
+    assert!(v1.includes(v2));
+    assert!(v2.includes(v1));
+}
+#[test]
+fn key_perm_set_include_overlap_test() {
+    let v1 = key_perm_set![
+        KeyPerm::ManageBlob,
+        KeyPerm::Delete,
+        KeyPerm::Grant, // only in v1
+        KeyPerm::Rebind,
+        KeyPerm::Update,
+        KeyPerm::Use,
+    ];
+    let v2 = key_perm_set![
+        KeyPerm::ManageBlob,
+        KeyPerm::Delete,
+        KeyPerm::ReqForcedOp, // only in v2
+        KeyPerm::Rebind,
+        KeyPerm::Update,
+        KeyPerm::Use,
+    ];
+    assert!(!v1.includes(v2));
+    assert!(!v2.includes(v1));
+}
+#[test]
+fn key_perm_set_include_no_overlap_test() {
+    let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,];
+    let v2 = key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,];
+    assert!(!v1.includes(v2));
+    assert!(!v2.includes(v1));
+}
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
index a8a88d2..bf1149c 100644
--- a/keystore2/src/raw_device.rs
+++ b/keystore2/src/raw_device.rs
@@ -212,8 +212,8 @@
                         |key_blob| {
                             map_km_error({
                                 let _wp = wd::watch(concat!(
-                                    "In KeyMintDevice::lookup_or_generate_key: ",
-                                    "calling getKeyCharacteristics."
+                                    "KeyMintDevice::lookup_or_generate_key: ",
+                                    "calling IKeyMintDevice::getKeyCharacteristics."
                                 ));
                                 self.km_dev.getKeyCharacteristics(key_blob, &[], &[])
                             })
@@ -305,7 +305,9 @@
         let (begin_result, _) = self
             .upgrade_keyblob_if_required_with(db, key_id_guard, key_blob, |blob| {
                 map_km_error({
-                    let _wp = wd::watch("In use_key_in_one_step: calling: begin");
+                    let _wp = wd::watch(
+                        "KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::begin",
+                    );
                     self.km_dev.begin(purpose, blob, operation_parameters, auth_token)
                 })
             })
@@ -313,7 +315,8 @@
         let operation: Strong<dyn IKeyMintOperation> =
             begin_result.operation.ok_or_else(Error::sys).context(ks_err!("Operation missing"))?;
         map_km_error({
-            let _wp = wd::watch("In use_key_in_one_step: calling: finish");
+            let _wp =
+                wd::watch("KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::finish");
             operation.finish(Some(input), None, None, None, None)
         })
         .context(ks_err!("Failed to finish operation."))
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index cda93b3..2bdafd4 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -20,9 +20,8 @@
 //! DB.
 
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-    Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate,
-    KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel,
-    Tag::Tag,
+    Algorithm::Algorithm, AttestationKey::AttestationKey, KeyParameter::KeyParameter,
+    KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
 };
 use android_security_rkp_aidl::aidl::android::security::rkp::RemotelyProvisionedKey::RemotelyProvisionedKey;
 use android_system_keystore2::aidl::android::system::keystore2::{
@@ -85,7 +84,7 @@
         key: &KeyDescriptor,
         caller_uid: u32,
         params: &[KeyParameter],
-    ) -> Result<Option<(AttestationKey, Certificate)>> {
+    ) -> Result<Option<(AttestationKey, Vec<u8>)>> {
         if !self.is_asymmetric_key(params) || key.domain != Domain::APP {
             Ok(None)
         } else {
@@ -106,13 +105,14 @@
                     AttestationKey {
                         keyBlob: rkpd_key.keyBlob,
                         attestKeyParams: vec![],
-                        // Batch certificate is at the beginning of the certificate chain.
+                        // Batch certificate is at the beginning of the concatenated certificate
+                        // chain, and the helper function only looks at the first cert.
                         issuerSubjectName: parse_subject_from_certificate(
                             &rkpd_key.encodedCertChain,
                         )
                         .context(ks_err!("Failed to parse subject."))?,
                     },
-                    Certificate { encodedCertificate: rkpd_key.encodedCertChain },
+                    rkpd_key.encodedCertChain,
                 ))),
             }
         }
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 8412397..233f2ae 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -49,7 +49,7 @@
 };
 use crate::{globals::get_keymint_device, id_rotation::IdRotationState};
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-    Algorithm::Algorithm, AttestationKey::AttestationKey,
+    Algorithm::Algorithm, AttestationKey::AttestationKey, Certificate::Certificate,
     HardwareAuthenticatorType::HardwareAuthenticatorType, IKeyMintDevice::IKeyMintDevice,
     KeyCreationResult::KeyCreationResult, KeyFormat::KeyFormat,
     KeyMintHardwareInfo::KeyMintHardwareInfo, KeyParameter::KeyParameter,
@@ -64,7 +64,9 @@
     KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode,
 };
 use anyhow::{anyhow, Context, Result};
+use postprocessor_client::process_certificate_chain;
 use rkpd_client::store_rkpd_attestation_key;
+use rustutils::system_properties::read_bool;
 use std::convert::TryInto;
 use std::time::SystemTime;
 
@@ -131,11 +133,21 @@
             certificateChain: mut certificate_chain,
         } = creation_result;
 
+        // Unify the possible contents of the certificate chain.  The first entry in the `Vec` is
+        // always the leaf certificate (if present), but the rest of the chain may be present as
+        // either:
+        //  - `certificate_chain[1..n]`: each entry holds a single certificate, as returned by
+        //    KeyMint, or
+        //  - `certificate[1`]: a single `Certificate` from RKP that actually (and confusingly)
+        //    holds the DER-encoded certs of the chain concatenated together.
         let mut cert_info: CertificateInfo = CertificateInfo::new(
+            // Leaf is always a single cert in the first entry, if present.
             match certificate_chain.len() {
                 0 => None,
                 _ => Some(certificate_chain.remove(0).encodedCertificate),
             },
+            // Remainder may be either `[1..n]` individual certs, or just `[1]` holding a
+            // concatenated chain. Convert the former to the latter.
             match certificate_chain.len() {
                 0 => None,
                 _ => Some(
@@ -329,8 +341,9 @@
                 operation_parameters,
                 |blob| loop {
                     match map_km_error({
-                        let _wp =
-                            self.watch("In KeystoreSecurityLevel::create_operation: calling begin");
+                        let _wp = self.watch(
+                            "KeystoreSecurityLevel::create_operation: calling IKeyMintDevice::begin",
+                        );
                         self.keymint.begin(
                             purpose,
                             blob,
@@ -444,7 +457,7 @@
         // If there is an attestation challenge we need to get an application id.
         if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) {
             let _wp =
-                self.watch("In KeystoreSecurityLevel::add_required_parameters calling: get_aaid");
+                self.watch(" KeystoreSecurityLevel::add_required_parameters: calling get_aaid");
             match keystore2_aaid::get_aaid(uid) {
                 Ok(aaid_ok) => {
                     result.push(KeyParameter {
@@ -581,8 +594,8 @@
                         map_km_error({
                             let _wp = self.watch_millis(
                                 concat!(
-                                    "In KeystoreSecurityLevel::generate_key (UserGenerated): ",
-                                    "calling generate_key."
+                                    "KeystoreSecurityLevel::generate_key (UserGenerated): ",
+                                    "calling IKeyMintDevice::generate_key"
                                 ),
                                 5000, // Generate can take a little longer.
                             );
@@ -601,8 +614,8 @@
                     map_km_error({
                         let _wp = self.watch_millis(
                             concat!(
-                                "In KeystoreSecurityLevel::generate_key (RkpdProvisioned): ",
-                                "calling generate_key.",
+                                "KeystoreSecurityLevel::generate_key (RkpdProvisioned): ",
+                                "calling IKeyMintDevice::generate_key",
                             ),
                             5000, // Generate can take a little longer.
                         );
@@ -621,15 +634,38 @@
                     log_security_safe_params(&params)
                 ))
                 .map(|(mut result, _)| {
-                    result.certificateChain.push(attestation_certs);
+                    if read_bool("remote_provisioning.use_cert_processor", false).unwrap_or(false) {
+                        let _wp = self.watch_millis(
+                            concat!(
+                                "KeystoreSecurityLevel::generate_key (RkpdProvisioned): ",
+                                "calling KeystorePostProcessor::process_certificate_chain",
+                            ),
+                            1000, // Post processing may take a little while due to network call.
+                        );
+                        // process_certificate_chain would either replace the certificate chain if
+                        // post-processing is successful or it would fallback to the original chain
+                        // on failure. In either case, we should get back the certificate chain
+                        // that is fit for storing with the newly generated key.
+                        result.certificateChain =
+                            process_certificate_chain(result.certificateChain, attestation_certs);
+                    } else {
+                        // The `certificateChain` in a `KeyCreationResult` should normally have one
+                        // `Certificate` for each certificate in the chain. To avoid having to
+                        // unnecessarily parse the RKP chain (which is concatenated DER-encoded
+                        // certs), stuff the whole concatenated chain into a single `Certificate`.
+                        // This is untangled by `store_new_key()`.
+                        result
+                            .certificateChain
+                            .push(Certificate { encodedCertificate: attestation_certs });
+                    }
                     result
                 })
             }
             None => map_km_error({
                 let _wp = self.watch_millis(
                     concat!(
-                        "In KeystoreSecurityLevel::generate_key (No attestation): ",
-                        "calling generate_key.",
+                        "KeystoreSecurityLevel::generate_key (No attestation key): ",
+                        "calling IKeyMintDevice::generate_key",
                     ),
                     5000, // Generate can take a little longer.
                 );
@@ -696,7 +732,8 @@
 
         let km_dev = &self.keymint;
         let creation_result = map_km_error({
-            let _wp = self.watch("In KeystoreSecurityLevel::import_key: calling importKey.");
+            let _wp =
+                self.watch("KeystoreSecurityLevel::import_key: calling IKeyMintDevice::importKey.");
             km_dev.importKey(&params, format, key_data, None /* attestKey */)
         })
         .context(ks_err!("Trying to call importKey"))?;
@@ -810,7 +847,7 @@
                 &[],
                 |wrapping_blob| {
                     let _wp = self.watch(
-                        "In KeystoreSecurityLevel::import_wrapped_key: calling importWrappedKey.",
+                        "KeystoreSecurityLevel::import_wrapped_key: calling IKeyMintDevice::importWrappedKey.",
                     );
                     let creation_result = map_km_error(self.keymint.importWrappedKey(
                         wrapped_data,
@@ -951,8 +988,8 @@
         let km_dev = &self.keymint;
         let res = {
             let _wp = self.watch(concat!(
-                "In IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ",
-                "calling convertStorageKeyToEphemeral (1)"
+                "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ",
+                "calling IKeyMintDevice::convertStorageKeyToEphemeral (1)"
             ));
             map_km_error(km_dev.convertStorageKeyToEphemeral(key_blob))
         };
@@ -962,19 +999,18 @@
             }
             Err(error::Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
                 let upgraded_blob = {
-                    let _wp = self.watch("In convert_storage_key_to_ephemeral: calling upgradeKey");
+                    let _wp = self.watch("IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: calling IKeyMintDevice::upgradeKey");
                     map_km_error(km_dev.upgradeKey(key_blob, &[]))
                 }
                 .context(ks_err!("Failed to upgrade key blob."))?;
                 let ephemeral_key = {
-                    let _wp = self.watch(
-                        "In convert_storage_key_to_ephemeral: calling convertStorageKeyToEphemeral (2)",
-                    );
+                    let _wp = self.watch(concat!(
+                        "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ",
+                        "calling IKeyMintDevice::convertStorageKeyToEphemeral (2)"
+                    ));
                     map_km_error(km_dev.convertStorageKeyToEphemeral(&upgraded_blob))
                 }
-                    .context(ks_err!(
-                        "Failed to retrieve ephemeral key (after upgrade)."
-                    ))?;
+                .context(ks_err!("Failed to retrieve ephemeral key (after upgrade)."))?;
                 Ok(EphemeralStorageKeyResponse {
                     ephemeralKey: ephemeral_key,
                     upgradedBlob: Some(upgraded_blob),
@@ -1001,7 +1037,8 @@
 
         let km_dev = &self.keymint;
         {
-            let _wp = self.watch("In KeystoreSecuritylevel::delete_key: calling deleteKey");
+            let _wp =
+                self.watch("KeystoreSecuritylevel::delete_key: calling IKeyMintDevice::deleteKey");
             map_km_error(km_dev.deleteKey(key_blob)).context(ks_err!("keymint device deleteKey"))
         }
     }
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index b760a56..85ac7bc 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -27,7 +27,10 @@
 };
 use crate::{
     database::Uuid,
-    globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_IMPORTER, SUPER_KEY},
+    globals::{
+        create_thread_local_db, DB, ENCODED_MODULE_INFO, LEGACY_BLOB_LOADER, LEGACY_IMPORTER,
+        SUPER_KEY,
+    },
 };
 use crate::{database::KEYSTORE_UUID, permission};
 use crate::{
@@ -39,6 +42,7 @@
     id_rotation::IdRotationState,
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::Tag::Tag;
 use android_hardware_security_keymint::binder::{BinderFeatures, Strong, ThreadState};
 use android_system_keystore2::aidl::android::system::keystore2::{
     Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
@@ -62,11 +66,18 @@
         id_rotation_state: IdRotationState,
     ) -> Result<Strong<dyn IKeystoreService>> {
         let mut result: Self = Default::default();
-        let (dev, uuid) = KeystoreSecurityLevel::new_native_binder(
+        let (dev, uuid) = match KeystoreSecurityLevel::new_native_binder(
             SecurityLevel::TRUSTED_ENVIRONMENT,
             id_rotation_state.clone(),
-        )
-        .context(ks_err!("Trying to construct mandatory security level TEE."))?;
+        ) {
+            Ok(v) => v,
+            Err(e) => {
+                log::error!("Failed to construct mandatory security level TEE: {e:?}");
+                log::error!("Does the device have a /default Keymaster or KeyMint instance?");
+                return Err(e.context(ks_err!("Trying to construct mandatory security level TEE")));
+            }
+        };
+
         result.i_sec_level_by_uuid.insert(uuid, dev);
         result.uuid_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid);
 
@@ -307,6 +318,20 @@
         DB.with(|db| count_key_entries(&mut db.borrow_mut(), k.domain, k.nspace))
     }
 
+    fn get_supplementary_attestation_info(&self, tag: Tag) -> Result<Vec<u8>> {
+        match tag {
+            Tag::MODULE_HASH => {
+                let info = ENCODED_MODULE_INFO.read().unwrap();
+                (*info)
+                    .clone()
+                    .ok_or(Error::Rc(ResponseCode::INFO_NOT_AVAILABLE))
+                    .context(ks_err!("Module info not received."))
+            }
+            _ => Err(Error::Rc(ResponseCode::INVALID_ARGUMENT))
+                .context(ks_err!("Tag {tag:?} not supported for getSupplementaryAttestationInfo.")),
+        }
+    }
+
     fn list_entries_batched(
         &self,
         domain: Domain,
@@ -434,4 +459,14 @@
         let _wp = wd::watch("IKeystoreService::getNumberOfEntries");
         self.count_num_entries(domain, namespace).map_err(into_logged_binder)
     }
+
+    fn getSupplementaryAttestationInfo(&self, tag: Tag) -> binder::Result<Vec<u8>> {
+        if keystore2_flags::attest_modules() {
+            let _wp = wd::watch("IKeystoreService::getSupplementaryAttestationInfo");
+            self.get_supplementary_attestation_info(tag).map_err(into_logged_binder)
+        } else {
+            log::error!("attest_modules flag is not toggled");
+            Err(binder::StatusCode::UNKNOWN_TRANSACTION.into())
+        }
+    }
 }
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 706a255..3e65753 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -52,6 +52,9 @@
 };
 use std::{convert::TryFrom, ops::Deref};
 
+#[cfg(test)]
+mod tests;
+
 const MAX_MAX_BOOT_LEVEL: usize = 1_000_000_000;
 /// Allow up to 15 seconds between the user unlocking using a biometric, and the auth
 /// token being used to unlock in [`SuperKeyManager::try_unlock_user_with_biometric`].
@@ -914,7 +917,7 @@
                     KeyType::Client, /* TODO Should be Super b/189470584 */
                     |dev| {
                         let _wp =
-                            wd::watch("In lock_unlocked_device_required_keys: calling importKey.");
+                            wd::watch("SKM::lock_unlocked_device_required_keys: calling IKeyMintDevice::importKey.");
                         dev.importKey(key_params.as_slice(), KeyFormat::RAW, &encrypting_key, None)
                     },
                 )?;
@@ -1215,7 +1218,7 @@
     Ref(&'a [u8]),
 }
 
-impl<'a> KeyBlob<'a> {
+impl KeyBlob<'_> {
     pub fn force_reencrypt(&self) -> bool {
         if let KeyBlob::Sensitive { force_reencrypt, .. } = self {
             *force_reencrypt
@@ -1226,7 +1229,7 @@
 }
 
 /// Deref returns a reference to the key material in any variant.
-impl<'a> Deref for KeyBlob<'a> {
+impl Deref for KeyBlob<'_> {
     type Target = [u8];
 
     fn deref(&self) -> &Self::Target {
@@ -1237,288 +1240,3 @@
         }
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::database::tests::make_bootlevel_key_entry;
-    use crate::database::tests::make_test_key_entry;
-    use crate::database::tests::new_test_db;
-    use rand::prelude::*;
-    const USER_ID: u32 = 0;
-    const TEST_KEY_ALIAS: &str = "TEST_KEY";
-    const TEST_BOOT_KEY_ALIAS: &str = "TEST_BOOT_KEY";
-
-    pub fn generate_password_blob() -> Password<'static> {
-        let mut rng = rand::thread_rng();
-        let mut password = vec![0u8; 64];
-        rng.fill_bytes(&mut password);
-
-        let mut zvec = ZVec::new(64).expect("Failed to create ZVec");
-        zvec[..].copy_from_slice(&password[..]);
-
-        Password::Owned(zvec)
-    }
-
-    fn setup_test(pw: &Password) -> (Arc<RwLock<SuperKeyManager>>, KeystoreDB, LegacyImporter) {
-        let mut keystore_db = new_test_db().unwrap();
-        let mut legacy_importer = LegacyImporter::new(Arc::new(Default::default()));
-        legacy_importer.set_empty();
-        let skm: Arc<RwLock<SuperKeyManager>> = Default::default();
-        assert!(skm
-            .write()
-            .unwrap()
-            .initialize_user(&mut keystore_db, &legacy_importer, USER_ID, pw, false)
-            .is_ok());
-        (skm, keystore_db, legacy_importer)
-    }
-
-    fn assert_unlocked(
-        skm: &Arc<RwLock<SuperKeyManager>>,
-        keystore_db: &mut KeystoreDB,
-        legacy_importer: &LegacyImporter,
-        user_id: u32,
-        err_msg: &str,
-    ) {
-        let user_state =
-            skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
-        match user_state {
-            UserState::AfterFirstUnlock(_) => {}
-            _ => panic!("{}", err_msg),
-        }
-    }
-
-    fn assert_locked(
-        skm: &Arc<RwLock<SuperKeyManager>>,
-        keystore_db: &mut KeystoreDB,
-        legacy_importer: &LegacyImporter,
-        user_id: u32,
-        err_msg: &str,
-    ) {
-        let user_state =
-            skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
-        match user_state {
-            UserState::BeforeFirstUnlock => {}
-            _ => panic!("{}", err_msg),
-        }
-    }
-
-    fn assert_uninitialized(
-        skm: &Arc<RwLock<SuperKeyManager>>,
-        keystore_db: &mut KeystoreDB,
-        legacy_importer: &LegacyImporter,
-        user_id: u32,
-        err_msg: &str,
-    ) {
-        let user_state =
-            skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
-        match user_state {
-            UserState::Uninitialized => {}
-            _ => panic!("{}", err_msg),
-        }
-    }
-
-    #[test]
-    fn test_initialize_user() {
-        let pw: Password = generate_password_blob();
-        let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
-        assert_unlocked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "The user was not unlocked after initialization!",
-        );
-    }
-
-    #[test]
-    fn test_unlock_user() {
-        let pw: Password = generate_password_blob();
-        let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
-        assert_unlocked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "The user was not unlocked after initialization!",
-        );
-
-        skm.write().unwrap().data.user_keys.clear();
-        assert_locked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "Clearing the cache did not lock the user!",
-        );
-
-        assert!(skm
-            .write()
-            .unwrap()
-            .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw)
-            .is_ok());
-        assert_unlocked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "The user did not unlock!",
-        );
-    }
-
-    #[test]
-    fn test_unlock_wrong_password() {
-        let pw: Password = generate_password_blob();
-        let wrong_pw: Password = generate_password_blob();
-        let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
-        assert_unlocked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "The user was not unlocked after initialization!",
-        );
-
-        skm.write().unwrap().data.user_keys.clear();
-        assert_locked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "Clearing the cache did not lock the user!",
-        );
-
-        assert!(skm
-            .write()
-            .unwrap()
-            .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &wrong_pw)
-            .is_err());
-        assert_locked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "The user was unlocked with an incorrect password!",
-        );
-    }
-
-    #[test]
-    fn test_unlock_user_idempotent() {
-        let pw: Password = generate_password_blob();
-        let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
-        assert_unlocked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "The user was not unlocked after initialization!",
-        );
-
-        skm.write().unwrap().data.user_keys.clear();
-        assert_locked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "Clearing the cache did not lock the user!",
-        );
-
-        for _ in 0..5 {
-            assert!(skm
-                .write()
-                .unwrap()
-                .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw)
-                .is_ok());
-            assert_unlocked(
-                &skm,
-                &mut keystore_db,
-                &legacy_importer,
-                USER_ID,
-                "The user did not unlock!",
-            );
-        }
-    }
-
-    fn test_user_removal(locked: bool) {
-        let pw: Password = generate_password_blob();
-        let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
-        assert_unlocked(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "The user was not unlocked after initialization!",
-        );
-
-        assert!(make_test_key_entry(
-            &mut keystore_db,
-            Domain::APP,
-            USER_ID.into(),
-            TEST_KEY_ALIAS,
-            None
-        )
-        .is_ok());
-        assert!(make_bootlevel_key_entry(
-            &mut keystore_db,
-            Domain::APP,
-            USER_ID.into(),
-            TEST_BOOT_KEY_ALIAS,
-            false
-        )
-        .is_ok());
-
-        assert!(keystore_db
-            .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client)
-            .unwrap());
-        assert!(keystore_db
-            .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client)
-            .unwrap());
-
-        if locked {
-            skm.write().unwrap().data.user_keys.clear();
-            assert_locked(
-                &skm,
-                &mut keystore_db,
-                &legacy_importer,
-                USER_ID,
-                "Clearing the cache did not lock the user!",
-            );
-        }
-
-        assert!(skm
-            .write()
-            .unwrap()
-            .remove_user(&mut keystore_db, &legacy_importer, USER_ID)
-            .is_ok());
-        assert_uninitialized(
-            &skm,
-            &mut keystore_db,
-            &legacy_importer,
-            USER_ID,
-            "The user was not removed!",
-        );
-
-        assert!(!skm
-            .write()
-            .unwrap()
-            .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID)
-            .unwrap());
-
-        assert!(!keystore_db
-            .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client)
-            .unwrap());
-        assert!(!keystore_db
-            .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client)
-            .unwrap());
-    }
-
-    #[test]
-    fn test_remove_unlocked_user() {
-        test_user_removal(false);
-    }
-
-    #[test]
-    fn test_remove_locked_user() {
-        test_user_removal(true);
-    }
-}
diff --git a/keystore2/src/super_key/tests.rs b/keystore2/src/super_key/tests.rs
new file mode 100644
index 0000000..76a96a7
--- /dev/null
+++ b/keystore2/src/super_key/tests.rs
@@ -0,0 +1,287 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Super-key tests.
+
+use super::*;
+use crate::database::tests::make_bootlevel_key_entry;
+use crate::database::tests::make_test_key_entry;
+use crate::database::tests::new_test_db;
+use rand::prelude::*;
+const USER_ID: u32 = 0;
+const TEST_KEY_ALIAS: &str = "TEST_KEY";
+const TEST_BOOT_KEY_ALIAS: &str = "TEST_BOOT_KEY";
+
+pub fn generate_password_blob() -> Password<'static> {
+    let mut rng = rand::thread_rng();
+    let mut password = vec![0u8; 64];
+    rng.fill_bytes(&mut password);
+
+    let mut zvec = ZVec::new(64).expect("Failed to create ZVec");
+    zvec[..].copy_from_slice(&password[..]);
+
+    Password::Owned(zvec)
+}
+
+fn setup_test(pw: &Password) -> (Arc<RwLock<SuperKeyManager>>, KeystoreDB, LegacyImporter) {
+    let mut keystore_db = new_test_db().unwrap();
+    let mut legacy_importer = LegacyImporter::new(Arc::new(Default::default()));
+    legacy_importer.set_empty();
+    let skm: Arc<RwLock<SuperKeyManager>> = Default::default();
+    assert!(skm
+        .write()
+        .unwrap()
+        .initialize_user(&mut keystore_db, &legacy_importer, USER_ID, pw, false)
+        .is_ok());
+    (skm, keystore_db, legacy_importer)
+}
+
+fn assert_unlocked(
+    skm: &Arc<RwLock<SuperKeyManager>>,
+    keystore_db: &mut KeystoreDB,
+    legacy_importer: &LegacyImporter,
+    user_id: u32,
+    err_msg: &str,
+) {
+    let user_state =
+        skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
+    match user_state {
+        UserState::AfterFirstUnlock(_) => {}
+        _ => panic!("{}", err_msg),
+    }
+}
+
+fn assert_locked(
+    skm: &Arc<RwLock<SuperKeyManager>>,
+    keystore_db: &mut KeystoreDB,
+    legacy_importer: &LegacyImporter,
+    user_id: u32,
+    err_msg: &str,
+) {
+    let user_state =
+        skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
+    match user_state {
+        UserState::BeforeFirstUnlock => {}
+        _ => panic!("{}", err_msg),
+    }
+}
+
+fn assert_uninitialized(
+    skm: &Arc<RwLock<SuperKeyManager>>,
+    keystore_db: &mut KeystoreDB,
+    legacy_importer: &LegacyImporter,
+    user_id: u32,
+    err_msg: &str,
+) {
+    let user_state =
+        skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap();
+    match user_state {
+        UserState::Uninitialized => {}
+        _ => panic!("{}", err_msg),
+    }
+}
+
+#[test]
+fn test_initialize_user() {
+    let pw: Password = generate_password_blob();
+    let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+    assert_unlocked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "The user was not unlocked after initialization!",
+    );
+}
+
+#[test]
+fn test_unlock_user() {
+    let pw: Password = generate_password_blob();
+    let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+    assert_unlocked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "The user was not unlocked after initialization!",
+    );
+
+    skm.write().unwrap().data.user_keys.clear();
+    assert_locked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "Clearing the cache did not lock the user!",
+    );
+
+    assert!(skm
+        .write()
+        .unwrap()
+        .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw)
+        .is_ok());
+    assert_unlocked(&skm, &mut keystore_db, &legacy_importer, USER_ID, "The user did not unlock!");
+}
+
+#[test]
+fn test_unlock_wrong_password() {
+    let pw: Password = generate_password_blob();
+    let wrong_pw: Password = generate_password_blob();
+    let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+    assert_unlocked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "The user was not unlocked after initialization!",
+    );
+
+    skm.write().unwrap().data.user_keys.clear();
+    assert_locked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "Clearing the cache did not lock the user!",
+    );
+
+    assert!(skm
+        .write()
+        .unwrap()
+        .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &wrong_pw)
+        .is_err());
+    assert_locked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "The user was unlocked with an incorrect password!",
+    );
+}
+
+#[test]
+fn test_unlock_user_idempotent() {
+    let pw: Password = generate_password_blob();
+    let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+    assert_unlocked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "The user was not unlocked after initialization!",
+    );
+
+    skm.write().unwrap().data.user_keys.clear();
+    assert_locked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "Clearing the cache did not lock the user!",
+    );
+
+    for _ in 0..5 {
+        assert!(skm
+            .write()
+            .unwrap()
+            .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw)
+            .is_ok());
+        assert_unlocked(
+            &skm,
+            &mut keystore_db,
+            &legacy_importer,
+            USER_ID,
+            "The user did not unlock!",
+        );
+    }
+}
+
+fn test_user_removal(locked: bool) {
+    let pw: Password = generate_password_blob();
+    let (skm, mut keystore_db, legacy_importer) = setup_test(&pw);
+    assert_unlocked(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "The user was not unlocked after initialization!",
+    );
+
+    assert!(make_test_key_entry(
+        &mut keystore_db,
+        Domain::APP,
+        USER_ID.into(),
+        TEST_KEY_ALIAS,
+        None
+    )
+    .is_ok());
+    assert!(make_bootlevel_key_entry(
+        &mut keystore_db,
+        Domain::APP,
+        USER_ID.into(),
+        TEST_BOOT_KEY_ALIAS,
+        false
+    )
+    .is_ok());
+
+    assert!(keystore_db
+        .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client)
+        .unwrap());
+    assert!(keystore_db
+        .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client)
+        .unwrap());
+
+    if locked {
+        skm.write().unwrap().data.user_keys.clear();
+        assert_locked(
+            &skm,
+            &mut keystore_db,
+            &legacy_importer,
+            USER_ID,
+            "Clearing the cache did not lock the user!",
+        );
+    }
+
+    assert!(skm.write().unwrap().remove_user(&mut keystore_db, &legacy_importer, USER_ID).is_ok());
+    assert_uninitialized(
+        &skm,
+        &mut keystore_db,
+        &legacy_importer,
+        USER_ID,
+        "The user was not removed!",
+    );
+
+    assert!(!skm
+        .write()
+        .unwrap()
+        .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID)
+        .unwrap());
+
+    assert!(!keystore_db
+        .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client)
+        .unwrap());
+    assert!(!keystore_db
+        .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client)
+        .unwrap());
+}
+
+#[test]
+fn test_remove_unlocked_user() {
+    test_user_removal(false);
+}
+
+#[test]
+fn test_remove_locked_user() {
+    test_user_removal(true);
+}
diff --git a/keystore2/src/sw_keyblob.rs b/keystore2/src/sw_keyblob.rs
index 47ab49f..c0173b5 100644
--- a/keystore2/src/sw_keyblob.rs
+++ b/keystore2/src/sw_keyblob.rs
@@ -28,6 +28,9 @@
 use keystore2_crypto::hmac_sha256;
 use std::mem::size_of;
 
+#[cfg(test)]
+mod tests;
+
 /// Root of trust value.
 const SOFTWARE_ROOT_OF_TRUST: &[u8] = b"SW";
 
@@ -556,481 +559,3 @@
         .clone_from_slice(&serialized_size.to_ne_bytes());
     Ok(result)
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-        Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
-        KeyOrigin::KeyOrigin, KeyParameter::KeyParameter,
-        KeyParameterValue::KeyParameterValue as KPV, KeyPurpose::KeyPurpose,
-        PaddingMode::PaddingMode, Tag::Tag,
-    };
-
-    macro_rules! expect_err {
-        ($result:expr, $err_msg:expr) => {
-            assert!(
-                $result.is_err(),
-                "Expected error containing '{}', got success {:?}",
-                $err_msg,
-                $result
-            );
-            let err = $result.err();
-            assert!(
-                format!("{:?}", err).contains($err_msg),
-                "Unexpected error {:?}, doesn't contain '{}'",
-                err,
-                $err_msg
-            );
-        };
-    }
-
-    #[test]
-    fn test_consume_u8() {
-        let buffer = [1, 2];
-        let mut data = &buffer[..];
-        assert_eq!(1u8, consume_u8(&mut data).unwrap());
-        assert_eq!(2u8, consume_u8(&mut data).unwrap());
-        let result = consume_u8(&mut data);
-        expect_err!(result, "failed to find 1 byte");
-    }
-
-    #[test]
-    fn test_consume_u32() {
-        // All supported platforms are little-endian.
-        let buffer = [
-            0x01, 0x02, 0x03, 0x04, // little-endian u32
-            0x04, 0x03, 0x02, 0x01, // little-endian u32
-            0x11, 0x12, 0x13,
-        ];
-        let mut data = &buffer[..];
-        assert_eq!(0x04030201u32, consume_u32(&mut data).unwrap());
-        assert_eq!(0x01020304u32, consume_u32(&mut data).unwrap());
-        let result = consume_u32(&mut data);
-        expect_err!(result, "failed to find 4 bytes");
-    }
-
-    #[test]
-    fn test_consume_i64() {
-        // All supported platforms are little-endian.
-        let buffer = [
-            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // little-endian i64
-            0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // little-endian i64
-            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
-        ];
-        let mut data = &buffer[..];
-        assert_eq!(0x0807060504030201i64, consume_i64(&mut data).unwrap());
-        assert_eq!(0x0102030405060708i64, consume_i64(&mut data).unwrap());
-        let result = consume_i64(&mut data);
-        expect_err!(result, "failed to find 8 bytes");
-    }
-
-    #[test]
-    fn test_consume_vec() {
-        let buffer = [
-            0x01, 0x00, 0x00, 0x00, 0xaa, //
-            0x00, 0x00, 0x00, 0x00, //
-            0x01, 0x00, 0x00, 0x00, 0xbb, //
-            0x07, 0x00, 0x00, 0x00, 0xbb, // not enough data
-        ];
-        let mut data = &buffer[..];
-        assert_eq!(vec![0xaa], consume_vec(&mut data).unwrap());
-        assert_eq!(Vec::<u8>::new(), consume_vec(&mut data).unwrap());
-        assert_eq!(vec![0xbb], consume_vec(&mut data).unwrap());
-        let result = consume_vec(&mut data);
-        expect_err!(result, "failed to find 7 bytes");
-
-        let buffer = [
-            0x01, 0x00, 0x00, //
-        ];
-        let mut data = &buffer[..];
-        let result = consume_vec(&mut data);
-        expect_err!(result, "failed to find 4 bytes");
-    }
-
-    #[test]
-    fn test_key_new_from_serialized() {
-        let hidden = hidden_params(&[], &[SOFTWARE_ROOT_OF_TRUST]);
-        // Test data originally generated by instrumenting Cuttlefish C++ KeyMint while running VTS
-        // tests.
-        let tests = [
-            (
-                concat!(
-                    "0010000000d43c2f04f948521b81bdbf001310f5920000000000000000000000",
-                    "00000000000c0000006400000002000010200000000300003080000000010000",
-                    "2000000000010000200100000004000020020000000600002001000000be0200",
-                    "1000000000c1020030b0ad0100c20200307b150300bd020060a8bb52407b0100",
-                    "00ce02003011643401cf020030000000003b06b13ae6ae6671",
-                ),
-                KeyBlob {
-                    key_material: hex::decode("d43c2f04f948521b81bdbf001310f592").unwrap(),
-                    hw_enforced: vec![],
-                    sw_enforced: vec![
-                        KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::AES) },
-                        KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(128) },
-                        KeyParameter {
-                            tag: Tag::PURPOSE,
-                            value: KPV::KeyPurpose(KeyPurpose::ENCRYPT),
-                        },
-                        KeyParameter {
-                            tag: Tag::PURPOSE,
-                            value: KPV::KeyPurpose(KeyPurpose::DECRYPT),
-                        },
-                        KeyParameter {
-                            tag: Tag::BLOCK_MODE,
-                            value: KPV::BlockMode(BlockMode::CBC),
-                        },
-                        KeyParameter {
-                            tag: Tag::PADDING,
-                            value: KPV::PaddingMode(PaddingMode::NONE),
-                        },
-                        KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
-                        KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
-                        KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
-                        KeyParameter {
-                            tag: Tag::CREATION_DATETIME,
-                            value: KPV::DateTime(1628871769000),
-                        },
-                        KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
-                        KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
-                    ],
-                },
-                Some(KeyFormat::RAW),
-            ),
-            (
-                concat!(
-                    "00df0000003081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6b",
-                    "a14e46ab7ca532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654",
-                    "e9e79413cd503eae3d9cf68ed24f47a00706052b81040023a181890381860004",
-                    "006b840f0db0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d14",
-                    "0743bdd028db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b7",
-                    "45e30142e90685646661550344113aaf28bdee6cb02d19df1faab4398556a909",
-                    "7d6f64b95209601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3",
-                    "b877984d000000000000000000000000000000000c0000006400000002000010",
-                    "030000000a000010030000000100002002000000010000200300000005000020",
-                    "000000000300003009020000be02001000000000c1020030b0ad0100c2020030",
-                    "7b150300bd02006018d352407b010000ce02003011643401cf02003000000000",
-                    "2f69002e55e9b0a3"
-                ),
-                KeyBlob {
-                    key_material: hex::decode(concat!(
-                        "3081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6ba14e46ab7c",
-                        "a532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654e9e79413cd",
-                        "503eae3d9cf68ed24f47a00706052b81040023a181890381860004006b840f0d",
-                        "b0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d140743bdd028",
-                        "db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b745e30142e9",
-                        "0685646661550344113aaf28bdee6cb02d19df1faab4398556a9097d6f64b952",
-                        "09601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3b877984d",
-                    ))
-                    .unwrap(),
-                    hw_enforced: vec![],
-                    sw_enforced: vec![
-                        KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) },
-                        KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::P_521) },
-                        KeyParameter {
-                            tag: Tag::PURPOSE,
-                            value: KPV::KeyPurpose(KeyPurpose::SIGN),
-                        },
-                        KeyParameter {
-                            tag: Tag::PURPOSE,
-                            value: KPV::KeyPurpose(KeyPurpose::VERIFY),
-                        },
-                        KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
-                        KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(521) },
-                        KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
-                        KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
-                        KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
-                        KeyParameter {
-                            tag: Tag::CREATION_DATETIME,
-                            value: KPV::DateTime(1628871775000),
-                        },
-                        KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
-                        KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
-                    ],
-                },
-                Some(KeyFormat::PKCS8),
-            ),
-            (
-                concat!(
-                    "0037000000541d4c440223650d5f51753c1abd80c725034485551e874d62327c",
-                    "65f6247a057f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc00000000",
-                    "0000000000000000000000000c00000064000000020000108000000003000030",
-                    "b801000001000020020000000100002003000000050000200400000008000030",
-                    "00010000be02001000000000c1020030b0ad0100c20200307b150300bd020060",
-                    "00d752407b010000ce02003011643401cf0200300000000036e6986ffc45fbb0",
-                ),
-                KeyBlob {
-                    key_material: hex::decode(concat!(
-                        "541d4c440223650d5f51753c1abd80c725034485551e874d62327c65f6247a05",
-                        "7f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc"
-                    ))
-                    .unwrap(),
-                    hw_enforced: vec![],
-                    sw_enforced: vec![
-                        KeyParameter {
-                            tag: Tag::ALGORITHM,
-                            value: KPV::Algorithm(Algorithm::HMAC),
-                        },
-                        KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(440) },
-                        KeyParameter {
-                            tag: Tag::PURPOSE,
-                            value: KPV::KeyPurpose(KeyPurpose::SIGN),
-                        },
-                        KeyParameter {
-                            tag: Tag::PURPOSE,
-                            value: KPV::KeyPurpose(KeyPurpose::VERIFY),
-                        },
-                        KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::SHA_2_256) },
-                        KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KPV::Integer(256) },
-                        KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
-                        KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
-                        KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
-                        KeyParameter {
-                            tag: Tag::CREATION_DATETIME,
-                            value: KPV::DateTime(1628871776000),
-                        },
-                        KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
-                        KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
-                    ],
-                },
-                Some(KeyFormat::RAW),
-            ),
-            (
-                concat!(
-                    "00a8040000308204a40201000282010100bc47b5c71116766669b91fa747df87",
-                    "a1963df83956569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082",
-                    "825c7c6e482a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31",
-                    "c1ced84878edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c3973",
-                    "6838151642eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d",
-                    "5337c8bf9245d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0de",
-                    "b0957d61dbba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2",
-                    "540c84e45c4a99fb338b76bba7722856b5113341c349708937228f167d238ed8",
-                    "efb9cc19547dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af",
-                    "59fe87421af9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d3703",
-                    "8bf9f588ae20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178",
-                    "a848e06d558c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88e",
-                    "e610260e406c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30a",
-                    "e7a31f8262da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73a",
-                    "e6f8b29a9144eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b",
-                    "4217c8db50db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817",
-                    "d9380b190bd382aaffa37785759f285194c11a188bccde0e2e2902818100fb23",
-                    "3335770c9f3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a63",
-                    "3f7790d1011bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f2",
-                    "7a74852d6c7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd78009490",
-                    "4c2856d2b944fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f45990281",
-                    "8100bfecf2bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a7158458",
-                    "18e01181ff06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb",
-                    "0078b77fb5b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc84",
-                    "20fcbf48d1eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b57",
-                    "298902818100ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd",
-                    "3765559994a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55",
-                    "dad696d3821def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2",
-                    "d8480ed07a7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f6332",
-                    "38f521ba764902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6",
-                    "603c6265f70018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d46",
-                    "93fa48e3a9abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b",
-                    "2a57e18a2e131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc",
-                    "2af4ed75760858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1",
-                    "c0a258d882984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4",
-                    "340a9fae64a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c5526",
-                    "1941b3654533b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a2",
-                    "47f56a337e3b9845b4f2b61356000000000000000000000000000000000d0000",
-                    "007000000002000010010000000300003000080000c800005001000100000000",
-                    "0001000020020000000100002003000000050000200000000006000020010000",
-                    "00be02001000000000c1020030b0ad0100c20200307b150300bd020060a8bb52",
-                    "407b010000ce02003011643401cf02003000000000544862e9c961e857",
-                ),
-                KeyBlob {
-                    key_material: hex::decode(concat!(
-                        "308204a40201000282010100bc47b5c71116766669b91fa747df87a1963df839",
-                        "56569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082825c7c6e48",
-                        "2a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31c1ced84878",
-                        "edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c39736838151642",
-                        "eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d5337c8bf92",
-                        "45d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0deb0957d61db",
-                        "ba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2540c84e45c",
-                        "4a99fb338b76bba7722856b5113341c349708937228f167d238ed8efb9cc1954",
-                        "7dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af59fe87421a",
-                        "f9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d37038bf9f588ae",
-                        "20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178a848e06d55",
-                        "8c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88ee610260e40",
-                        "6c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30ae7a31f8262",
-                        "da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73ae6f8b29a91",
-                        "44eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b4217c8db50",
-                        "db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817d9380b190b",
-                        "d382aaffa37785759f285194c11a188bccde0e2e2902818100fb233335770c9f",
-                        "3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a633f7790d101",
-                        "1bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f27a74852d6c",
-                        "7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd780094904c2856d2b9",
-                        "44fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f459902818100bfecf2",
-                        "bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a715845818e01181ff",
-                        "06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb0078b77fb5",
-                        "b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc8420fcbf48d1",
-                        "eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b572989028181",
-                        "00ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd3765559994",
-                        "a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55dad696d382",
-                        "1def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2d8480ed07a",
-                        "7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f633238f521ba76",
-                        "4902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6603c6265f7",
-                        "0018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d4693fa48e3a9",
-                        "abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b2a57e18a2e",
-                        "131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc2af4ed7576",
-                        "0858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1c0a258d882",
-                        "984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4340a9fae64",
-                        "a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c55261941b36545",
-                        "33b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a247f56a337e",
-                        "3b9845b4f2b61356",
-                    ))
-                    .unwrap(),
-                    hw_enforced: vec![],
-                    sw_enforced: vec![
-                        KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::RSA) },
-                        KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(2048) },
-                        KeyParameter {
-                            tag: Tag::RSA_PUBLIC_EXPONENT,
-                            value: KPV::LongInteger(65537),
-                        },
-                        KeyParameter {
-                            tag: Tag::PURPOSE,
-                            value: KPV::KeyPurpose(KeyPurpose::SIGN),
-                        },
-                        KeyParameter {
-                            tag: Tag::PURPOSE,
-                            value: KPV::KeyPurpose(KeyPurpose::VERIFY),
-                        },
-                        KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
-                        KeyParameter {
-                            tag: Tag::PADDING,
-                            value: KPV::PaddingMode(PaddingMode::NONE),
-                        },
-                        KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
-                        KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
-                        KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
-                        KeyParameter {
-                            tag: Tag::CREATION_DATETIME,
-                            value: KPV::DateTime(1628871769000),
-                        },
-                        KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
-                        KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
-                    ],
-                },
-                // No support for RSA keys in export_key().
-                None,
-            ),
-        ];
-
-        for (input, want, want_format) in tests {
-            let input = hex::decode(input).unwrap();
-            let got = KeyBlob::new_from_serialized(&input, &hidden).expect("invalid keyblob!");
-            assert!(got == want);
-
-            if let Some(want_format) = want_format {
-                let (got_format, _key_material, params) =
-                    export_key(&input, &[]).expect("invalid keyblob!");
-                assert_eq!(got_format, want_format);
-                // All the test cases are software-only keys.
-                assert_eq!(params, got.sw_enforced);
-            }
-        }
-    }
-
-    #[test]
-    fn test_add_der_len() {
-        let tests = [
-            (0, "00"),
-            (1, "01"),
-            (126, "7e"),
-            (127, "7f"),
-            (128, "8180"),
-            (129, "8181"),
-            (255, "81ff"),
-            (256, "820100"),
-            (257, "820101"),
-            (65535, "82ffff"),
-        ];
-        for (input, want) in tests {
-            let mut got = Vec::new();
-            add_der_len(&mut got, input).unwrap();
-            assert_eq!(hex::encode(got), want, " for input length {input}");
-        }
-    }
-
-    #[test]
-    fn test_pkcs8_wrap_key_p256() {
-        // Key material taken from `ec_256_key` in
-        // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp
-        let input = hex::decode(concat!(
-            "3025",   // SEQUENCE (ECPrivateKey)
-            "020101", // INTEGER length 1 value 1 (version)
-            "0420",   // OCTET STRING (privateKey)
-            "737c2ecd7b8d1940bf2930aa9b4ed3ff",
-            "941eed09366bc03299986481f3a4d859",
-        ))
-        .unwrap();
-        let want = hex::decode(concat!(
-            // RFC 5208 s5
-            "3041",             // SEQUENCE (PrivateKeyInfo) {
-            "020100",           // INTEGER length 1 value 0 (version)
-            "3013",             // SEQUENCE length 0x13 (AlgorithmIdentifier) {
-            "0607",             // OBJECT IDENTIFIER length 7 (algorithm)
-            "2a8648ce3d0201",   // 1.2.840.10045.2.1 (ecPublicKey)
-            "0608",             // OBJECT IDENTIFIER length 8 (param)
-            "2a8648ce3d030107", //  1.2.840.10045.3.1.7 (secp256r1)
-            // } end SEQUENCE (AlgorithmIdentifier)
-            "0427",   // OCTET STRING (privateKey) holding...
-            "3025",   // SEQUENCE (ECPrivateKey)
-            "020101", // INTEGER length 1 value 1 (version)
-            "0420",   // OCTET STRING length 0x20 (privateKey)
-            "737c2ecd7b8d1940bf2930aa9b4ed3ff",
-            "941eed09366bc03299986481f3a4d859",
-            // } end SEQUENCE (ECPrivateKey)
-            // } end SEQUENCE (PrivateKeyInfo)
-        ))
-        .unwrap();
-        let got = pkcs8_wrap_nist_key(&input, EcCurve::P_256).unwrap();
-        assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input));
-    }
-
-    #[test]
-    fn test_pkcs8_wrap_key_p521() {
-        // Key material taken from `ec_521_key` in
-        // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp
-        let input = hex::decode(concat!(
-            "3047",   // SEQUENCE length 0xd3 (ECPrivateKey)
-            "020101", // INTEGER length 1 value 1 (version)
-            "0442",   // OCTET STRING length 0x42 (privateKey)
-            "0011458c586db5daa92afab03f4fe46a",
-            "a9d9c3ce9a9b7a006a8384bec4c78e8e",
-            "9d18d7d08b5bcfa0e53c75b064ad51c4",
-            "49bae0258d54b94b1e885ded08ed4fb2",
-            "5ce9",
-            // } end SEQUENCE (ECPrivateKey)
-        ))
-        .unwrap();
-        let want = hex::decode(concat!(
-            // RFC 5208 s5
-            "3060",           // SEQUENCE (PrivateKeyInfo) {
-            "020100",         // INTEGER length 1 value 0 (version)
-            "3010",           // SEQUENCE length 0x10 (AlgorithmIdentifier) {
-            "0607",           // OBJECT IDENTIFIER length 7 (algorithm)
-            "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey)
-            "0605",           // OBJECT IDENTIFIER length 5 (param)
-            "2b81040023",     //  1.3.132.0.35 (secp521r1)
-            // } end SEQUENCE (AlgorithmIdentifier)
-            "0449",   // OCTET STRING (privateKey) holding...
-            "3047",   // SEQUENCE (ECPrivateKey)
-            "020101", // INTEGER length 1 value 1 (version)
-            "0442",   // OCTET STRING length 0x42 (privateKey)
-            "0011458c586db5daa92afab03f4fe46a",
-            "a9d9c3ce9a9b7a006a8384bec4c78e8e",
-            "9d18d7d08b5bcfa0e53c75b064ad51c4",
-            "49bae0258d54b94b1e885ded08ed4fb2",
-            "5ce9",
-            // } end SEQUENCE (ECPrivateKey)
-            // } end SEQUENCE (PrivateKeyInfo)
-        ))
-        .unwrap();
-        let got = pkcs8_wrap_nist_key(&input, EcCurve::P_521).unwrap();
-        assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input));
-    }
-}
diff --git a/keystore2/src/sw_keyblob/tests.rs b/keystore2/src/sw_keyblob/tests.rs
new file mode 100644
index 0000000..fe01112
--- /dev/null
+++ b/keystore2/src/sw_keyblob/tests.rs
@@ -0,0 +1,449 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Tests for software-backed keyblobs.
+use super::*;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+    Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
+    KeyOrigin::KeyOrigin, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue as KPV,
+    KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, Tag::Tag,
+};
+
+macro_rules! expect_err {
+    ($result:expr, $err_msg:expr) => {
+        assert!(
+            $result.is_err(),
+            "Expected error containing '{}', got success {:?}",
+            $err_msg,
+            $result
+        );
+        let err = $result.err();
+        assert!(
+            format!("{:?}", err).contains($err_msg),
+            "Unexpected error {:?}, doesn't contain '{}'",
+            err,
+            $err_msg
+        );
+    };
+}
+
+#[test]
+fn test_consume_u8() {
+    let buffer = [1, 2];
+    let mut data = &buffer[..];
+    assert_eq!(1u8, consume_u8(&mut data).unwrap());
+    assert_eq!(2u8, consume_u8(&mut data).unwrap());
+    let result = consume_u8(&mut data);
+    expect_err!(result, "failed to find 1 byte");
+}
+
+#[test]
+fn test_consume_u32() {
+    // All supported platforms are little-endian.
+    let buffer = [
+        0x01, 0x02, 0x03, 0x04, // little-endian u32
+        0x04, 0x03, 0x02, 0x01, // little-endian u32
+        0x11, 0x12, 0x13,
+    ];
+    let mut data = &buffer[..];
+    assert_eq!(0x04030201u32, consume_u32(&mut data).unwrap());
+    assert_eq!(0x01020304u32, consume_u32(&mut data).unwrap());
+    let result = consume_u32(&mut data);
+    expect_err!(result, "failed to find 4 bytes");
+}
+
+#[test]
+fn test_consume_i64() {
+    // All supported platforms are little-endian.
+    let buffer = [
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // little-endian i64
+        0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // little-endian i64
+        0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    ];
+    let mut data = &buffer[..];
+    assert_eq!(0x0807060504030201i64, consume_i64(&mut data).unwrap());
+    assert_eq!(0x0102030405060708i64, consume_i64(&mut data).unwrap());
+    let result = consume_i64(&mut data);
+    expect_err!(result, "failed to find 8 bytes");
+}
+
+#[test]
+fn test_consume_vec() {
+    let buffer = [
+        0x01, 0x00, 0x00, 0x00, 0xaa, //
+        0x00, 0x00, 0x00, 0x00, //
+        0x01, 0x00, 0x00, 0x00, 0xbb, //
+        0x07, 0x00, 0x00, 0x00, 0xbb, // not enough data
+    ];
+    let mut data = &buffer[..];
+    assert_eq!(vec![0xaa], consume_vec(&mut data).unwrap());
+    assert_eq!(Vec::<u8>::new(), consume_vec(&mut data).unwrap());
+    assert_eq!(vec![0xbb], consume_vec(&mut data).unwrap());
+    let result = consume_vec(&mut data);
+    expect_err!(result, "failed to find 7 bytes");
+
+    let buffer = [
+        0x01, 0x00, 0x00, //
+    ];
+    let mut data = &buffer[..];
+    let result = consume_vec(&mut data);
+    expect_err!(result, "failed to find 4 bytes");
+}
+
+#[test]
+fn test_key_new_from_serialized() {
+    let hidden = hidden_params(&[], &[SOFTWARE_ROOT_OF_TRUST]);
+    // Test data originally generated by instrumenting Cuttlefish C++ KeyMint while running VTS
+    // tests.
+    let tests = [
+        (
+            concat!(
+                "0010000000d43c2f04f948521b81bdbf001310f5920000000000000000000000",
+                "00000000000c0000006400000002000010200000000300003080000000010000",
+                "2000000000010000200100000004000020020000000600002001000000be0200",
+                "1000000000c1020030b0ad0100c20200307b150300bd020060a8bb52407b0100",
+                "00ce02003011643401cf020030000000003b06b13ae6ae6671",
+            ),
+            KeyBlob {
+                key_material: hex::decode("d43c2f04f948521b81bdbf001310f592").unwrap(),
+                hw_enforced: vec![],
+                sw_enforced: vec![
+                    KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::AES) },
+                    KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(128) },
+                    KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::ENCRYPT) },
+                    KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::DECRYPT) },
+                    KeyParameter { tag: Tag::BLOCK_MODE, value: KPV::BlockMode(BlockMode::CBC) },
+                    KeyParameter { tag: Tag::PADDING, value: KPV::PaddingMode(PaddingMode::NONE) },
+                    KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+                    KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
+                    KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
+                    KeyParameter {
+                        tag: Tag::CREATION_DATETIME,
+                        value: KPV::DateTime(1628871769000),
+                    },
+                    KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
+                    KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
+                ],
+            },
+            Some(KeyFormat::RAW),
+        ),
+        (
+            concat!(
+                "00df0000003081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6b",
+                "a14e46ab7ca532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654",
+                "e9e79413cd503eae3d9cf68ed24f47a00706052b81040023a181890381860004",
+                "006b840f0db0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d14",
+                "0743bdd028db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b7",
+                "45e30142e90685646661550344113aaf28bdee6cb02d19df1faab4398556a909",
+                "7d6f64b95209601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3",
+                "b877984d000000000000000000000000000000000c0000006400000002000010",
+                "030000000a000010030000000100002002000000010000200300000005000020",
+                "000000000300003009020000be02001000000000c1020030b0ad0100c2020030",
+                "7b150300bd02006018d352407b010000ce02003011643401cf02003000000000",
+                "2f69002e55e9b0a3"
+            ),
+            KeyBlob {
+                key_material: hex::decode(concat!(
+                    "3081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6ba14e46ab7c",
+                    "a532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654e9e79413cd",
+                    "503eae3d9cf68ed24f47a00706052b81040023a181890381860004006b840f0d",
+                    "b0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d140743bdd028",
+                    "db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b745e30142e9",
+                    "0685646661550344113aaf28bdee6cb02d19df1faab4398556a9097d6f64b952",
+                    "09601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3b877984d",
+                ))
+                .unwrap(),
+                hw_enforced: vec![],
+                sw_enforced: vec![
+                    KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) },
+                    KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::P_521) },
+                    KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) },
+                    KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) },
+                    KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
+                    KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(521) },
+                    KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+                    KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
+                    KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
+                    KeyParameter {
+                        tag: Tag::CREATION_DATETIME,
+                        value: KPV::DateTime(1628871775000),
+                    },
+                    KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
+                    KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
+                ],
+            },
+            Some(KeyFormat::PKCS8),
+        ),
+        (
+            concat!(
+                "0037000000541d4c440223650d5f51753c1abd80c725034485551e874d62327c",
+                "65f6247a057f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc00000000",
+                "0000000000000000000000000c00000064000000020000108000000003000030",
+                "b801000001000020020000000100002003000000050000200400000008000030",
+                "00010000be02001000000000c1020030b0ad0100c20200307b150300bd020060",
+                "00d752407b010000ce02003011643401cf0200300000000036e6986ffc45fbb0",
+            ),
+            KeyBlob {
+                key_material: hex::decode(concat!(
+                    "541d4c440223650d5f51753c1abd80c725034485551e874d62327c65f6247a05",
+                    "7f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc"
+                ))
+                .unwrap(),
+                hw_enforced: vec![],
+                sw_enforced: vec![
+                    KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::HMAC) },
+                    KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(440) },
+                    KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) },
+                    KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) },
+                    KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::SHA_2_256) },
+                    KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KPV::Integer(256) },
+                    KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+                    KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
+                    KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
+                    KeyParameter {
+                        tag: Tag::CREATION_DATETIME,
+                        value: KPV::DateTime(1628871776000),
+                    },
+                    KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
+                    KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
+                ],
+            },
+            Some(KeyFormat::RAW),
+        ),
+        (
+            concat!(
+                "00a8040000308204a40201000282010100bc47b5c71116766669b91fa747df87",
+                "a1963df83956569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082",
+                "825c7c6e482a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31",
+                "c1ced84878edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c3973",
+                "6838151642eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d",
+                "5337c8bf9245d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0de",
+                "b0957d61dbba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2",
+                "540c84e45c4a99fb338b76bba7722856b5113341c349708937228f167d238ed8",
+                "efb9cc19547dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af",
+                "59fe87421af9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d3703",
+                "8bf9f588ae20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178",
+                "a848e06d558c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88e",
+                "e610260e406c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30a",
+                "e7a31f8262da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73a",
+                "e6f8b29a9144eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b",
+                "4217c8db50db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817",
+                "d9380b190bd382aaffa37785759f285194c11a188bccde0e2e2902818100fb23",
+                "3335770c9f3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a63",
+                "3f7790d1011bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f2",
+                "7a74852d6c7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd78009490",
+                "4c2856d2b944fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f45990281",
+                "8100bfecf2bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a7158458",
+                "18e01181ff06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb",
+                "0078b77fb5b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc84",
+                "20fcbf48d1eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b57",
+                "298902818100ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd",
+                "3765559994a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55",
+                "dad696d3821def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2",
+                "d8480ed07a7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f6332",
+                "38f521ba764902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6",
+                "603c6265f70018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d46",
+                "93fa48e3a9abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b",
+                "2a57e18a2e131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc",
+                "2af4ed75760858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1",
+                "c0a258d882984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4",
+                "340a9fae64a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c5526",
+                "1941b3654533b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a2",
+                "47f56a337e3b9845b4f2b61356000000000000000000000000000000000d0000",
+                "007000000002000010010000000300003000080000c800005001000100000000",
+                "0001000020020000000100002003000000050000200000000006000020010000",
+                "00be02001000000000c1020030b0ad0100c20200307b150300bd020060a8bb52",
+                "407b010000ce02003011643401cf02003000000000544862e9c961e857",
+            ),
+            KeyBlob {
+                key_material: hex::decode(concat!(
+                    "308204a40201000282010100bc47b5c71116766669b91fa747df87a1963df839",
+                    "56569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082825c7c6e48",
+                    "2a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31c1ced84878",
+                    "edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c39736838151642",
+                    "eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d5337c8bf92",
+                    "45d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0deb0957d61db",
+                    "ba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2540c84e45c",
+                    "4a99fb338b76bba7722856b5113341c349708937228f167d238ed8efb9cc1954",
+                    "7dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af59fe87421a",
+                    "f9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d37038bf9f588ae",
+                    "20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178a848e06d55",
+                    "8c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88ee610260e40",
+                    "6c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30ae7a31f8262",
+                    "da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73ae6f8b29a91",
+                    "44eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b4217c8db50",
+                    "db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817d9380b190b",
+                    "d382aaffa37785759f285194c11a188bccde0e2e2902818100fb233335770c9f",
+                    "3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a633f7790d101",
+                    "1bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f27a74852d6c",
+                    "7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd780094904c2856d2b9",
+                    "44fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f459902818100bfecf2",
+                    "bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a715845818e01181ff",
+                    "06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb0078b77fb5",
+                    "b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc8420fcbf48d1",
+                    "eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b572989028181",
+                    "00ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd3765559994",
+                    "a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55dad696d382",
+                    "1def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2d8480ed07a",
+                    "7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f633238f521ba76",
+                    "4902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6603c6265f7",
+                    "0018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d4693fa48e3a9",
+                    "abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b2a57e18a2e",
+                    "131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc2af4ed7576",
+                    "0858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1c0a258d882",
+                    "984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4340a9fae64",
+                    "a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c55261941b36545",
+                    "33b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a247f56a337e",
+                    "3b9845b4f2b61356",
+                ))
+                .unwrap(),
+                hw_enforced: vec![],
+                sw_enforced: vec![
+                    KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::RSA) },
+                    KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(2048) },
+                    KeyParameter { tag: Tag::RSA_PUBLIC_EXPONENT, value: KPV::LongInteger(65537) },
+                    KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) },
+                    KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) },
+                    KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
+                    KeyParameter { tag: Tag::PADDING, value: KPV::PaddingMode(PaddingMode::NONE) },
+                    KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+                    KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) },
+                    KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) },
+                    KeyParameter {
+                        tag: Tag::CREATION_DATETIME,
+                        value: KPV::DateTime(1628871769000),
+                    },
+                    KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) },
+                    KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) },
+                ],
+            },
+            // No support for RSA keys in export_key().
+            None,
+        ),
+    ];
+
+    for (input, want, want_format) in tests {
+        let input = hex::decode(input).unwrap();
+        let got = KeyBlob::new_from_serialized(&input, &hidden).expect("invalid keyblob!");
+        assert!(got == want);
+
+        if let Some(want_format) = want_format {
+            let (got_format, _key_material, params) =
+                export_key(&input, &[]).expect("invalid keyblob!");
+            assert_eq!(got_format, want_format);
+            // All the test cases are software-only keys.
+            assert_eq!(params, got.sw_enforced);
+        }
+    }
+}
+
+#[test]
+fn test_add_der_len() {
+    let tests = [
+        (0, "00"),
+        (1, "01"),
+        (126, "7e"),
+        (127, "7f"),
+        (128, "8180"),
+        (129, "8181"),
+        (255, "81ff"),
+        (256, "820100"),
+        (257, "820101"),
+        (65535, "82ffff"),
+    ];
+    for (input, want) in tests {
+        let mut got = Vec::new();
+        add_der_len(&mut got, input).unwrap();
+        assert_eq!(hex::encode(got), want, " for input length {input}");
+    }
+}
+
+#[test]
+fn test_pkcs8_wrap_key_p256() {
+    // Key material taken from `ec_256_key` in
+    // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp
+    let input = hex::decode(concat!(
+        "3025",   // SEQUENCE (ECPrivateKey)
+        "020101", // INTEGER length 1 value 1 (version)
+        "0420",   // OCTET STRING (privateKey)
+        "737c2ecd7b8d1940bf2930aa9b4ed3ff",
+        "941eed09366bc03299986481f3a4d859",
+    ))
+    .unwrap();
+    let want = hex::decode(concat!(
+        // RFC 5208 s5
+        "3041",             // SEQUENCE (PrivateKeyInfo) {
+        "020100",           // INTEGER length 1 value 0 (version)
+        "3013",             // SEQUENCE length 0x13 (AlgorithmIdentifier) {
+        "0607",             // OBJECT IDENTIFIER length 7 (algorithm)
+        "2a8648ce3d0201",   // 1.2.840.10045.2.1 (ecPublicKey)
+        "0608",             // OBJECT IDENTIFIER length 8 (param)
+        "2a8648ce3d030107", //  1.2.840.10045.3.1.7 (secp256r1)
+        // } end SEQUENCE (AlgorithmIdentifier)
+        "0427",   // OCTET STRING (privateKey) holding...
+        "3025",   // SEQUENCE (ECPrivateKey)
+        "020101", // INTEGER length 1 value 1 (version)
+        "0420",   // OCTET STRING length 0x20 (privateKey)
+        "737c2ecd7b8d1940bf2930aa9b4ed3ff",
+        "941eed09366bc03299986481f3a4d859",
+        // } end SEQUENCE (ECPrivateKey)
+        // } end SEQUENCE (PrivateKeyInfo)
+    ))
+    .unwrap();
+    let got = pkcs8_wrap_nist_key(&input, EcCurve::P_256).unwrap();
+    assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input));
+}
+
+#[test]
+fn test_pkcs8_wrap_key_p521() {
+    // Key material taken from `ec_521_key` in
+    // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp
+    let input = hex::decode(concat!(
+        "3047",   // SEQUENCE length 0xd3 (ECPrivateKey)
+        "020101", // INTEGER length 1 value 1 (version)
+        "0442",   // OCTET STRING length 0x42 (privateKey)
+        "0011458c586db5daa92afab03f4fe46a",
+        "a9d9c3ce9a9b7a006a8384bec4c78e8e",
+        "9d18d7d08b5bcfa0e53c75b064ad51c4",
+        "49bae0258d54b94b1e885ded08ed4fb2",
+        "5ce9",
+        // } end SEQUENCE (ECPrivateKey)
+    ))
+    .unwrap();
+    let want = hex::decode(concat!(
+        // RFC 5208 s5
+        "3060",           // SEQUENCE (PrivateKeyInfo) {
+        "020100",         // INTEGER length 1 value 0 (version)
+        "3010",           // SEQUENCE length 0x10 (AlgorithmIdentifier) {
+        "0607",           // OBJECT IDENTIFIER length 7 (algorithm)
+        "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey)
+        "0605",           // OBJECT IDENTIFIER length 5 (param)
+        "2b81040023",     //  1.3.132.0.35 (secp521r1)
+        // } end SEQUENCE (AlgorithmIdentifier)
+        "0449",   // OCTET STRING (privateKey) holding...
+        "3047",   // SEQUENCE (ECPrivateKey)
+        "020101", // INTEGER length 1 value 1 (version)
+        "0442",   // OCTET STRING length 0x42 (privateKey)
+        "0011458c586db5daa92afab03f4fe46a",
+        "a9d9c3ce9a9b7a006a8384bec4c78e8e",
+        "9d18d7d08b5bcfa0e53c75b064ad51c4",
+        "49bae0258d54b94b1e885ded08ed4fb2",
+        "5ce9",
+        // } end SEQUENCE (ECPrivateKey)
+        // } end SEQUENCE (PrivateKeyInfo)
+    ))
+    .unwrap();
+    let got = pkcs8_wrap_nist_key(&input, EcCurve::P_521).unwrap();
+    assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input));
+}
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index 54f382d..35290df 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -38,16 +38,23 @@
 };
 use android_system_keystore2::aidl::android::system::keystore2::{
     Authorization::Authorization, Domain::Domain, KeyDescriptor::KeyDescriptor,
+    ResponseCode::ResponseCode,
 };
 use anyhow::{Context, Result};
-use binder::{Strong, ThreadState};
+use binder::{FromIBinder, StatusCode, Strong, 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 log::{info, warn};
 use std::iter::IntoIterator;
+use std::thread::sleep;
+use std::time::Duration;
+
+#[cfg(test)]
+mod tests;
 
 /// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to GeneralizedTime
 /// 999912312359559, which is 253402300799000 ms from Jan 1, 1970.
@@ -73,6 +80,7 @@
 pub fn check_grant_permission(access_vec: KeyPermSet, key: &KeyDescriptor) -> anyhow::Result<()> {
     ThreadState::with_calling_sid(|calling_sid| {
         permission::check_grant_permission(
+            ThreadState::get_calling_uid(),
             calling_sid
                 .ok_or_else(Error::sys)
                 .context(ks_err!("Cannot check permission without calling_sid."))?,
@@ -119,14 +127,20 @@
 /// identifiers. It throws an error if the permissions cannot be verified or if the caller doesn't
 /// have the right permissions. Otherwise it returns silently.
 pub fn check_device_attestation_permissions() -> anyhow::Result<()> {
-    check_android_permission("android.permission.READ_PRIVILEGED_PHONE_STATE")
+    check_android_permission(
+        "android.permission.READ_PRIVILEGED_PHONE_STATE",
+        Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+    )
 }
 
 /// This function checks whether the calling app has the Android permissions needed to attest the
 /// device-unique identifier. It throws an error if the permissions cannot be verified or if the
 /// caller doesn't have the right permissions. Otherwise it returns silently.
 pub fn check_unique_id_attestation_permissions() -> anyhow::Result<()> {
-    check_android_permission("android.permission.REQUEST_UNIQUE_ID_ATTESTATION")
+    check_android_permission(
+        "android.permission.REQUEST_UNIQUE_ID_ATTESTATION",
+        Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+    )
 }
 
 /// This function checks whether the calling app has the Android permissions needed to manage
@@ -135,16 +149,24 @@
 /// It throws an error if the permissions cannot be verified or if the caller doesn't
 /// have the right permissions. Otherwise it returns silently.
 pub fn check_get_app_uids_affected_by_sid_permissions() -> anyhow::Result<()> {
-    check_android_permission("android.permission.MANAGE_USERS")
+    check_android_permission(
+        "android.permission.MANAGE_USERS",
+        Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+    )
 }
 
-fn check_android_permission(permission: &str) -> anyhow::Result<()> {
+/// This function checks whether the calling app has the Android permission needed to dump
+/// Keystore state to logcat.
+pub fn check_dump_permission() -> anyhow::Result<()> {
+    check_android_permission("android.permission.DUMP", Error::Rc(ResponseCode::PERMISSION_DENIED))
+}
+
+fn check_android_permission(permission: &str, err: Error) -> anyhow::Result<()> {
     let permission_controller: Strong<dyn IPermissionController::IPermissionController> =
         binder::get_interface("permission")?;
 
     let binder_result = {
-        let _wp =
-            watchdog::watch("In check_device_attestation_permissions: calling checkPermission.");
+        let _wp = watchdog::watch("check_android_permission: calling checkPermission");
         permission_controller.checkPermission(
             permission,
             ThreadState::get_calling_pid(),
@@ -155,8 +177,7 @@
         map_binder_status(binder_result).context(ks_err!("checkPermission failed"))?;
     match has_permissions {
         true => Ok(()),
-        false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS))
-            .context(ks_err!("caller does not have the permission to attest device IDs")),
+        false => Err(err).context(ks_err!("caller does not have the '{permission}' permission")),
     }
 }
 
@@ -261,7 +282,9 @@
     log::debug!("import parameters={import_params:?}");
 
     let creation_result = {
-        let _wp = watchdog::watch("In utils::import_keyblob_and_perform_op: calling importKey.");
+        let _wp = watchdog::watch(
+            "utils::import_keyblob_and_perform_op: calling IKeyMintDevice::importKey",
+        );
         map_km_error(km_dev.importKey(&import_params, format, &key_material, None))
     }
     .context(ks_err!("Upgrade failed."))?;
@@ -301,7 +324,9 @@
     NewBlobHandler: FnOnce(&[u8]) -> Result<()>,
 {
     let upgraded_blob = {
-        let _wp = watchdog::watch("In utils::upgrade_keyblob_and_perform_op: calling upgradeKey.");
+        let _wp = watchdog::watch(
+            "utils::upgrade_keyblob_and_perform_op: calling IKeyMintDevice::upgradeKey.",
+        );
         map_km_error(km_dev.upgradeKey(key_blob, upgrade_params))
     }
     .context(ks_err!("Upgrade failed."))?;
@@ -513,44 +538,48 @@
     result
 }
 
-fn estimate_safe_amount_to_return(
+pub(crate) fn estimate_safe_amount_to_return(
     domain: Domain,
     namespace: i64,
+    start_past_alias: Option<&str>,
     key_descriptors: &[KeyDescriptor],
     response_size_limit: usize,
 ) -> usize {
-    let mut items_to_return = 0;
-    let mut returned_bytes: usize = 0;
+    let mut count = 0;
+    let mut bytes: usize = 0;
     // Estimate the transaction size to avoid returning more items than what
     // could fit in a binder transaction.
     for kd in key_descriptors.iter() {
         // 4 bytes for the Domain enum
         // 8 bytes for the Namespace long.
-        returned_bytes += 4 + 8;
+        bytes += 4 + 8;
         // Size of the alias string. Includes 4 bytes for length encoding.
         if let Some(alias) = &kd.alias {
-            returned_bytes += 4 + alias.len();
+            bytes += 4 + alias.len();
         }
         // Size of the blob. Includes 4 bytes for length encoding.
         if let Some(blob) = &kd.blob {
-            returned_bytes += 4 + blob.len();
+            bytes += 4 + blob.len();
         }
         // The binder transaction size limit is 1M. Empirical measurements show
         // that the binder overhead is 60% (to be confirmed). So break after
         // 350KB and return a partial list.
-        if returned_bytes > response_size_limit {
+        if bytes > response_size_limit {
             log::warn!(
-                "{domain:?}:{namespace}: Key descriptors list ({} items) may exceed binder \
-                       size, returning {items_to_return} items est {returned_bytes} bytes.",
+                "{domain:?}:{namespace}: Key descriptors list ({} items after {start_past_alias:?}) \
+                 may exceed binder size, returning {count} items est. {bytes} bytes",
                 key_descriptors.len(),
             );
             break;
         }
-        items_to_return += 1;
+        count += 1;
     }
-    items_to_return
+    count
 }
 
+/// Estimate for maximum size of a Binder response in bytes.
+pub(crate) const RESPONSE_SIZE_LIMIT: usize = 358400;
+
 /// List all key aliases for a given domain + namespace. whose alias is greater
 /// than start_past_alias (if provided).
 pub fn list_key_entries(
@@ -574,9 +603,13 @@
         start_past_alias,
     );
 
-    const RESPONSE_SIZE_LIMIT: usize = 358400;
-    let safe_amount_to_return =
-        estimate_safe_amount_to_return(domain, namespace, &merged_key_entries, RESPONSE_SIZE_LIMIT);
+    let safe_amount_to_return = estimate_safe_amount_to_return(
+        domain,
+        namespace,
+        start_past_alias,
+        &merged_key_entries,
+        RESPONSE_SIZE_LIMIT,
+    );
     Ok(merged_key_entries[..safe_amount_to_return].to_vec())
 }
 
@@ -629,129 +662,24 @@
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use anyhow::Result;
+pub(crate) fn retry_get_interface<T: FromIBinder + ?Sized>(
+    name: &str,
+) -> Result<Strong<T>, StatusCode> {
+    let retry_count = if cfg!(early_vm) { 5 } else { 1 };
 
-    #[test]
-    fn check_device_attestation_permissions_test() -> Result<()> {
-        check_device_attestation_permissions().or_else(|error| {
-            match error.root_cause().downcast_ref::<Error>() {
-                // Expected: the context for this test might not be allowed to attest device IDs.
-                Some(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) => Ok(()),
-                // Other errors are unexpected
-                _ => Err(error),
+    let mut wait_time = Duration::from_secs(5);
+    for i in 1..retry_count {
+        match binder::get_interface(name) {
+            Ok(res) => return Ok(res),
+            Err(e) => {
+                warn!("failed to get interface {name}. Retry {i}/{retry_count}: {e:?}");
+                sleep(wait_time);
+                wait_time *= 2;
             }
-        })
+        }
     }
-
-    fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> {
-        key_aliases
-            .iter()
-            .map(|key_alias| KeyDescriptor {
-                domain: Domain::APP,
-                nspace: 0,
-                alias: Some(key_alias.to_string()),
-                blob: None,
-            })
-            .collect::<Vec<KeyDescriptor>>()
+    if retry_count > 1 {
+        info!("{retry_count}-th (last) retry to get interface: {name}");
     }
-
-    fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> {
-        key_descriptors
-            .iter()
-            .map(
-                |kd| {
-                    if let Some(alias) = &kd.alias {
-                        String::from(alias)
-                    } else {
-                        String::from("")
-                    }
-                },
-            )
-            .collect::<Vec<String>>()
-    }
-
-    #[test]
-    fn test_safe_amount_to_return() -> Result<()> {
-        let key_aliases = vec!["key1", "key2", "key3"];
-        let key_descriptors = create_key_descriptors_from_aliases(&key_aliases);
-
-        assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 20), 1);
-        assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 50), 2);
-        assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 100), 3);
-        Ok(())
-    }
-
-    #[test]
-    fn test_merge_and_sort_lists_without_filtering() -> Result<()> {
-        let legacy_key_aliases = vec!["key_c", "key_a", "key_b"];
-        let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
-        let db_key_aliases = vec!["key_a", "key_d"];
-        let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
-        let result =
-            merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None);
-        assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]);
-        Ok(())
-    }
-
-    #[test]
-    fn test_merge_and_sort_lists_with_filtering() -> Result<()> {
-        let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
-        let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
-        let db_key_aliases = vec!["key_c", "key_g"];
-        let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
-        let result = merge_and_filter_key_entry_lists(
-            &legacy_key_descriptors,
-            &db_key_descriptors,
-            Some("key_b"),
-        );
-        assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]);
-        Ok(())
-    }
-
-    #[test]
-    fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> {
-        let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
-        let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
-        let db_key_aliases = vec!["key_d", "key_e", "key_g"];
-        let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
-        let result = merge_and_filter_key_entry_lists(
-            &legacy_key_descriptors,
-            &db_key_descriptors,
-            Some("key_c"),
-        );
-        assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]);
-        Ok(())
-    }
-
-    #[test]
-    fn test_list_key_parameters_with_filter_on_security_sensitive_info() -> Result<()> {
-        let params = vec![
-            KmKeyParameter { tag: Tag::APPLICATION_ID, value: KeyParameterValue::Integer(0) },
-            KmKeyParameter { tag: Tag::APPLICATION_DATA, value: KeyParameterValue::Integer(0) },
-            KmKeyParameter {
-                tag: Tag::CERTIFICATE_NOT_AFTER,
-                value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
-            },
-            KmKeyParameter {
-                tag: Tag::CERTIFICATE_NOT_BEFORE,
-                value: KeyParameterValue::DateTime(0),
-            },
-        ];
-        let wanted = vec![
-            KmKeyParameter {
-                tag: Tag::CERTIFICATE_NOT_AFTER,
-                value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
-            },
-            KmKeyParameter {
-                tag: Tag::CERTIFICATE_NOT_BEFORE,
-                value: KeyParameterValue::DateTime(0),
-            },
-        ];
-
-        assert_eq!(log_security_safe_params(&params), wanted);
-        Ok(())
-    }
+    binder::get_interface(name)
 }
diff --git a/keystore2/src/utils/tests.rs b/keystore2/src/utils/tests.rs
new file mode 100644
index 0000000..e514b2a
--- /dev/null
+++ b/keystore2/src/utils/tests.rs
@@ -0,0 +1,125 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Utility functions tests.
+
+use super::*;
+use anyhow::Result;
+
+#[test]
+fn check_device_attestation_permissions_test() -> Result<()> {
+    check_device_attestation_permissions().or_else(|error| {
+        match error.root_cause().downcast_ref::<Error>() {
+            // Expected: the context for this test might not be allowed to attest device IDs.
+            Some(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) => Ok(()),
+            // Other errors are unexpected
+            _ => Err(error),
+        }
+    })
+}
+
+fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> {
+    key_aliases
+        .iter()
+        .map(|key_alias| KeyDescriptor {
+            domain: Domain::APP,
+            nspace: 0,
+            alias: Some(key_alias.to_string()),
+            blob: None,
+        })
+        .collect::<Vec<KeyDescriptor>>()
+}
+
+fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> {
+    key_descriptors
+        .iter()
+        .map(|kd| if let Some(alias) = &kd.alias { String::from(alias) } else { String::from("") })
+        .collect::<Vec<String>>()
+}
+
+#[test]
+fn test_safe_amount_to_return() -> Result<()> {
+    let key_aliases = vec!["key1", "key2", "key3"];
+    let key_descriptors = create_key_descriptors_from_aliases(&key_aliases);
+
+    assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, None, &key_descriptors, 20), 1);
+    assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, None, &key_descriptors, 50), 2);
+    assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, None, &key_descriptors, 100), 3);
+    Ok(())
+}
+
+#[test]
+fn test_merge_and_sort_lists_without_filtering() -> Result<()> {
+    let legacy_key_aliases = vec!["key_c", "key_a", "key_b"];
+    let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
+    let db_key_aliases = vec!["key_a", "key_d"];
+    let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
+    let result =
+        merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None);
+    assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]);
+    Ok(())
+}
+
+#[test]
+fn test_merge_and_sort_lists_with_filtering() -> Result<()> {
+    let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
+    let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
+    let db_key_aliases = vec!["key_c", "key_g"];
+    let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
+    let result = merge_and_filter_key_entry_lists(
+        &legacy_key_descriptors,
+        &db_key_descriptors,
+        Some("key_b"),
+    );
+    assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]);
+    Ok(())
+}
+
+#[test]
+fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> {
+    let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
+    let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
+    let db_key_aliases = vec!["key_d", "key_e", "key_g"];
+    let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
+    let result = merge_and_filter_key_entry_lists(
+        &legacy_key_descriptors,
+        &db_key_descriptors,
+        Some("key_c"),
+    );
+    assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]);
+    Ok(())
+}
+
+#[test]
+fn test_list_key_parameters_with_filter_on_security_sensitive_info() -> Result<()> {
+    let params = vec![
+        KmKeyParameter { tag: Tag::APPLICATION_ID, value: KeyParameterValue::Integer(0) },
+        KmKeyParameter { tag: Tag::APPLICATION_DATA, value: KeyParameterValue::Integer(0) },
+        KmKeyParameter {
+            tag: Tag::CERTIFICATE_NOT_AFTER,
+            value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
+        },
+        KmKeyParameter { tag: Tag::CERTIFICATE_NOT_BEFORE, value: KeyParameterValue::DateTime(0) },
+    ];
+    let wanted = vec![
+        KmKeyParameter {
+            tag: Tag::CERTIFICATE_NOT_AFTER,
+            value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER),
+        },
+        KmKeyParameter { tag: Tag::CERTIFICATE_NOT_BEFORE, value: KeyParameterValue::DateTime(0) },
+    ];
+
+    assert_eq!(log_security_safe_params(&params), wanted);
+    Ok(())
+}
diff --git a/keystore2/src/watchdog_helper.rs b/keystore2/src/watchdog_helper.rs
index 1072ac0..63383aa 100644
--- a/keystore2/src/watchdog_helper.rs
+++ b/keystore2/src/watchdog_helper.rs
@@ -17,8 +17,7 @@
 /// This module provides helpers for simplified use of the watchdog module.
 #[cfg(feature = "watchdog")]
 pub mod watchdog {
-    use lazy_static::lazy_static;
-    use std::sync::Arc;
+    use std::sync::{Arc, LazyLock};
     use std::time::Duration;
     pub use watchdog_rs::WatchPoint;
     use watchdog_rs::Watchdog;
@@ -28,10 +27,8 @@
 
     const DEFAULT_TIMEOUT: Duration = Duration::from_millis(DEFAULT_TIMEOUT_MS);
 
-    lazy_static! {
-        /// A Watchdog thread, that can be used to create watch points.
-        static ref WD: Arc<Watchdog> = Watchdog::new(Duration::from_secs(10));
-    }
+    /// A Watchdog thread, that can be used to create watch points.
+    static WD: LazyLock<Arc<Watchdog>> = LazyLock::new(|| Watchdog::new(Duration::from_secs(10)));
 
     /// Sets a watch point with `id` and a timeout of `millis` milliseconds.
     pub fn watch_millis(id: &'static str, millis: u64) -> Option<WatchPoint> {
diff --git a/keystore2/test_utils/Android.bp b/keystore2/test_utils/Android.bp
index 4c7c18a..57da27f 100644
--- a/keystore2/test_utils/Android.bp
+++ b/keystore2/test_utils/Android.bp
@@ -42,15 +42,15 @@
         "libthiserror",
     ],
     static_libs: [
+        "libcppbor",
+        "libkeymaster_portable",
+        "libkeymint_support",
         "libkeystore-engine",
         "libkeystore2_ffi_test_utils",
     ],
     shared_libs: [
-        "android.system.keystore2-V4-ndk",
         "libbase",
         "libcrypto",
-        "libkeymaster_portable",
-        "libkeymint_support",
     ],
 }
 
@@ -59,6 +59,12 @@
     crate_name: "keystore2_test_utils",
     srcs: ["lib.rs"],
     defaults: ["libkeystore2_test_utils_defaults"],
+    static_libs: [
+        // Also include static_libs for the NDK variants so that they are available
+        // for dependencies.
+        "android.system.keystore2-V5-ndk",
+        "android.hardware.security.keymint-V4-ndk",
+    ],
 }
 
 rust_test {
@@ -75,20 +81,22 @@
     name: "libkeystore2_ffi_test_utils",
     srcs: ["ffi_test_utils.cpp"],
     defaults: [
-        "keymint_use_latest_hal_aidl_ndk_shared",
-        "keystore2_use_latest_aidl_ndk_shared",
+        "keymint_use_latest_hal_aidl_ndk_static",
+        "keystore2_use_latest_aidl_ndk_static",
     ],
     generated_headers: [
         "cxx-bridge-header",
         "libkeystore2_ffi_test_utils_bridge_header",
     ],
     generated_sources: ["libkeystore2_ffi_test_utils_bridge_code"],
-    static_libs: ["libkeystore-engine"],
+    static_libs: [
+        "libkeymaster_portable",
+        "libkeymint_support",
+        "libkeystore-engine",
+    ],
     shared_libs: [
         "libbase",
         "libcrypto",
-        "libkeymaster_portable",
-        "libkeymint_support",
     ],
 }
 
diff --git a/keystore2/test_utils/attestation/Android.bp b/keystore2/test_utils/attestation/Android.bp
new file mode 100644
index 0000000..dbf02d0
--- /dev/null
+++ b/keystore2/test_utils/attestation/Android.bp
@@ -0,0 +1,54 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_security_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_security_license"],
+}
+
+rust_defaults {
+    name: "libkeystore_attestation_defaults",
+    crate_name: "keystore_attestation",
+    srcs: ["lib.rs"],
+    defaults: [
+        "keymint_use_latest_hal_aidl_rust",
+    ],
+    rustlibs: [
+        "libbinder_rs",
+        "libder",
+        "liblog_rust",
+        "libspki",
+        "libx509_cert",
+    ],
+}
+
+rust_library {
+    name: "libkeystore_attestation",
+    defaults: ["libkeystore_attestation_defaults"],
+}
+
+rust_test {
+    name: "libkeystore_attestation_test",
+    defaults: ["libkeystore_attestation_defaults"],
+    rustlibs: [
+        "libhex",
+    ],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    compile_multilib: "first",
+}
diff --git a/keystore2/test_utils/attestation/lib.rs b/keystore2/test_utils/attestation/lib.rs
new file mode 100644
index 0000000..31d3314
--- /dev/null
+++ b/keystore2/test_utils/attestation/lib.rs
@@ -0,0 +1,693 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Attestation parsing.
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+    Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
+    HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin,
+    KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue as KPV,
+    KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, Tag::Tag, TagType::TagType,
+};
+use der::asn1::{Null, ObjectIdentifier, OctetStringRef, SetOfVec};
+use der::{oid::AssociatedOid, DerOrd, Enumerated, Reader, Sequence, SliceReader};
+use der::{Decode, EncodeValue, Length};
+use std::borrow::Cow;
+
+/// Determine the tag type for a tag, based on the top 4 bits of the tag number.
+fn tag_type(tag: Tag) -> TagType {
+    let raw_type = (tag.0 as u32) & 0xf0000000;
+    TagType(raw_type as i32)
+}
+
+/// Determine the raw tag value with tag type information stripped out.
+fn raw_tag_value(tag: Tag) -> u32 {
+    (tag.0 as u32) & 0x0fffffffu32
+}
+
+/// OID value for the Android Attestation extension.
+pub const ATTESTATION_EXTENSION_OID: ObjectIdentifier =
+    ObjectIdentifier::new_unwrap("1.3.6.1.4.1.11129.2.1.17");
+
+/// Attestation extension contents
+#[derive(Debug, Clone, Sequence, PartialEq)]
+pub struct AttestationExtension<'a> {
+    /// Attestation version.
+    pub attestation_version: i32,
+    /// Security level that created the attestation.
+    pub attestation_security_level: SecurityLevel,
+    /// Keymint version.
+    pub keymint_version: i32,
+    /// Security level of the KeyMint instance holding the key.
+    pub keymint_security_level: SecurityLevel,
+    /// Attestation challenge.
+    #[asn1(type = "OCTET STRING")]
+    pub attestation_challenge: &'a [u8],
+    /// Unique ID.
+    #[asn1(type = "OCTET STRING")]
+    pub unique_id: &'a [u8],
+    /// Software-enforced key characteristics.
+    pub sw_enforced: AuthorizationList<'a>,
+    /// Hardware-enforced key characteristics.
+    pub hw_enforced: AuthorizationList<'a>,
+}
+
+impl AssociatedOid for AttestationExtension<'_> {
+    const OID: ObjectIdentifier = ATTESTATION_EXTENSION_OID;
+}
+
+/// Security level enumeration
+#[repr(u32)]
+#[derive(Debug, Clone, Copy, Enumerated, PartialEq)]
+pub enum SecurityLevel {
+    /// Software.
+    Software = 0,
+    /// TEE.
+    TrustedEnvironment = 1,
+    /// StrongBox.
+    Strongbox = 2,
+}
+
+/// Root of Trust ASN.1 structure
+#[derive(Debug, Clone, Sequence)]
+pub struct RootOfTrust<'a> {
+    /// Verified boot key hash.
+    #[asn1(type = "OCTET STRING")]
+    pub verified_boot_key: &'a [u8],
+    /// Device bootloader lock state.
+    pub device_locked: bool,
+    /// Verified boot state.
+    pub verified_boot_state: VerifiedBootState,
+    /// Verified boot hash
+    #[asn1(type = "OCTET STRING")]
+    pub verified_boot_hash: &'a [u8],
+}
+
+/// Attestation Application ID ASN.1 structure
+#[derive(Debug, Clone, Sequence)]
+pub struct AttestationApplicationId<'a> {
+    /// Package info.
+    pub package_info_records: SetOfVec<PackageInfoRecord<'a>>,
+    /// Signatures.
+    pub signature_digests: SetOfVec<OctetStringRef<'a>>,
+}
+
+/// Package record
+#[derive(Debug, Clone, Sequence)]
+pub struct PackageInfoRecord<'a> {
+    /// Package name
+    pub package_name: OctetStringRef<'a>,
+    /// Package version
+    pub version: i64,
+}
+
+impl DerOrd for PackageInfoRecord<'_> {
+    fn der_cmp(&self, other: &Self) -> Result<std::cmp::Ordering, der::Error> {
+        self.package_name.der_cmp(&other.package_name)
+    }
+}
+
+/// Verified Boot State as ASN.1 ENUMERATED type.
+#[repr(u32)]
+#[derive(Debug, Clone, Copy, Enumerated)]
+pub enum VerifiedBootState {
+    /// Verified.
+    Verified = 0,
+    /// Self-signed.
+    SelfSigned = 1,
+    /// Unverified.
+    Unverified = 2,
+    /// Failed.
+    Failed = 3,
+}
+
+/// Struct corresponding to an ASN.1 DER-serialized `AuthorizationList`.
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct AuthorizationList<'a> {
+    /// Key authorizations.
+    pub auths: Cow<'a, [KeyParameter]>,
+}
+
+impl From<Vec<KeyParameter>> for AuthorizationList<'_> {
+    /// Build an `AuthorizationList` using a set of key parameters.
+    fn from(auths: Vec<KeyParameter>) -> Self {
+        AuthorizationList { auths: auths.into() }
+    }
+}
+
+impl<'a> Sequence<'a> for AuthorizationList<'a> {}
+
+/// Stub (non-)implementation of DER-encoding, needed to implement [`Sequence`].
+impl EncodeValue for AuthorizationList<'_> {
+    fn value_len(&self) -> der::Result<Length> {
+        unimplemented!("Only decoding is implemented");
+    }
+    fn encode_value(&self, _writer: &mut impl der::Writer) -> der::Result<()> {
+        unimplemented!("Only decoding is implemented");
+    }
+}
+
+/// Implementation of [`der::DecodeValue`] which constructs an [`AuthorizationList`] from bytes.
+impl<'a> der::DecodeValue<'a> for AuthorizationList<'a> {
+    fn decode_value<R: der::Reader<'a>>(decoder: &mut R, header: der::Header) -> der::Result<Self> {
+        // Decode tags in the expected order.
+        let contents = decoder.read_slice(header.length)?;
+        let mut reader = SliceReader::new(contents)?;
+        let decoder = &mut reader;
+        let mut auths = Vec::new();
+        let mut next: Option<u32> = None;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::PURPOSE)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ALGORITHM)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::KEY_SIZE)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::BLOCK_MODE)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::DIGEST)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::PADDING)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::CALLER_NONCE)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::MIN_MAC_LENGTH)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::EC_CURVE)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::RSA_PUBLIC_EXPONENT)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::RSA_OAEP_MGF_DIGEST)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ROLLBACK_RESISTANCE)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::EARLY_BOOT_ONLY)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ACTIVE_DATETIME)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ORIGINATION_EXPIRE_DATETIME)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::USAGE_EXPIRE_DATETIME)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::USAGE_COUNT_LIMIT)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::USER_SECURE_ID)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::NO_AUTH_REQUIRED)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::USER_AUTH_TYPE)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::AUTH_TIMEOUT)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ALLOW_WHILE_ON_BODY)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::TRUSTED_USER_PRESENCE_REQUIRED)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::TRUSTED_CONFIRMATION_REQUIRED)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::UNLOCKED_DEVICE_REQUIRED)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::CREATION_DATETIME)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::CREATION_DATETIME)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ORIGIN)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ROOT_OF_TRUST)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::OS_VERSION)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::OS_PATCHLEVEL)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_APPLICATION_ID)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_BRAND)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_DEVICE)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_PRODUCT)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_SERIAL)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_SERIAL)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_SERIAL)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_IMEI)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_MEID)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_MANUFACTURER)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_MODEL)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::VENDOR_PATCHLEVEL)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::BOOT_PATCHLEVEL)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::DEVICE_UNIQUE_ATTESTATION)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::ATTESTATION_ID_SECOND_IMEI)?;
+        next = decode_opt_field(decoder, next, &mut auths, Tag::MODULE_HASH)?;
+
+        if next.is_some() {
+            // Extra tag encountered.
+            return Err(decoder.error(der::ErrorKind::Incomplete {
+                expected_len: Length::ZERO,
+                actual_len: decoder.remaining_len(),
+            }));
+        }
+
+        Ok(auths.into())
+    }
+}
+
+/// Attempt to decode an optional field associated with `expected_tag` from the `decoder`.
+///
+/// If `already_read_asn1_tag` is provided, then that ASN.1 tag has already been read from the
+/// `decoder` and its associated data is next.
+///
+/// (Because the field is optional, we might not read the tag we expect, but instead a later tag
+/// from the list.  If this happens, the actual decoded ASN.1 tag value is returned to the caller to
+/// be passed in on the next call to this function.)
+///
+/// If the decoded or re-used ASN.1 tag is the expected one, continue on to read the associated
+/// value and populate it in `auths`.
+fn decode_opt_field<'a, R: der::Reader<'a>>(
+    decoder: &mut R,
+    already_read_asn1_tag: Option<u32>,
+    auths: &mut Vec<KeyParameter>,
+    expected_tag: Tag,
+) -> Result<Option<u32>, der::Error> {
+    // Decode the ASN.1 tag if no tag is provided
+    let asn1_tag = match already_read_asn1_tag {
+        Some(tag) => Some(tag),
+        None => decode_explicit_tag_from_bytes(decoder)?,
+    };
+    let expected_asn1_tag = raw_tag_value(expected_tag);
+    match asn1_tag {
+        Some(v) if v == expected_asn1_tag => {
+            // Decode the length of the inner encoding
+            let inner_len = Length::decode(decoder)?;
+            if decoder.remaining_len() < inner_len {
+                return Err(der::ErrorKind::Incomplete {
+                    expected_len: inner_len,
+                    actual_len: decoder.remaining_len(),
+                }
+                .into());
+            }
+            let next_tlv = decoder.tlv_bytes()?;
+            decode_value_from_bytes(expected_tag, next_tlv, auths)?;
+            Ok(None)
+        }
+        Some(tag) => Ok(Some(tag)), // Return the tag for which the value is unread.
+        None => Ok(None),
+    }
+}
+
+/// Decode one or more `KeyParameterValue`s of the type associated with `tag` from the `decoder`,
+/// and add them to `auths`.
+fn decode_value_from_bytes(
+    tag: Tag,
+    data: &[u8],
+    auths: &mut Vec<KeyParameter>,
+) -> Result<(), der::Error> {
+    match tag_type(tag) {
+        TagType::ENUM_REP => {
+            let values = SetOfVec::<i32>::from_der(data)?;
+            for value in values.as_slice() {
+                auths.push(KeyParameter {
+                    tag,
+                    value: match tag {
+                        Tag::BLOCK_MODE => KPV::BlockMode(BlockMode(*value)),
+                        Tag::PADDING => KPV::PaddingMode(PaddingMode(*value)),
+                        Tag::DIGEST => KPV::Digest(Digest(*value)),
+                        Tag::RSA_OAEP_MGF_DIGEST => KPV::Digest(Digest(*value)),
+                        Tag::PURPOSE => KPV::KeyPurpose(KeyPurpose(*value)),
+                        _ => return Err(der::ErrorKind::TagNumberInvalid.into()),
+                    },
+                });
+            }
+        }
+        TagType::UINT_REP => {
+            let values = SetOfVec::<i32>::from_der(data)?;
+            for value in values.as_slice() {
+                auths.push(KeyParameter { tag, value: KPV::Integer(*value) });
+            }
+        }
+        TagType::ENUM => {
+            let value = i32::from_der(data)?;
+            auths.push(KeyParameter {
+                tag,
+                value: match tag {
+                    Tag::ALGORITHM => KPV::Algorithm(Algorithm(value)),
+                    Tag::EC_CURVE => KPV::EcCurve(EcCurve(value)),
+                    Tag::ORIGIN => KPV::Origin(KeyOrigin(value)),
+                    Tag::USER_AUTH_TYPE => {
+                        KPV::HardwareAuthenticatorType(HardwareAuthenticatorType(value))
+                    }
+                    _ => return Err(der::ErrorKind::TagNumberInvalid.into()),
+                },
+            });
+        }
+        TagType::UINT => {
+            let value = i32::from_der(data)?;
+            auths.push(KeyParameter { tag, value: KPV::Integer(value) });
+        }
+        TagType::ULONG => {
+            let value = i64::from_der(data)?;
+            auths.push(KeyParameter { tag, value: KPV::LongInteger(value) });
+        }
+        TagType::DATE => {
+            let value = i64::from_der(data)?;
+            auths.push(KeyParameter { tag, value: KPV::DateTime(value) });
+        }
+        TagType::BOOL => {
+            let _value = Null::from_der(data)?;
+            auths.push(KeyParameter { tag, value: KPV::BoolValue(true) });
+        }
+        TagType::BYTES if tag == Tag::ROOT_OF_TRUST => {
+            // Special case: root of trust is an ASN.1 `SEQUENCE` not an `OCTET STRING` so don't
+            // decode the bytes.
+            auths.push(KeyParameter { tag: Tag::ROOT_OF_TRUST, value: KPV::Blob(data.to_vec()) });
+        }
+        TagType::BYTES | TagType::BIGNUM => {
+            let value = OctetStringRef::from_der(data)?.as_bytes().to_vec();
+            auths.push(KeyParameter { tag, value: KPV::Blob(value) });
+        }
+        _ => {
+            return Err(der::ErrorKind::TagNumberInvalid.into());
+        }
+    }
+    Ok(())
+}
+
+/// Decode an explicit ASN.1 tag value, coping with large (>=31) tag values
+/// (which the `der` crate doesn't deal with).  Returns `Ok(None)` if the
+/// decoder is empty.
+fn decode_explicit_tag_from_bytes<'a, R: der::Reader<'a>>(
+    decoder: &mut R,
+) -> Result<Option<u32>, der::Error> {
+    if decoder.remaining_len() == Length::ZERO {
+        return Ok(None);
+    }
+    let b1 = decoder.read_byte()?;
+    let tag = if b1 & 0b00011111 == 0b00011111u8 {
+        // The initial byte of 0xbf indicates a larger (>=31) value for the ASN.1 tag:
+        // - 0bXY...... = class
+        // - 0b..C..... = constructed/primitive bit
+        // - 0b...11111 = marker indicating high tag form, tag value to follow
+        //
+        // The top three bits should be 0b101 = constructed context-specific
+        if b1 & 0b11100000 != 0b10100000 {
+            return Err(der::ErrorKind::TagNumberInvalid.into());
+        }
+
+        // The subsequent encoded tag value is broken down into 7-bit chunks (in big-endian order),
+        // and each chunk gets a high bit of 1 except the last, which gets a high bit of zero.
+        let mut bit_count = 0;
+        let mut tag: u32 = 0;
+        loop {
+            let b = decoder.read_byte()?;
+            let low_b = b & 0b01111111;
+            if bit_count == 0 && low_b == 0 {
+                // The first part of the tag number is zero, implying it is not miminally encoded.
+                return Err(der::ErrorKind::TagNumberInvalid.into());
+            }
+
+            bit_count += 7;
+            if bit_count > 32 {
+                // Tag value has more bits than the output type can hold.
+                return Err(der::ErrorKind::TagNumberInvalid.into());
+            }
+            tag = (tag << 7) | (low_b as u32);
+            if b & 0x80u8 == 0x00u8 {
+                // Top bit clear => this is the final part of the value.
+                if tag < 31 {
+                    // Tag is small enough that it should have been in short form.
+                    return Err(der::ErrorKind::TagNumberInvalid.into());
+                }
+                break tag;
+            }
+        }
+    } else {
+        // Get the tag value from the low 5 bits.
+        (b1 & 0b00011111u8) as u32
+    };
+    Ok(Some(tag))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use der::Encode;
+
+    const SIG: &[u8; 32] = &[
+        0xa4, 0x0d, 0xa8, 0x0a, 0x59, 0xd1, 0x70, 0xca, 0xa9, 0x50, 0xcf, 0x15, 0xc1, 0x8c, 0x45,
+        0x4d, 0x47, 0xa3, 0x9b, 0x26, 0x98, 0x9d, 0x8b, 0x64, 0x0e, 0xcd, 0x74, 0x5b, 0xa7, 0x1b,
+        0xf5, 0xdc,
+    ];
+    const VB_KEY: &[u8; 32] = &[0; 32];
+    const VB_HASH: &[u8; 32] = &[
+        0x6f, 0x84, 0xe6, 0x02, 0x73, 0x9d, 0x86, 0x2c, 0x93, 0x2a, 0x28, 0xf0, 0xa5, 0x27, 0x65,
+        0xa4, 0xae, 0xc2, 0x27, 0x8c, 0xb6, 0x3b, 0xe9, 0xbb, 0x63, 0xc7, 0xa8, 0xc7, 0x03, 0xad,
+        0x8e, 0xc1,
+    ];
+
+    /// Build a sample `AuthorizationList` suitable for use as `sw_enforced`.
+    fn sw_enforced() -> AuthorizationList<'static> {
+        let sig = OctetStringRef::new(SIG).unwrap();
+        let package = PackageInfoRecord {
+            package_name: OctetStringRef::new(b"android.keystore.cts").unwrap(),
+            version: 34,
+        };
+        let mut package_info_records = SetOfVec::new();
+        package_info_records.insert(package).unwrap();
+        let mut signature_digests = SetOfVec::new();
+        signature_digests.insert(sig).unwrap();
+        let aaid = AttestationApplicationId { package_info_records, signature_digests };
+        AuthorizationList {
+            auths: vec![
+                KeyParameter { tag: Tag::CREATION_DATETIME, value: KPV::DateTime(0x01903116c71f) },
+                KeyParameter {
+                    tag: Tag::ATTESTATION_APPLICATION_ID,
+                    value: KPV::Blob(aaid.to_der().unwrap()),
+                },
+            ]
+            .into(),
+        }
+    }
+
+    /// Build a sample `AuthorizationList` suitable for use as `hw_enforced`.
+    fn hw_enforced() -> AuthorizationList<'static> {
+        let rot = RootOfTrust {
+            verified_boot_key: VB_KEY,
+            device_locked: false,
+            verified_boot_state: VerifiedBootState::Unverified,
+            verified_boot_hash: VB_HASH,
+        };
+        AuthorizationList {
+            auths: vec![
+                KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::AGREE_KEY) },
+                KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) },
+                KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(256) },
+                KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) },
+                KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::CURVE_25519) },
+                KeyParameter { tag: Tag::NO_AUTH_REQUIRED, value: KPV::BoolValue(true) },
+                KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) },
+                KeyParameter { tag: Tag::ROOT_OF_TRUST, value: KPV::Blob(rot.to_der().unwrap()) },
+                KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(140000) },
+                KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202404) },
+                KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20240405) },
+                KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(20240405) },
+            ]
+            .into(),
+        }
+    }
+
+    #[test]
+    fn test_decode_auth_list_1() {
+        let want = sw_enforced();
+        let data = hex::decode(concat!(
+            "3055",     //  SEQUENCE
+            "bf853d08", //  [701]
+            "0206",     //  INTEGER
+            "01903116c71f",
+            "bf854545",                                 //  [709]
+            "0443",                                     //  OCTET STRING
+            "3041",                                     //  SEQUENCE
+            "311b",                                     //  SET
+            "3019",                                     //  SEQUENCE
+            "0414",                                     //  OCTET STRING
+            "616e64726f69642e6b657973746f72652e637473", //  "android.keystore.cts"
+            "020122",                                   //  INTEGER
+            "3122",                                     //  SET
+            "0420",                                     //  OCTET STRING
+            "a40da80a59d170caa950cf15c18c454d",
+            "47a39b26989d8b640ecd745ba71bf5dc",
+        ))
+        .unwrap();
+        let got = AuthorizationList::from_der(&data).unwrap();
+        assert_eq!(got, want);
+    }
+
+    #[test]
+    fn test_decode_auth_list_2() {
+        let want = hw_enforced();
+        let data = hex::decode(concat!(
+            "3081a1",   //  SEQUENCE
+            "a105",     //  [1]
+            "3103",     //  SET
+            "020106",   //  INTEGER
+            "a203",     //  [2]
+            "020103",   //  INTEGER 3
+            "a304",     //  [4]
+            "02020100", //  INTEGER 256
+            "a505",     //  [5]
+            "3103",     //  SET
+            "020100",   //  INTEGER 0
+            "aa03",     //  [10]
+            "020104",   //  INTEGER 4
+            "bf837702", //  [503]
+            "0500",     //  NULL
+            "bf853e03", //  [702]
+            "020100",   //  INTEGER 0
+            "bf85404c", //  [704]
+            "304a",     //  SEQUENCE
+            "0420",     //  OCTET STRING
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "010100", //  BOOLEAN
+            "0a0102", //  ENUMERATED
+            "0420",   //  OCTET STRING
+            "6f84e602739d862c932a28f0a52765a4",
+            "aec2278cb63be9bb63c7a8c703ad8ec1",
+            "bf854105",     //  [705]
+            "02030222e0",   //  INTEGER
+            "bf854205",     //  [706]
+            "02030316a4",   //  INTEGER
+            "bf854e06",     //  [718]
+            "02040134d815", //  INTEGER
+            "bf854f06",     //  [709]
+            "02040134d815", //  INTEGER
+        ))
+        .unwrap();
+        let got = AuthorizationList::from_der(&data).unwrap();
+        assert_eq!(got, want);
+    }
+
+    #[test]
+    fn test_decode_extension() {
+        let zeroes = [0; 128];
+        let want = AttestationExtension {
+            attestation_version: 300,
+            attestation_security_level: SecurityLevel::TrustedEnvironment,
+            keymint_version: 300,
+            keymint_security_level: SecurityLevel::TrustedEnvironment,
+            attestation_challenge: &zeroes,
+            unique_id: &[],
+            sw_enforced: sw_enforced(),
+            hw_enforced: hw_enforced(),
+        };
+
+        let data = hex::decode(concat!(
+            // Full extension would include the following prefix:
+            // "308201a2",             //  SEQUENCE
+            // "060a",                 //  OBJECT IDENTIFIER
+            // "2b06010401d679020111", //  Android attestation extension (1.3.6.1.4.1.11129.2.1.17)
+            // "04820192",             //  OCTET STRING
+            "3082018e", //  SEQUENCE
+            "0202012c", //  INTEGER 300
+            "0a0101",   //  ENUMERATED 1
+            "0202012c", //  INTEGER 300
+            "0a0101",   //  ENUMERATED 1
+            "048180",   //  OCTET STRING
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "0400", //  OCTET STRING
+            // softwareEnforced
+            "3055",     //  SEQUENCE
+            "bf853d08", //  [701]
+            "0206",     //  INTEGER
+            "01903116c71f",
+            "bf854545",                                 //  [709]
+            "0443",                                     //  OCTET STRING
+            "3041",                                     //  SEQUENCE
+            "311b",                                     //  SET
+            "3019",                                     //  SEQUENCE
+            "0414",                                     //  OCTET STRING
+            "616e64726f69642e6b657973746f72652e637473", //  "android.keystore.cts"
+            "020122",                                   //  INTEGER
+            "3122",                                     //  SET
+            "0420",                                     //  OCTET STRING
+            "a40da80a59d170caa950cf15c18c454d",
+            "47a39b26989d8b640ecd745ba71bf5dc",
+            // softwareEnforced
+            "3081a1",   //  SEQUENCE
+            "a105",     //  [1]
+            "3103",     //  SET
+            "020106",   //  INTEGER
+            "a203",     //  [2]
+            "020103",   //  INTEGER 3
+            "a304",     //  [4]
+            "02020100", //  INTEGER 256
+            "a505",     //  [5]
+            "3103",     //  SET
+            "020100",   //  INTEGER 0
+            "aa03",     //  [10]
+            "020104",   //  INTEGER 4
+            "bf837702", //  [503]
+            "0500",     //  NULL
+            "bf853e03", //  [702]
+            "020100",   //  INTEGER 0
+            "bf85404c", //  [704]
+            "304a",     //  SEQUENCE
+            "0420",     //  OCTET STRING
+            "00000000000000000000000000000000",
+            "00000000000000000000000000000000",
+            "010100", //  BOOLEAN
+            "0a0102", //  ENUMERATED
+            "0420",   //  OCTET STRING
+            "6f84e602739d862c932a28f0a52765a4",
+            "aec2278cb63be9bb63c7a8c703ad8ec1",
+            "bf854105",     //  [705]
+            "02030222e0",   //  INTEGER
+            "bf854205",     //  [706]
+            "02030316a4",   //  INTEGER
+            "bf854e06",     //  [718]
+            "02040134d815", //  INTEGER
+            "bf854f06",     //  [719]
+            "02040134d815", //  INTEGER
+        ))
+        .unwrap();
+        let got = AttestationExtension::from_der(&data).unwrap();
+        assert_eq!(got, want);
+    }
+
+    #[test]
+    fn test_decode_empty_auth_list() {
+        let want = AuthorizationList::default();
+        let data = hex::decode(
+            "3000", //  SEQUENCE
+        )
+        .unwrap();
+        let got = AuthorizationList::from_der(&data).unwrap();
+        assert_eq!(got, want);
+    }
+
+    #[test]
+    fn test_decode_explicit_tag() {
+        let err = Err(der::ErrorKind::TagNumberInvalid.into());
+        let tests = [
+            (vec![], Ok(None)),
+            (vec![0b10100000], Ok(Some(0))),
+            (vec![0b10100001], Ok(Some(1))),
+            (vec![0b10100010], Ok(Some(2))),
+            (vec![0b10111110], Ok(Some(30))),
+            (vec![0b10111111, 0b00011111], Ok(Some(31))),
+            (vec![0b10111111, 0b00100000], Ok(Some(32))),
+            (vec![0b10111111, 0b01111111], Ok(Some(127))),
+            (vec![0b10111111, 0b10000001, 0b00000000], Ok(Some(128))),
+            (vec![0b10111111, 0b10000010, 0b00000000], Ok(Some(256))),
+            (vec![0b10111111, 0b10000001, 0b10000000, 0b00000001], Ok(Some(16385))),
+            (vec![0b10111111, 0b10010000, 0b10000000, 0b10000000, 0b00000000], Ok(Some(33554432))),
+            // Top bits ignored for low tag numbers
+            (vec![0b00000000], Ok(Some(0))),
+            (vec![0b00000001], Ok(Some(1))),
+            // High tag numbers should start with 0b101
+            (vec![0b10011111, 0b00100000], err),
+            (vec![0b11111111, 0b00100000], err),
+            (vec![0b00111111, 0b00100000], err),
+            // High tag numbers should be minimally encoded
+            (vec![0b10111111, 0b10000000, 0b10000001, 0b00000000], err),
+            (vec![0b10111111, 0b00011110], err),
+            // Bigger than u32
+            (
+                vec![
+                    0b10111111, 0b10000001, 0b10000000, 0b10000000, 0b10000000, 0b10000000,
+                    0b00000000,
+                ],
+                err,
+            ),
+            // Incomplete tag
+            (vec![0b10111111, 0b10000001], Err(der::Error::incomplete(der::Length::new(2)))),
+        ];
+
+        for (input, want) in tests {
+            let mut reader = SliceReader::new(&input).unwrap();
+            let got = decode_explicit_tag_from_bytes(&mut reader);
+            assert_eq!(got, want, "for input {}", hex::encode(input));
+        }
+    }
+}
diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs
index a96d994..d3d6fc4 100644
--- a/keystore2/test_utils/authorizations.rs
+++ b/keystore2/test_utils/authorizations.rs
@@ -18,8 +18,9 @@
 
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
-    KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose,
-    PaddingMode::PaddingMode, Tag::Tag,
+    HardwareAuthenticatorType::HardwareAuthenticatorType, KeyParameter::KeyParameter,
+    KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
+    Tag::Tag,
 };
 
 /// Helper struct to create set of Authorizations.
@@ -369,6 +370,33 @@
         });
         self
     }
+
+    /// Set user secure ID.
+    pub fn user_secure_id(mut self, sid: i64) -> Self {
+        self.0.push(KeyParameter {
+            tag: Tag::USER_SECURE_ID,
+            value: KeyParameterValue::LongInteger(sid),
+        });
+        self
+    }
+
+    /// Set user auth type.
+    pub fn user_auth_type(mut self, auth_type: HardwareAuthenticatorType) -> Self {
+        self.0.push(KeyParameter {
+            tag: Tag::USER_AUTH_TYPE,
+            value: KeyParameterValue::HardwareAuthenticatorType(auth_type),
+        });
+        self
+    }
+
+    /// Set auth timeout.
+    pub fn auth_timeout(mut self, timeout_secs: i32) -> Self {
+        self.0.push(KeyParameter {
+            tag: Tag::AUTH_TIMEOUT,
+            value: KeyParameterValue::Integer(timeout_secs),
+        });
+        self
+    }
 }
 
 impl Deref for AuthSetBuilder {
diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs
index fdb2afb..98b227b 100644
--- a/keystore2/test_utils/key_generations.rs
+++ b/keystore2/test_utils/key_generations.rs
@@ -38,6 +38,7 @@
 use nix::unistd::getuid;
 use std::collections::HashSet;
 use std::fmt::Write;
+use std::path::PathBuf;
 
 /// Shell namespace.
 pub const SELINUX_SHELL_NAMESPACE: i64 = 1;
@@ -50,6 +51,10 @@
 /// Vold context
 pub const TARGET_VOLD_CTX: &str = "u:r:vold:s0";
 
+const TEE_KEYMINT_RKP_ONLY: &str = "remote_provisioning.tee.rkp_only";
+
+const STRONGBOX_KEYMINT_RKP_ONLY: &str = "remote_provisioning.strongbox.rkp_only";
+
 /// Allowed tags in generated/imported key authorizations.
 /// See hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl for the
 /// list feature tags.
@@ -387,6 +392,57 @@
     })
 }
 
+/// Check for a specific KeyMint error.
+#[macro_export]
+macro_rules! expect_km_error {
+    { $result:expr, $want:expr } => {
+        match $result {
+            Ok(_) => return Err(format!(
+                "{}:{}: Expected KeyMint error {:?}, found success",
+                file!(),
+                line!(),
+                $want
+            ).into()),
+            Err(s) if s.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+                    && s.service_specific_error() == $want.0 => {}
+            Err(e) => return Err(format!(
+                "{}:{}: Expected KeyMint service-specific error {:?}, got {e:?}",
+                file!(),
+                line!(),
+                $want
+            ).into()),
+        }
+
+    };
+}
+
+/// Get the value of the given system property, if the given system property doesn't exist
+/// then returns an empty byte vector.
+pub fn get_system_prop(name: &str) -> Vec<u8> {
+    match rustutils::system_properties::read(name) {
+        Ok(Some(value)) => value.as_bytes().to_vec(),
+        _ => vec![],
+    }
+}
+
+/// Determines whether test is running on GSI.
+pub fn is_gsi() -> bool {
+    // This file is only present on GSI builds.
+    PathBuf::from("/system/system_ext/etc/init/init.gsi.rc").as_path().is_file()
+}
+
+/// Determines whether the test is on a GSI build where the rkp-only status of the device is
+/// unknown. GSI replaces the values for remote_prov_prop properties (since they’re
+/// system_internal_prop properties), so on GSI the properties are not reliable indicators of
+/// whether StrongBox/TEE is RKP-only or not.
+pub fn is_rkp_only_unknown_on_gsi(sec_level: SecurityLevel) -> bool {
+    if sec_level == SecurityLevel::TRUSTED_ENVIRONMENT {
+        is_gsi() && get_system_prop(TEE_KEYMINT_RKP_ONLY).is_empty()
+    } else {
+        is_gsi() && get_system_prop(STRONGBOX_KEYMINT_RKP_ONLY).is_empty()
+    }
+}
+
 /// Verify that given key param is listed in given authorizations list.
 pub fn check_key_param(authorizations: &[Authorization], key_param: &KeyParameter) -> bool {
     authorizations.iter().any(|auth| &auth.keyParameter == key_param)
@@ -401,9 +457,8 @@
 ) {
     // Make sure key authorizations contains only `ALLOWED_TAGS_IN_KEY_AUTHS`
     authorizations.iter().all(|auth| {
-        // Ignore `INVALID` tag if the backend is Keymaster and not KeyMint.
-        // Keymaster allows INVALID tag for unsupported key parameters.
-        if sl.is_keymaster() && auth.keyParameter.tag == Tag::INVALID {
+        // Ignore `INVALID` tag
+        if auth.keyParameter.tag == Tag::INVALID {
             return true;
         }
         assert!(
@@ -424,13 +479,30 @@
             return true;
         }
 
-        // Ignore below parameters if the backend is Keymaster and not KeyMint.
-        // Keymaster does not support these parameters. These key parameters are introduced in
-        // KeyMint1.0.
+        // `Tag::RSA_OAEP_MGF_DIGEST` was added in KeyMint 1.0, but the KeyMint VTS tests didn't
+        // originally check for its presence and so some implementations of early versions (< 3) of
+        // the KeyMint HAL don't include it (cf. b/297306437 and aosp/2758513).
+        //
+        // Given that Keymaster implementations will also omit this tag, skip the check for it
+        // altogether (and rely on the updated KeyMint VTS tests to ensure that up-level KeyMint
+        // implementations correctly populate this tag).
+        if matches!(key_param.tag, Tag::RSA_OAEP_MGF_DIGEST) {
+            return true;
+        }
+
+        // Don't check these parameters if the underlying device is a Keymaster implementation.
         if sl.is_keymaster() {
-            if matches!(key_param.tag, Tag::RSA_OAEP_MGF_DIGEST | Tag::USAGE_COUNT_LIMIT) {
+            if matches!(
+                key_param.tag,
+                // `Tag::USAGE_COUNT_LIMIT` was added in KeyMint 1.0.
+                Tag::USAGE_COUNT_LIMIT |
+                // Keymaster implementations may not consistently include `Tag::VENDOR_PATCHLEVEL`
+                // in generated key characteristics.
+                Tag::VENDOR_PATCHLEVEL
+            ) {
                 return true;
             }
+            // `KeyPurpose::ATTEST_KEY` was added in KeyMint 1.0.
             if key_param.tag == Tag::PURPOSE
                 && key_param.value == KeyParameterValue::KeyPurpose(KeyPurpose::ATTEST_KEY)
             {
@@ -464,26 +536,28 @@
             value: KeyParameterValue::Integer(get_os_version().try_into().unwrap())
         }
     ));
-    assert!(check_key_param(
-        authorizations,
-        &KeyParameter {
-            tag: Tag::OS_PATCHLEVEL,
-            value: KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())
-        }
-    ));
-
-    // Access denied for finding vendor-patch-level ("ro.vendor.build.security_patch") property
-    // in a test running with `untrusted_app` context. Keeping this check to verify
-    // vendor-patch-level in tests running with `su` context.
-    if getuid().is_root() {
+    if is_gsi() && sl.is_keymaster() {
+        // The expected value of TAG::OS_PATCHLEVEL should match the system's reported
+        // OS patch level (obtained via get_os_patchlevel()). However, booting a Generic System
+        // Image (GSI) with a newer patch level is permitted. Therefore, the generated key's
+        // TAG::OS_PATCHLEVEL may be less than or equal to the current system's OS patch level.
+        assert!(authorizations.iter().map(|auth| &auth.keyParameter).any(|key_param| key_param
+            .tag
+            == Tag::OS_PATCHLEVEL
+            && key_param.value
+                <= KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())));
+    } else {
+        // The KeyMint spec required that the patch-levels match that of the running system, even
+        // under GSI.
         assert!(check_key_param(
             authorizations,
             &KeyParameter {
-                tag: Tag::VENDOR_PATCHLEVEL,
-                value: KeyParameterValue::Integer(get_vendor_patchlevel().try_into().unwrap())
+                tag: Tag::OS_PATCHLEVEL,
+                value: KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())
             }
         ));
     }
+
     assert!(check_key_param(
         authorizations,
         &KeyParameter { tag: Tag::ORIGIN, value: KeyParameterValue::Origin(expected_key_origin) }
@@ -505,6 +579,22 @@
             .iter()
             .map(|auth| &auth.keyParameter)
             .any(|key_param| key_param.tag == Tag::CREATION_DATETIME));
+
+        // Access denied for finding vendor-patch-level ("ro.vendor.build.security_patch") property
+        // in a test running with `untrusted_app` context. Keeping this check to verify
+        // vendor-patch-level in tests running with `su` context.
+        if getuid().is_root() {
+            // Keymaster implementations may not consistently include `Tag::VENDOR_PATCHLEVEL`
+            // in generated key characteristics. So, checking this if the underlying device is a
+            // KeyMint implementation.
+            assert!(check_key_param(
+                authorizations,
+                &KeyParameter {
+                    tag: Tag::VENDOR_PATCHLEVEL,
+                    value: KeyParameterValue::Integer(get_vendor_patchlevel().try_into().unwrap())
+                }
+            ));
+        }
     }
 }
 
@@ -623,7 +713,7 @@
     alias: Option<String>,
     key_params: &KeyParams,
     attest_key: Option<&KeyDescriptor>,
-) -> binder::Result<KeyMetadata> {
+) -> binder::Result<Option<KeyMetadata>> {
     let mut gen_params = AuthSetBuilder::new()
         .no_auth_required()
         .algorithm(Algorithm::RSA)
@@ -649,13 +739,29 @@
         gen_params = gen_params.attestation_challenge(value.to_vec())
     }
 
-    let key_metadata = sl.binder.generateKey(
+    let key_metadata = match sl.binder.generateKey(
         &KeyDescriptor { domain, nspace, alias, blob: None },
         attest_key,
         &gen_params,
         0,
         b"entropy",
-    )?;
+    ) {
+        Ok(metadata) => metadata,
+        Err(e) => {
+            return if is_rkp_only_unknown_on_gsi(sl.level)
+                && e.service_specific_error() == ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED.0
+            {
+                // GSI replaces the values for remote_prov_prop properties (since they’re
+                // system_internal_prop properties), so on GSI the properties are not
+                // reliable indicators of whether StrongBox/TEE are RKP-only or not.
+                // Test can be skipped if it generates a key with attestation but doesn't provide
+                // an ATTEST_KEY and rkp-only property is undetermined.
+                Ok(None)
+            } else {
+                Err(e)
+            };
+        }
+    };
 
     // Must have a public key.
     assert!(key_metadata.certificate.is_some());
@@ -686,7 +792,7 @@
             }
         ));
     }
-    Ok(key_metadata)
+    Ok(Some(key_metadata))
 }
 
 /// Generate AES/3DES key.
@@ -784,12 +890,12 @@
     sl: &SecLevel,
     algorithm: Algorithm,
     att_challenge: &[u8],
-) -> binder::Result<KeyMetadata> {
+) -> binder::Result<Option<KeyMetadata>> {
     assert!(algorithm == Algorithm::RSA || algorithm == Algorithm::EC);
 
     if algorithm == Algorithm::RSA {
         let alias = "ks_rsa_attest_test_key";
-        let metadata = generate_rsa_key(
+        generate_rsa_key(
             sl,
             Domain::APP,
             -1,
@@ -805,14 +911,8 @@
             },
             None,
         )
-        .unwrap();
-        Ok(metadata)
     } else {
-        let metadata =
-            generate_ec_attestation_key(sl, att_challenge, Digest::SHA_2_256, EcCurve::P_256)
-                .unwrap();
-
-        Ok(metadata)
+        generate_ec_attestation_key(sl, att_challenge, Digest::SHA_2_256, EcCurve::P_256)
     }
 }
 
@@ -823,7 +923,7 @@
     att_challenge: &[u8],
     digest: Digest,
     ec_curve: EcCurve,
-) -> binder::Result<KeyMetadata> {
+) -> binder::Result<Option<KeyMetadata>> {
     let alias = "ks_attest_ec_test_key";
     let gen_params = AuthSetBuilder::new()
         .no_auth_required()
@@ -833,7 +933,7 @@
         .digest(digest)
         .attestation_challenge(att_challenge.to_vec());
 
-    let attestation_key_metadata = sl.binder.generateKey(
+    let attestation_key_metadata = match sl.binder.generateKey(
         &KeyDescriptor {
             domain: Domain::APP,
             nspace: -1,
@@ -844,7 +944,23 @@
         &gen_params,
         0,
         b"entropy",
-    )?;
+    ) {
+        Ok(metadata) => metadata,
+        Err(e) => {
+            return if is_rkp_only_unknown_on_gsi(sl.level)
+                && e.service_specific_error() == ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED.0
+            {
+                // GSI replaces the values for remote_prov_prop properties (since they’re
+                // system_internal_prop properties), so on GSI the properties are not
+                // reliable indicators of whether StrongBox/TEE are RKP-only or not.
+                // Test can be skipped if it generates a key with attestation but doesn't provide
+                // an ATTEST_KEY and rkp-only property is undetermined.
+                Ok(None)
+            } else {
+                Err(e)
+            };
+        }
+    };
 
     // Should have public certificate.
     assert!(attestation_key_metadata.certificate.is_some());
@@ -857,7 +973,7 @@
         &gen_params,
         KeyOrigin::GENERATED,
     );
-    Ok(attestation_key_metadata)
+    Ok(Some(attestation_key_metadata))
 }
 
 /// Generate EC-P-256 key and attest it with given attestation key.
@@ -1421,8 +1537,8 @@
     sl: &SecLevel,
     gen_params: &AuthSetBuilder,
     alias: &str,
-) -> binder::Result<KeyMetadata> {
-    let key_metadata = sl.binder.generateKey(
+) -> binder::Result<Option<KeyMetadata>> {
+    let key_metadata = match sl.binder.generateKey(
         &KeyDescriptor {
             domain: Domain::APP,
             nspace: -1,
@@ -1433,7 +1549,23 @@
         gen_params,
         0,
         b"entropy",
-    )?;
+    ) {
+        Ok(metadata) => metadata,
+        Err(e) => {
+            return if is_rkp_only_unknown_on_gsi(sl.level)
+                && e.service_specific_error() == ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED.0
+            {
+                // GSI replaces the values for remote_prov_prop properties (since they’re
+                // system_internal_prop properties), so on GSI the properties are not
+                // reliable indicators of whether StrongBox/TEE are RKP-only or not.
+                // Test can be skipped if it generates a key with attestation but doesn't provide
+                // an ATTEST_KEY and rkp-only property is undetermined.
+                Ok(None)
+            } else {
+                Err(e)
+            };
+        }
+    };
 
     if gen_params.iter().any(|kp| {
         matches!(
@@ -1478,7 +1610,7 @@
     }
     check_key_authorizations(sl, &key_metadata.authorizations, gen_params, KeyOrigin::GENERATED);
 
-    Ok(key_metadata)
+    Ok(Some(key_metadata))
 }
 
 /// Generate a key using given authorizations and create an operation using the generated key.
@@ -1487,8 +1619,10 @@
     gen_params: &AuthSetBuilder,
     op_params: &AuthSetBuilder,
     alias: &str,
-) -> binder::Result<CreateOperationResponse> {
-    let key_metadata = generate_key(sl, gen_params, alias)?;
+) -> binder::Result<Option<CreateOperationResponse>> {
+    let Some(key_metadata) = generate_key(sl, gen_params, alias)? else {
+        return Ok(None);
+    };
 
-    sl.binder.createOperation(&key_metadata.key, op_params, false)
+    sl.binder.createOperation(&key_metadata.key, op_params, false).map(Some)
 }
diff --git a/keystore2/test_utils/lib.rs b/keystore2/test_utils/lib.rs
index 8b766dd..8e74f92 100644
--- a/keystore2/test_utils/lib.rs
+++ b/keystore2/test_utils/lib.rs
@@ -21,10 +21,10 @@
 
 use android_system_keystore2::aidl::android::system::keystore2::{
     IKeystoreService::IKeystoreService,
-    IKeystoreSecurityLevel::IKeystoreSecurityLevel,
+    IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-    ErrorCode::ErrorCode, SecurityLevel::SecurityLevel,
+    ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel,
 };
 use android_security_authorization::aidl::android::security::authorization::IKeystoreAuthorization::IKeystoreAuthorization;
 
@@ -176,4 +176,35 @@
     pub fn is_keymaster(&self) -> bool {
         !self.is_keymint()
     }
+
+    /// Get KeyMint version.
+    /// Returns 0 if the underlying device is Keymaster not KeyMint.
+    pub fn get_keymint_version(&self) -> i32 {
+        let instance = match self.level {
+            SecurityLevel::TRUSTED_ENVIRONMENT => "default",
+            SecurityLevel::STRONGBOX => "strongbox",
+            l => panic!("unexpected level {l:?}"),
+        };
+        let name = format!("android.hardware.security.keymint.IKeyMintDevice/{instance}");
+        if binder::is_declared(&name).expect("Could not check for declared keymint interface") {
+            let km: binder::Strong<dyn IKeyMintDevice> = binder::get_interface(&name).unwrap();
+            km.getInterfaceVersion().unwrap()
+        } else {
+            0
+        }
+    }
+
+    /// Delete a key.
+    pub fn delete_key(&self, key: &KeyDescriptor) -> binder::Result<()> {
+        match self.binder.deleteKey(key) {
+            Ok(()) => Ok(()),
+            Err(s)
+                if s.exception_code() == binder::ExceptionCode::SERVICE_SPECIFIC
+                    && s.service_specific_error() == ErrorCode::UNIMPLEMENTED.0 =>
+            {
+                Ok(())
+            }
+            Err(e) => Err(e),
+        }
+    }
 }
diff --git a/keystore2/test_utils/run_as.rs b/keystore2/test_utils/run_as.rs
index d39d069..7a9acb7 100644
--- a/keystore2/test_utils/run_as.rs
+++ b/keystore2/test_utils/run_as.rs
@@ -32,12 +32,104 @@
     fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write, ForkResult, Gid,
     Pid, Uid,
 };
-use serde::{de::DeserializeOwned, Serialize};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::io::{Read, Write};
 use std::marker::PhantomData;
 use std::os::fd::AsRawFd;
 use std::os::fd::OwnedFd;
 
+/// Newtype string error, which can be serialized and transferred out from a sub-process.
+#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
+pub struct Error(pub String);
+
+/// Allow ergonomic use of [`anyhow::Error`].
+impl From<anyhow::Error> for Error {
+    fn from(err: anyhow::Error) -> Self {
+        // Use the debug format of [`anyhow::Error`] to include backtrace.
+        Self(format!("{:?}", err))
+    }
+}
+impl From<String> for Error {
+    fn from(val: String) -> Self {
+        Self(val)
+    }
+}
+impl From<&str> for Error {
+    fn from(val: &str) -> Self {
+        Self(val.to_string())
+    }
+}
+
+impl std::fmt::Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl std::error::Error for Error {}
+
+/// Equivalent to the [`assert!`] macro which returns an [`Error`] rather than emitting a panic.
+/// This is useful for test code that is `run_as`, so failures are more accessible.
+#[macro_export]
+macro_rules! expect {
+    ($cond:expr $(,)?) => {{
+        let result = $cond;
+        if !result {
+            return Err($crate::run_as::Error(format!(
+                "{}:{}: check '{}' failed",
+                file!(),
+                line!(),
+                stringify!($cond)
+            )));
+        }
+    }};
+    ($cond:expr, $($arg:tt)+) => {{
+        let result = $cond;
+        if !result {
+            return Err($crate::run_as::Error(format!(
+                "{}:{}: check '{}' failed: {}",
+                file!(),
+                line!(),
+                stringify!($cond),
+                format_args!($($arg)+)
+            )));
+        }
+    }};
+}
+
+/// Equivalent to the [`assert_eq!`] macro which returns an [`Error`] rather than emitting a panic.
+/// This is useful for test code that is `run_as`, so failures are more accessible.
+#[macro_export]
+macro_rules! expect_eq {
+    ($left:expr, $right:expr $(,)?) => {{
+        let left = $left;
+        let right = $right;
+        if left != right {
+            return Err($crate::run_as::Error(format!(
+                "{}:{}: assertion {} == {} failed\n  left: {left:?}\n right: {right:?}\n",
+                file!(),
+                line!(),
+                stringify!($left),
+                stringify!($right),
+            )));
+        }
+    }};
+    ($left:expr, $right:expr, $($arg:tt)+) => {{
+        let left = $left;
+        let right = $right;
+        if left != right {
+            return Err($crate::run_as::Error(format!(
+                "{}:{}: assertion {} == {} failed: {}\n  left: {left:?}\n right: {right:?}\n",
+                file!(),
+                line!(),
+                stringify!($left),
+                stringify!($right),
+                format_args!($($arg)+)
+            )));
+        }
+    }};
+}
+
 fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) {
     setgid(gid).expect("Failed to set GID. This test might need more privileges.");
     setuid(uid).expect("Failed to set UID. This test might need more privileges.");
@@ -119,31 +211,48 @@
     /// Receiving blocks until an object of type T has been read from the channel.
     /// Panics if an error occurs during io or deserialization.
     pub fn recv(&mut self) -> T {
+        match self.recv_err() {
+            Ok(val) => val,
+            Err(e) => panic!("{e}"),
+        }
+    }
+
+    /// Receives a serializable object from the corresponding ChannelWriter.
+    /// Receiving blocks until an object of type T has been read from the channel.
+    pub fn recv_err(&mut self) -> Result<T, Error> {
         let mut size_buffer = [0u8; std::mem::size_of::<usize>()];
         match self.0.read(&mut size_buffer).expect("In ChannelReader::recv: Failed to read size.") {
             r if r != size_buffer.len() => {
-                panic!("In ChannelReader::recv: Failed to read size. Insufficient data: {}", r);
+                return Err(format!(
+                    "In ChannelReader::recv: Failed to read size. Insufficient data: {}",
+                    r
+                )
+                .into());
             }
             _ => {}
         };
         let size = usize::from_be_bytes(size_buffer);
         let mut data_buffer = vec![0u8; size];
-        match self
-            .0
-            .read(&mut data_buffer)
-            .expect("In ChannelReader::recv: Failed to read serialized data.")
-        {
-            r if r != data_buffer.len() => {
-                panic!(
+        match self.0.read(&mut data_buffer) {
+            Ok(r) if r != data_buffer.len() => {
+                return Err(format!(
                     "In ChannelReader::recv: Failed to read serialized data. Insufficient data: {}",
                     r
-                );
+                )
+                .into());
             }
-            _ => {}
+            Ok(_) => {}
+            Err(e) => {
+                return Err(format!(
+                    "In ChannelReader::recv: Failed to read serialized data: {e:?}"
+                )
+                .into())
+            }
         };
 
-        serde_cbor::from_slice(&data_buffer)
-            .expect("In ChannelReader::recv: Failed to deserialize data.")
+        serde_cbor::from_slice(&data_buffer).map_err(|e| {
+            format!("In ChannelReader::recv: Failed to deserialize data: {e:?}").into()
+        })
     }
 }
 
@@ -186,6 +295,11 @@
     /// Get child result. Panics if the child did not exit with status 0 or if a serialization
     /// error occurred.
     pub fn get_result(mut self) -> R {
+        self.get_death_result()
+    }
+
+    /// Get child result via a mutable reference.
+    fn get_death_result(&mut self) -> R {
         let status =
             waitpid(self.pid, None).expect("ChildHandle::wait: Failed while waiting for child.");
         match status {
@@ -205,6 +319,31 @@
     }
 }
 
+impl<R, M> ChildHandle<Result<R, Error>, M>
+where
+    R: Serialize + DeserializeOwned,
+    M: Serialize + DeserializeOwned,
+{
+    /// Receive a response from the child.  If the child has closed the response
+    /// channel, assume it has terminated and read the final result.
+    /// Panics on child failure, but will display the child error value.
+    pub fn recv_or_die(&mut self) -> M {
+        match self.response_reader.recv_err() {
+            Ok(v) => v,
+            Err(_e) => {
+                // We have failed to read from the `response_reader` channel.
+                // Assume this is because the child completed early with an error.
+                match self.get_death_result() {
+                    Ok(_) => {
+                        panic!("Child completed OK despite failure to read a response!")
+                    }
+                    Err(e) => panic!("Child failed with:\n{e}"),
+                }
+            }
+        }
+    }
+}
+
 impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> Drop for ChildHandle<R, M> {
     fn drop(&mut self) {
         if self.exit_status.is_none() {
@@ -213,6 +352,40 @@
     }
 }
 
+/// Run the given closure in a new process running as an untrusted app with the given `uid` and
+/// `gid`. Parent process will run without waiting for child status.
+///
+/// # Safety
+/// run_as_child runs the given closure in the client branch of fork. And it uses non
+/// async signal safe API. This means that calling this function in a multi threaded program
+/// yields undefined behavior in the child. As of this writing, it is safe to call this function
+/// from a Rust device test, because every test itself is spawned as a separate process.
+///
+/// # Safety Binder
+/// It is okay for the closure to use binder services, however, this does not work
+/// if the parent initialized libbinder already. So do not use binder outside of the closure
+/// in your test.
+pub unsafe fn run_as_child_app<F, R, M>(
+    uid: u32,
+    gid: u32,
+    f: F,
+) -> Result<ChildHandle<R, M>, nix::Error>
+where
+    R: Serialize + DeserializeOwned,
+    M: Serialize + DeserializeOwned,
+    F: 'static + Send + FnOnce(&mut ChannelReader<M>, &mut ChannelWriter<M>) -> R,
+{
+    // Safety: Caller guarantees that the process only has a single thread.
+    unsafe {
+        run_as_child(
+            "u:r:untrusted_app:s0:c91,c256,c10,c20",
+            Uid::from_raw(uid),
+            Gid::from_raw(gid),
+            f,
+        )
+    }
+}
+
 /// Run the given closure in a new process running with the new identity given as
 /// `uid`, `gid`, and `se_context`. Parent process will run without waiting for child status.
 ///
@@ -244,7 +417,7 @@
     let (response_reader, mut response_writer) =
         pipe_channel().expect("Failed to create cmd pipe.");
 
-    // SAFETY: Our caller guarantees that the process only has a single thread, so calling
+    // Safety: Our caller guarantees that the process only has a single thread, so calling
     // non-async-signal-safe functions in the child is in fact safe.
     match unsafe { fork() } {
         Ok(ForkResult::Parent { child, .. }) => {
@@ -283,6 +456,50 @@
     }
 }
 
+/// Run the given closure in a new process running with the root identity.
+///
+/// # Safety
+/// run_as runs the given closure in the client branch of fork. And it uses non
+/// async signal safe API. This means that calling this function in a multi threaded program
+/// yields undefined behavior in the child. As of this writing, it is safe to call this function
+/// from a Rust device test, because every test itself is spawned as a separate process.
+///
+/// # Safety Binder
+/// It is okay for the closure to use binder services, however, this does not work
+/// if the parent initialized libbinder already. So do not use binder outside of the closure
+/// in your test.
+pub unsafe fn run_as_root<F, R>(f: F) -> R
+where
+    R: Serialize + DeserializeOwned,
+    F: 'static + Send + FnOnce() -> R,
+{
+    // SAFETY: Our caller guarantees that the process only has a single thread.
+    unsafe { run_as("u:r:su:s0", Uid::from_raw(0), Gid::from_raw(0), f) }
+}
+
+/// Run the given closure in a new `untrusted_app` process running with the given `uid` and `gid`.
+///
+/// # Safety
+/// run_as runs the given closure in the client branch of fork. And it uses non
+/// async signal safe API. This means that calling this function in a multi threaded program
+/// yields undefined behavior in the child. As of this writing, it is safe to call this function
+/// from a Rust device test, because every test itself is spawned as a separate process.
+///
+/// # Safety Binder
+/// It is okay for the closure to use binder services, however, this does not work
+/// if the parent initialized libbinder already. So do not use binder outside of the closure
+/// in your test.
+pub unsafe fn run_as_app<F, R>(uid: u32, gid: u32, f: F) -> R
+where
+    R: Serialize + DeserializeOwned,
+    F: 'static + Send + FnOnce() -> R,
+{
+    // SAFETY: Our caller guarantees that the process only has a single thread.
+    unsafe {
+        run_as("u:r:untrusted_app:s0:c91,c256,c10,c20", Uid::from_raw(uid), Gid::from_raw(gid), f)
+    }
+}
+
 /// Run the given closure in a new process running with the new identity given as
 /// `uid`, `gid`, and `se_context`.
 ///
@@ -357,9 +574,12 @@
         // Safety: run_as must be called from a single threaded process.
         // This device test is run as a separate single threaded process.
         unsafe {
-            run_as(selinux::getcon().unwrap().to_str().unwrap(), getuid(), getgid(), || {
-                panic!("Closure must panic.")
-            })
+            run_as::<_, ()>(
+                selinux::getcon().unwrap().to_str().unwrap(),
+                getuid(),
+                getgid(),
+                || panic!("Closure must panic."),
+            )
         };
     }
 
diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp
index ca9f5e3..8ec5238 100644
--- a/keystore2/tests/Android.bp
+++ b/keystore2/tests/Android.bp
@@ -24,32 +24,45 @@
 
 rust_test {
     name: "keystore2_client_tests",
-    compile_multilib: "first",
     defaults: [
         "keymint_use_latest_hal_aidl_rust",
         "keystore2_use_latest_aidl_rust",
     ],
+    static_libs: [
+        // Also include static_libs for the NDK variants so that they are available
+        // for dependencies.
+        "android.system.keystore2-V5-ndk",
+        "android.hardware.security.keymint-V4-ndk",
+    ],
     srcs: ["keystore2_client_tests.rs"],
     test_suites: [
+        "automotive-sdv-tests",
         "general-tests",
         "vts",
     ],
     test_config: "AndroidTest.xml",
 
     rustlibs: [
+        "android.hardware.gatekeeper-V1-rust",
         "android.hardware.security.secureclock-V1-rust",
         "android.security.authorization-rust",
         "android.security.maintenance-rust",
         "libaconfig_android_hardware_biometrics_rust",
         "libandroid_logger",
         "libandroid_security_flags_rust",
+        "libanyhow",
         "libbinder_rs",
+        "libbssl_crypto",
+        "libkeystore_attestation",
         "libkeystore2_test_utils",
+        "libhex",
         "liblog_rust",
+        "libkeystore2_flags_rust",
         "libnix",
         "libopenssl",
         "librustutils",
         "libserde",
+        "libx509_cert",
         "packagemanager_aidl-rust",
     ],
     require_root: true,
diff --git a/keystore2/tests/AndroidTest.xml b/keystore2/tests/AndroidTest.xml
index dde18a9..7db36f7 100644
--- a/keystore2/tests/AndroidTest.xml
+++ b/keystore2/tests/AndroidTest.xml
@@ -14,7 +14,6 @@
      limitations under the License.
 -->
 <configuration description="Config to run keystore2_client_tests device tests.">
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
 
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
     </target_preparer>
diff --git a/keystore2/tests/keystore2_client_3des_key_tests.rs b/keystore2/tests/keystore2_client_3des_key_tests.rs
index 29f1617..4cb81d1 100644
--- a/keystore2/tests/keystore2_client_3des_key_tests.rs
+++ b/keystore2/tests/keystore2_client_3des_key_tests.rs
@@ -72,6 +72,7 @@
 /// Generate 3DES keys with various block modes and paddings.
 ///  - Block Modes: ECB, CBC
 ///  - Padding Modes: NONE, PKCS7
+///
 /// Test should generate keys and perform operation successfully.
 #[test]
 fn keystore2_3des_ecb_cbc_generate_key_success() {
diff --git a/keystore2/tests/keystore2_client_aes_key_tests.rs b/keystore2/tests/keystore2_client_aes_key_tests.rs
index 9f85c38..7128911 100644
--- a/keystore2/tests/keystore2_client_aes_key_tests.rs
+++ b/keystore2/tests/keystore2_client_aes_key_tests.rs
@@ -75,6 +75,7 @@
 /// Generate AES keys with various block modes and paddings.
 ///  - Block Modes: ECB, CBC
 ///  - Padding Modes: NONE, PKCS7
+///
 /// Test should generate keys and perform operation successfully.
 #[test]
 fn keystore2_aes_ecb_cbc_generate_key() {
@@ -106,6 +107,7 @@
 /// Generate AES keys with -
 ///  - Block Modes: `CTR, GCM`
 ///  - Padding Modes: `NONE`
+///
 /// Test should generate keys and perform operation successfully.
 #[test]
 fn keystore2_aes_ctr_gcm_generate_key_success() {
@@ -133,6 +135,7 @@
 /// Generate AES keys with -
 ///  - Block Modes: `CTR, GCM`
 ///  - Padding Modes: `PKCS7`
+///
 /// Try to create an operation using generated keys, test should fail to create an operation
 /// with an error code `INCOMPATIBLE_PADDING_MODE`.
 #[test]
@@ -200,7 +203,7 @@
 }
 
 /// Try to create an operation using AES key with multiple block modes. Test should fail to create
-/// an operation with `UNSUPPORTED_BLOCK_MODE` error code.
+/// an operation.
 #[test]
 fn keystore2_aes_key_op_fails_multi_block_modes() {
     let sl = SecLevel::tee();
@@ -244,11 +247,16 @@
         false,
     ));
     assert!(result.is_err());
-    assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_BLOCK_MODE), result.unwrap_err());
+    assert!(matches!(
+        result.unwrap_err(),
+        Error::Km(ErrorCode::INCOMPATIBLE_BLOCK_MODE)
+            | Error::Km(ErrorCode::UNSUPPORTED_BLOCK_MODE)
+            | Error::Km(ErrorCode::INVALID_ARGUMENT)
+    ));
 }
 
 /// Try to create an operation using AES key with multiple padding modes. Test should fail to create
-/// an operation with `UNSUPPORTED_PADDING_MODE` error code.
+/// an operation.
 #[test]
 fn keystore2_aes_key_op_fails_multi_padding_modes() {
     let sl = SecLevel::tee();
@@ -292,7 +300,12 @@
         false,
     ));
     assert!(result.is_err());
-    assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE), result.unwrap_err());
+    assert!(matches!(
+        result.unwrap_err(),
+        Error::Km(ErrorCode::INCOMPATIBLE_PADDING_MODE)
+            | Error::Km(ErrorCode::UNSUPPORTED_PADDING_MODE)
+            | Error::Km(ErrorCode::INVALID_ARGUMENT)
+    ));
 }
 
 /// Generate a AES-ECB key with unpadded mode. Try to create an operation using generated key
diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs
index b51896a..02dfd3f 100644
--- a/keystore2/tests/keystore2_client_attest_key_tests.rs
+++ b/keystore2/tests/keystore2_client_attest_key_tests.rs
@@ -33,7 +33,7 @@
 use keystore2_test_utils::{
     authorizations, key_generations, key_generations::Error, run_as, SecLevel,
 };
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
 use rustutils::users::AID_USER_OFFSET;
 
 /// Generate RSA and EC attestation keys and use them for signing RSA-signing keys.
@@ -47,8 +47,12 @@
 
     for algo in [Algorithm::RSA, Algorithm::EC] {
         // Create attestation key.
-        let attestation_key_metadata =
-            key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap();
+        let Some(attestation_key_metadata) = key_generations::map_ks_error(
+            key_generations::generate_attestation_key(&sl, algo, att_challenge),
+        )
+        .unwrap() else {
+            return;
+        };
 
         let mut cert_chain: Vec<u8> = Vec::new();
         cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -57,7 +61,7 @@
 
         // Create RSA signing key and use attestation key to sign it.
         let sign_key_alias = format!("ks_attest_rsa_signing_key_{}", getuid());
-        let sign_key_metadata = key_generations::generate_rsa_key(
+        let Some(sign_key_metadata) = key_generations::generate_rsa_key(
             &sl,
             Domain::APP,
             -1,
@@ -73,7 +77,9 @@
             },
             Some(&attestation_key_metadata.key),
         )
-        .unwrap();
+        .unwrap() else {
+            return;
+        };
 
         let mut cert_chain: Vec<u8> = Vec::new();
         cert_chain.extend(sign_key_metadata.certificate.as_ref().unwrap());
@@ -94,8 +100,12 @@
 
     for algo in [Algorithm::RSA, Algorithm::EC] {
         // Create attestation key.
-        let attestation_key_metadata =
-            key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap();
+        let Some(attestation_key_metadata) = key_generations::map_ks_error(
+            key_generations::generate_attestation_key(&sl, algo, att_challenge),
+        )
+        .unwrap() else {
+            return;
+        };
 
         let mut cert_chain: Vec<u8> = Vec::new();
         cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -104,7 +114,7 @@
 
         // Create RSA encrypt/decrypt key and use attestation key to sign it.
         let decrypt_key_alias = format!("ks_attest_rsa_encrypt_key_{}", getuid());
-        let decrypt_key_metadata = key_generations::generate_rsa_key(
+        let Some(decrypt_key_metadata) = key_generations::generate_rsa_key(
             &sl,
             Domain::APP,
             -1,
@@ -120,7 +130,9 @@
             },
             Some(&attestation_key_metadata.key),
         )
-        .unwrap();
+        .unwrap() else {
+            return;
+        };
 
         let mut cert_chain: Vec<u8> = Vec::new();
         cert_chain.extend(decrypt_key_metadata.certificate.as_ref().unwrap());
@@ -142,8 +154,12 @@
 
     for algo in [Algorithm::RSA, Algorithm::EC] {
         // Create attestation key.
-        let attestation_key_metadata =
-            key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap();
+        let Some(attestation_key_metadata) = key_generations::map_ks_error(
+            key_generations::generate_attestation_key(&sl, algo, att_challenge),
+        )
+        .unwrap() else {
+            return;
+        };
 
         let mut cert_chain: Vec<u8> = Vec::new();
         cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -177,16 +193,27 @@
     skip_test_if_no_app_attest_key_feature!();
 
     let sl = SecLevel::tee();
+    if sl.get_keymint_version() < 2 {
+        // Curve 25519 was included in version 2 of the KeyMint interface.
+        // For device with KeyMint-V1 or Keymaster in backend, emulated Ed25519 key can't attest
+        // to a "real" RSA key.
+        return;
+    }
+
     let att_challenge: &[u8] = b"foo";
 
     // Create EcCurve::CURVE_25519 attestation key.
-    let attestation_key_metadata = key_generations::generate_ec_attestation_key(
-        &sl,
-        att_challenge,
-        Digest::NONE,
-        EcCurve::CURVE_25519,
-    )
-    .unwrap();
+    let Some(attestation_key_metadata) =
+        key_generations::map_ks_error(key_generations::generate_ec_attestation_key(
+            &sl,
+            att_challenge,
+            Digest::NONE,
+            EcCurve::CURVE_25519,
+        ))
+        .unwrap()
+    else {
+        return;
+    };
 
     let mut cert_chain: Vec<u8> = Vec::new();
     cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -195,7 +222,7 @@
 
     // Create RSA signing key and use attestation key to sign it.
     let sign_key_alias = format!("ksrsa_attested_sign_test_key_{}", getuid());
-    let sign_key_metadata = key_generations::generate_rsa_key(
+    let Some(sign_key_metadata) = key_generations::generate_rsa_key(
         &sl,
         Domain::APP,
         -1,
@@ -211,7 +238,9 @@
         },
         Some(&attestation_key_metadata.key),
     )
-    .unwrap();
+    .unwrap() else {
+        return;
+    };
 
     let mut cert_chain: Vec<u8> = Vec::new();
     cert_chain.extend(sign_key_metadata.certificate.as_ref().unwrap());
@@ -226,6 +255,13 @@
 fn keystore2_generate_rsa_attest_key_with_multi_purpose_fail() {
     skip_test_if_no_app_attest_key_feature!();
     let sl = SecLevel::tee();
+    if sl.get_keymint_version() < 2 {
+        // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined
+        // with other key purposes.  However, this was not checked at the time
+        // so we can only be strict about checking this for implementations of KeyMint
+        // version 2 and above.
+        return;
+    }
 
     let digest = Digest::SHA_2_256;
     let padding = PaddingMode::RSA_PKCS1_1_5_SIGN;
@@ -267,6 +303,13 @@
 fn keystore2_ec_attest_key_with_multi_purpose_fail() {
     skip_test_if_no_app_attest_key_feature!();
     let sl = SecLevel::tee();
+    if sl.get_keymint_version() < 2 {
+        // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined
+        // with other key purposes.  However, this was not checked at the time
+        // so we can only be strict about checking this for implementations of KeyMint
+        // version 2 and above.
+        return;
+    }
 
     let attest_key_alias = format!("ks_ec_attest_multipurpose_key_{}", getuid());
 
@@ -306,8 +349,12 @@
     let att_challenge: &[u8] = b"foo";
 
     // Create RSA attestation key.
-    let attestation_key_metadata =
-        key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap();
+    let Some(attestation_key_metadata) = key_generations::map_ks_error(
+        key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge),
+    )
+    .unwrap() else {
+        return;
+    };
 
     let mut cert_chain: Vec<u8> = Vec::new();
     cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
@@ -416,55 +463,6 @@
     assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
 }
 
-/// Generate RSA attestation key and try to use it as attestation key while generating symmetric
-/// key. Test should generate symmetric key successfully. Verify that generated symmetric key
-/// should not have attestation record or certificate.
-#[test]
-fn keystore2_attest_symmetric_key_fail_sys_error() {
-    skip_test_if_no_app_attest_key_feature!();
-
-    let sl = SecLevel::tee();
-    let att_challenge: &[u8] = b"foo";
-
-    // Create attestation key.
-    let attestation_key_metadata =
-        key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap();
-
-    let mut cert_chain: Vec<u8> = Vec::new();
-    cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap());
-    cert_chain.extend(attestation_key_metadata.certificateChain.as_ref().unwrap());
-    validate_certchain(&cert_chain).expect("Error while validating cert chain.");
-
-    // Generate symmetric key with above generated key as attestation key.
-    let gen_params = authorizations::AuthSetBuilder::new()
-        .no_auth_required()
-        .algorithm(Algorithm::AES)
-        .purpose(KeyPurpose::ENCRYPT)
-        .purpose(KeyPurpose::DECRYPT)
-        .key_size(128)
-        .padding_mode(PaddingMode::NONE)
-        .block_mode(BlockMode::ECB)
-        .attestation_challenge(att_challenge.to_vec());
-
-    let alias = format!("ks_test_sym_key_attest_{}", getuid());
-    let aes_key_metadata = sl
-        .binder
-        .generateKey(
-            &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
-            Some(&attestation_key_metadata.key),
-            &gen_params,
-            0,
-            b"entropy",
-        )
-        .unwrap();
-
-    // Should not have public certificate.
-    assert!(aes_key_metadata.certificate.is_none());
-
-    // Should not have an attestation record.
-    assert!(aes_key_metadata.certificateChain.is_none());
-}
-
 fn get_attestation_ids(keystore2: &binder::Strong<dyn IKeystoreService>) -> Vec<(Tag, Vec<u8>)> {
     let attest_ids = vec![
         (Tag::ATTESTATION_ID_BRAND, "brand"),
@@ -506,9 +504,12 @@
     let sl = SecLevel::tee();
 
     let att_challenge: &[u8] = b"foo";
-
-    let attest_key_metadata =
-        key_generations::generate_attestation_key(&sl, algorithm, att_challenge).unwrap();
+    let Some(attest_key_metadata) = key_generations::map_ks_error(
+        key_generations::generate_attestation_key(&sl, algorithm, att_challenge),
+    )
+    .unwrap() else {
+        return;
+    };
 
     let attest_id_params = get_attestation_ids(&sl.keystore2);
 
@@ -561,6 +562,8 @@
 #[test]
 fn keystore2_attest_key_fails_with_invalid_attestation_id() {
     skip_test_if_no_device_id_attestation_feature!();
+    skip_device_id_attestation_tests!();
+    skip_test_if_no_app_attest_key_feature!();
 
     let sl = SecLevel::tee();
 
@@ -568,9 +571,12 @@
     let att_challenge: &[u8] = b"foo";
 
     // Create EC-Attestation key.
-    let attest_key_metadata =
-        key_generations::generate_ec_attestation_key(&sl, att_challenge, digest, EcCurve::P_256)
-            .unwrap();
+    let Some(attest_key_metadata) = key_generations::map_ks_error(
+        key_generations::generate_ec_attestation_key(&sl, att_challenge, digest, EcCurve::P_256),
+    )
+    .unwrap() else {
+        return;
+    };
 
     let attest_id_params = vec![
         (Tag::ATTESTATION_ID_BRAND, b"invalid-brand".to_vec()),
@@ -609,12 +615,18 @@
         // Skip this test on device supporting `DEVICE_ID_ATTESTATION_FEATURE`.
         return;
     }
+    skip_device_id_attestation_tests!();
+    skip_test_if_no_app_attest_key_feature!();
 
     let sl = SecLevel::tee();
 
     let att_challenge: &[u8] = b"foo";
-    let attest_key_metadata =
-        key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap();
+    let Some(attest_key_metadata) = key_generations::map_ks_error(
+        key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge),
+    )
+    .unwrap() else {
+        return;
+    };
 
     let attest_id_params = get_attestation_ids(&sl.keystore2);
     for (attest_id, value) in attest_id_params {
@@ -643,42 +655,47 @@
 /// should return error response code - `GET_ATTESTATION_APPLICATION_ID_FAILED`.
 #[test]
 fn keystore2_generate_attested_key_fail_to_get_aaid() {
-    static APP_USER_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
     const USER_ID: u32 = 99;
-    const APPLICATION_ID: u32 = 10001;
+    const APPLICATION_ID: u32 = 19901;
     static APP_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static APP_GID: u32 = APP_UID;
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(APP_USER_CTX, Uid::from_raw(APP_UID), Gid::from_raw(APP_GID), || {
-            skip_test_if_no_app_attest_key_feature!();
-            let sl = SecLevel::tee();
-            let att_challenge: &[u8] = b"foo";
-            let alias = format!("ks_attest_rsa_encrypt_key_aaid_fail{}", getuid());
+    let gen_key_fn = || {
+        skip_test_if_no_app_attest_key_feature!();
+        let sl = SecLevel::tee();
+        if sl.keystore2.getInterfaceVersion().unwrap() < 4 {
+            // `GET_ATTESTATION_APPLICATION_ID_FAILED` is supported on devices with
+            // `IKeystoreService` version >= 4.
+            return;
+        }
+        let att_challenge: &[u8] = b"foo";
+        let alias = format!("ks_attest_rsa_encrypt_key_aaid_fail{}", getuid());
 
-            let result = key_generations::map_ks_error(key_generations::generate_rsa_key(
-                &sl,
-                Domain::APP,
-                -1,
-                Some(alias),
-                &key_generations::KeyParams {
-                    key_size: 2048,
-                    purpose: vec![KeyPurpose::ATTEST_KEY],
-                    padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
-                    digest: Some(Digest::SHA_2_256),
-                    mgf_digest: None,
-                    block_mode: None,
-                    att_challenge: Some(att_challenge.to_vec()),
-                },
-                None,
-            ));
+        let result = key_generations::map_ks_error(key_generations::generate_rsa_key(
+            &sl,
+            Domain::APP,
+            -1,
+            Some(alias),
+            &key_generations::KeyParams {
+                key_size: 2048,
+                purpose: vec![KeyPurpose::ATTEST_KEY],
+                padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN),
+                digest: Some(Digest::SHA_2_256),
+                mgf_digest: None,
+                block_mode: None,
+                att_challenge: Some(att_challenge.to_vec()),
+            },
+            None,
+        ));
 
-            assert!(result.is_err());
-            assert_eq!(
-                result.unwrap_err(),
-                Error::Rc(ResponseCode::GET_ATTESTATION_APPLICATION_ID_FAILED)
-            );
-        })
+        assert!(result.is_err());
+        assert_eq!(
+            result.unwrap_err(),
+            Error::Rc(ResponseCode::GET_ATTESTATION_APPLICATION_ID_FAILED)
+        );
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(APP_UID, APP_GID, gen_key_fn) };
 }
diff --git a/keystore2/tests/keystore2_client_authorizations_tests.rs b/keystore2/tests/keystore2_client_authorizations_tests.rs
index 0981783..6d105cc 100644
--- a/keystore2/tests/keystore2_client_authorizations_tests.rs
+++ b/keystore2/tests/keystore2_client_authorizations_tests.rs
@@ -13,13 +13,12 @@
 // limitations under the License.
 
 use crate::keystore2_client_test_utils::{
-    app_attest_key_feature_exists, delete_app_key, perform_sample_asym_sign_verify_op,
-    perform_sample_hmac_sign_verify_op, perform_sample_sym_key_decrypt_op,
-    perform_sample_sym_key_encrypt_op, verify_certificate_serial_num,
-    verify_certificate_subject_name, SAMPLE_PLAIN_TEXT,
+    app_attest_key_feature_exists, delete_app_key, get_vsr_api_level,
+    perform_sample_asym_sign_verify_op, perform_sample_hmac_sign_verify_op,
+    perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op,
+    verify_certificate_serial_num, verify_certificate_subject_name, SAMPLE_PLAIN_TEXT,
 };
 use crate::{require_keymint, skip_test_if_no_app_attest_key_feature};
-use aconfig_android_hardware_biometrics_rust;
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
     ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
@@ -27,6 +26,7 @@
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
+    KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue,
 };
 use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
     Timestamp::Timestamp
@@ -35,6 +35,8 @@
     Domain::Domain, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata,
     ResponseCode::ResponseCode,
 };
+use bssl_crypto::digest;
+use keystore_attestation::{AttestationExtension, ATTESTATION_EXTENSION_OID};
 use keystore2_test_utils::ffi_test_utils::get_value_from_attest_record;
 use keystore2_test_utils::{
     authorizations, get_keystore_auth_service, key_generations,
@@ -43,8 +45,9 @@
 use openssl::bn::{BigNum, MsbOption};
 use openssl::x509::X509NameBuilder;
 use std::time::SystemTime;
+use x509_cert::{certificate::Certificate, der::Decode};
 
-fn gen_key_including_unique_id(sl: &SecLevel, alias: &str) -> Vec<u8> {
+fn gen_key_including_unique_id(sl: &SecLevel, alias: &str) -> Option<Vec<u8>> {
     let gen_params = authorizations::AuthSetBuilder::new()
         .no_auth_required()
         .algorithm(Algorithm::EC)
@@ -55,7 +58,9 @@
         .attestation_challenge(b"foo".to_vec())
         .include_unique_id();
 
-    let key_metadata = key_generations::generate_key(sl, &gen_params, alias).unwrap();
+    let key_metadata =
+        key_generations::map_ks_error(key_generations::generate_key(sl, &gen_params, alias))
+            .unwrap()?;
 
     let unique_id = get_value_from_attest_record(
         key_metadata.certificate.as_ref().unwrap(),
@@ -64,7 +69,7 @@
     )
     .expect("Unique id not found.");
     assert!(!unique_id.is_empty());
-    unique_id
+    Some(unique_id)
 }
 
 fn generate_key_and_perform_sign_verify_op_max_times(
@@ -72,8 +77,10 @@
     gen_params: &authorizations::AuthSetBuilder,
     alias: &str,
     max_usage_count: i32,
-) -> binder::Result<KeyMetadata> {
-    let key_metadata = key_generations::generate_key(sl, gen_params, alias)?;
+) -> binder::Result<Option<KeyMetadata>> {
+    let Some(key_metadata) = key_generations::generate_key(sl, gen_params, alias)? else {
+        return Ok(None);
+    };
 
     // Use above generated key `max_usage_count` times.
     for _ in 0..max_usage_count {
@@ -85,7 +92,7 @@
         );
     }
 
-    Ok(key_metadata)
+    Ok(Some(key_metadata))
 }
 
 /// Generate a key with `USAGE_COUNT_LIMIT` and verify the key characteristics. Test should be able
@@ -100,9 +107,12 @@
     check_attestation: bool,
 ) {
     // Generate a key and use the key for `max_usage_count` times.
-    let key_metadata =
+    let Some(key_metadata) =
         generate_key_and_perform_sign_verify_op_max_times(sl, gen_params, alias, max_usage_count)
-            .unwrap();
+            .unwrap()
+    else {
+        return;
+    };
 
     let auth = key_generations::get_key_auth(&key_metadata.authorizations, Tag::USAGE_COUNT_LIMIT)
         .unwrap();
@@ -158,7 +168,6 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .attestation_challenge(b"foo".to_vec())
         .active_date_time(active_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_test";
@@ -189,7 +198,6 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .attestation_challenge(b"foo".to_vec())
         .active_date_time(future_active_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_test";
@@ -220,7 +228,6 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .attestation_challenge(b"foo".to_vec())
         .origination_expire_date_time(origination_expire_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_test";
@@ -251,7 +258,6 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .attestation_challenge(b"foo".to_vec())
         .origination_expire_date_time(origination_expire_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_test";
@@ -286,7 +292,9 @@
         .usage_expire_date_time(usage_expire_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_hmac_verify_success";
-    let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+    let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+        return;
+    };
 
     perform_sample_hmac_sign_verify_op(&sl.binder, &key_metadata.key);
     delete_app_key(&sl.keystore2, alias).unwrap();
@@ -313,7 +321,9 @@
         .usage_expire_date_time(usage_expire_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_hamc_verify_fail";
-    let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+    let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+        return;
+    };
 
     let result = key_generations::map_ks_error(
         sl.binder.createOperation(
@@ -349,7 +359,9 @@
         .usage_expire_date_time(usage_expire_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_test";
-    let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+    let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+        return;
+    };
     let cipher_text = perform_sample_sym_key_encrypt_op(
         &sl.binder,
         PaddingMode::PKCS7,
@@ -398,7 +410,9 @@
         .usage_expire_date_time(usage_expire_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_test";
-    let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+    let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+        return;
+    };
     let cipher_text = perform_sample_sym_key_encrypt_op(
         &sl.binder,
         PaddingMode::PKCS7,
@@ -440,7 +454,6 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .attestation_challenge(b"foo".to_vec())
         .early_boot_only();
 
     let alias = "ks_test_auth_tags_test";
@@ -462,6 +475,13 @@
 #[test]
 fn keystore2_gen_key_auth_max_uses_per_boot() {
     let sl = SecLevel::tee();
+    if sl.is_keymaster() {
+        // Older devices with Keymaster implementation may use the key during generateKey to export
+        // the generated public key (EC Key), leading to an unnecessary increment of the
+        // key-associated counter. This can cause the test to fail, so skipping this test on older
+        // devices to avoid test failure.
+        return;
+    }
     const MAX_USES_COUNT: i32 = 3;
 
     let gen_params = authorizations::AuthSetBuilder::new()
@@ -471,14 +491,16 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .attestation_challenge(b"foo".to_vec())
         .max_uses_per_boot(MAX_USES_COUNT);
 
     let alias = "ks_test_auth_tags_test";
     // Generate a key and use the key for `MAX_USES_COUNT` times.
-    let key_metadata =
+    let Some(key_metadata) =
         generate_key_and_perform_sign_verify_op_max_times(&sl, &gen_params, alias, MAX_USES_COUNT)
-            .unwrap();
+            .unwrap()
+    else {
+        return;
+    };
 
     // Try to use the key one more time.
     let result = key_generations::map_ks_error(sl.binder.createOperation(
@@ -499,6 +521,10 @@
 #[test]
 fn keystore2_gen_key_auth_usage_count_limit() {
     let sl = SecLevel::tee();
+    if sl.is_keymaster() {
+        // `USAGE_COUNT_LIMIT` is supported from KeyMint1.0
+        return;
+    }
     const MAX_USES_COUNT: i32 = 3;
 
     let gen_params = authorizations::AuthSetBuilder::new()
@@ -523,6 +549,10 @@
 #[test]
 fn keystore2_gen_key_auth_usage_count_limit_one() {
     let sl = SecLevel::tee();
+    if sl.is_keymaster() {
+        // `USAGE_COUNT_LIMIT` is supported from KeyMint1.0
+        return;
+    }
     const MAX_USES_COUNT: i32 = 1;
 
     let gen_params = authorizations::AuthSetBuilder::new()
@@ -546,6 +576,10 @@
 #[test]
 fn keystore2_gen_non_attested_key_auth_usage_count_limit() {
     let sl = SecLevel::tee();
+    if sl.is_keymaster() {
+        // `USAGE_COUNT_LIMIT` is supported from KeyMint1.0
+        return;
+    }
     const MAX_USES_COUNT: i32 = 2;
 
     let gen_params = authorizations::AuthSetBuilder::new()
@@ -583,7 +617,6 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .attestation_challenge(b"foo".to_vec())
         .creation_date_time(creation_datetime.try_into().unwrap());
 
     let alias = "ks_test_auth_tags_test";
@@ -609,92 +642,44 @@
 #[test]
 fn keystore2_gen_key_auth_include_unique_id_success() {
     let sl = SecLevel::tee();
+    if sl.is_keymaster() {
+        // b/387208956 - Some older devices with Keymaster implementations fail to generate an
+        // attestation key with `INCLUDE_UNIQUE_ID`, but this was not previously tested. Skip this
+        // test on devices with Keymaster implementation.
+        return;
+    }
 
     let alias_first = "ks_test_auth_tags_test_1";
-    let unique_id_first = gen_key_including_unique_id(&sl, alias_first);
+    if let Some(unique_id_first) = gen_key_including_unique_id(&sl, alias_first) {
+        let alias_second = "ks_test_auth_tags_test_2";
+        let unique_id_second = gen_key_including_unique_id(&sl, alias_second).unwrap();
 
-    let alias_second = "ks_test_auth_tags_test_2";
-    let unique_id_second = gen_key_including_unique_id(&sl, alias_second);
+        assert_eq!(unique_id_first, unique_id_second);
 
-    assert_eq!(unique_id_first, unique_id_second);
-
-    delete_app_key(&sl.keystore2, alias_first).unwrap();
-    delete_app_key(&sl.keystore2, alias_second).unwrap();
+        delete_app_key(&sl.keystore2, alias_first).unwrap();
+        delete_app_key(&sl.keystore2, alias_second).unwrap();
+    }
 }
 
-/// Generate a key with `APPLICATION_DATA`. Test should create an operation using the
-/// same `APPLICATION_DATA` successfully.
+/// Generate a key with `APPLICATION_DATA` and `APPLICATION_ID`. Test should create an operation
+/// successfully using the same `APPLICATION_DATA` and `APPLICATION_ID`.
 #[test]
-fn keystore2_gen_key_auth_app_data_test_success() {
+fn keystore2_gen_key_auth_app_data_app_id_test_success() {
     let sl = SecLevel::tee();
+    if sl.is_keymaster() && get_vsr_api_level() < 35 {
+        // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+        // test to run on older devices.
+        return;
+    }
 
     let gen_params = authorizations::AuthSetBuilder::new()
         .no_auth_required()
         .algorithm(Algorithm::EC)
         .purpose(KeyPurpose::SIGN)
         .purpose(KeyPurpose::VERIFY)
-        .digest(Digest::SHA_2_256)
+        .digest(Digest::NONE)
         .ec_curve(EcCurve::P_256)
-        .app_data(b"app-data".to_vec());
-
-    let alias = "ks_test_auth_tags_test";
-    let result = key_generations::create_key_and_operation(
-        &sl,
-        &gen_params,
-        &authorizations::AuthSetBuilder::new()
-            .purpose(KeyPurpose::SIGN)
-            .digest(Digest::SHA_2_256)
-            .app_data(b"app-data".to_vec()),
-        alias,
-    );
-    assert!(result.is_ok());
-    delete_app_key(&sl.keystore2, alias).unwrap();
-}
-
-/// Generate a key with `APPLICATION_DATA`. Try to create an operation using the
-/// different `APPLICATION_DATA`, test should fail to create an operation with error code
-/// `INVALID_KEY_BLOB`.
-#[test]
-fn keystore2_gen_key_auth_app_data_test_fail() {
-    let sl = SecLevel::tee();
-
-    let gen_params = authorizations::AuthSetBuilder::new()
-        .no_auth_required()
-        .algorithm(Algorithm::EC)
-        .purpose(KeyPurpose::SIGN)
-        .purpose(KeyPurpose::VERIFY)
-        .digest(Digest::SHA_2_256)
-        .ec_curve(EcCurve::P_256)
-        .app_data(b"app-data".to_vec());
-
-    let alias = "ks_test_auth_tags_test";
-    let result = key_generations::map_ks_error(key_generations::create_key_and_operation(
-        &sl,
-        &gen_params,
-        &authorizations::AuthSetBuilder::new()
-            .purpose(KeyPurpose::SIGN)
-            .digest(Digest::SHA_2_256)
-            .app_data(b"invalid-app-data".to_vec()),
-        alias,
-    ));
-    assert!(result.is_err());
-    assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err());
-    delete_app_key(&sl.keystore2, alias).unwrap();
-}
-
-/// Generate a key with `APPLICATION_ID`. Test should create an operation using the
-/// same `APPLICATION_ID` successfully.
-#[test]
-fn keystore2_gen_key_auth_app_id_test_success() {
-    let sl = SecLevel::tee();
-
-    let gen_params = authorizations::AuthSetBuilder::new()
-        .no_auth_required()
-        .algorithm(Algorithm::EC)
-        .purpose(KeyPurpose::SIGN)
-        .purpose(KeyPurpose::VERIFY)
-        .digest(Digest::SHA_2_256)
-        .ec_curve(EcCurve::P_256)
+        .app_data(b"app-data".to_vec())
         .app_id(b"app-id".to_vec());
 
     let alias = "ks_test_auth_tags_test";
@@ -703,7 +688,8 @@
         &gen_params,
         &authorizations::AuthSetBuilder::new()
             .purpose(KeyPurpose::SIGN)
-            .digest(Digest::SHA_2_256)
+            .digest(Digest::NONE)
+            .app_data(b"app-data".to_vec())
             .app_id(b"app-id".to_vec()),
         alias,
     );
@@ -711,20 +697,25 @@
     delete_app_key(&sl.keystore2, alias).unwrap();
 }
 
-/// Generate a key with `APPLICATION_ID`. Try to create an operation using the
-/// different `APPLICATION_ID`, test should fail to create an operation with error code
-/// `INVALID_KEY_BLOB`.
+/// Generate a key with `APPLICATION_DATA` and `APPLICATION_ID`. Try to create an operation using
+/// the different `APPLICATION_DATA` and `APPLICATION_ID`, test should fail to create an operation.
 #[test]
-fn keystore2_gen_key_auth_app_id_test_fail() {
+fn keystore2_op_auth_invalid_app_data_app_id_test_fail() {
     let sl = SecLevel::tee();
+    if sl.is_keymaster() && get_vsr_api_level() < 35 {
+        // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+        // test to run on older devices.
+        return;
+    }
 
     let gen_params = authorizations::AuthSetBuilder::new()
         .no_auth_required()
         .algorithm(Algorithm::EC)
         .purpose(KeyPurpose::SIGN)
         .purpose(KeyPurpose::VERIFY)
-        .digest(Digest::SHA_2_256)
+        .digest(Digest::NONE)
         .ec_curve(EcCurve::P_256)
+        .app_data(b"app-data".to_vec())
         .app_id(b"app-id".to_vec());
 
     let alias = "ks_test_auth_tags_test";
@@ -733,7 +724,8 @@
         &gen_params,
         &authorizations::AuthSetBuilder::new()
             .purpose(KeyPurpose::SIGN)
-            .digest(Digest::SHA_2_256)
+            .digest(Digest::NONE)
+            .app_data(b"invalid-app-data".to_vec())
             .app_id(b"invalid-app-id".to_vec()),
         alias,
     ));
@@ -742,6 +734,79 @@
     delete_app_key(&sl.keystore2, alias).unwrap();
 }
 
+/// Generate a key with `APPLICATION_DATA` and `APPLICATION_ID`. Try to create an operation using
+/// only `APPLICATION_ID`, test should fail to create an operation.
+#[test]
+fn keystore2_op_auth_missing_app_data_test_fail() {
+    let sl = SecLevel::tee();
+    if sl.is_keymaster() && get_vsr_api_level() < 35 {
+        // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+        // test to run on older devices.
+        return;
+    }
+
+    let gen_params = authorizations::AuthSetBuilder::new()
+        .no_auth_required()
+        .algorithm(Algorithm::EC)
+        .purpose(KeyPurpose::SIGN)
+        .purpose(KeyPurpose::VERIFY)
+        .digest(Digest::NONE)
+        .ec_curve(EcCurve::P_256)
+        .app_id(b"app-id".to_vec())
+        .app_data(b"app-data".to_vec());
+
+    let alias = "ks_test_auth_tags_test";
+    let result = key_generations::map_ks_error(key_generations::create_key_and_operation(
+        &sl,
+        &gen_params,
+        &authorizations::AuthSetBuilder::new()
+            .purpose(KeyPurpose::SIGN)
+            .digest(Digest::NONE)
+            .app_id(b"app-id".to_vec()),
+        alias,
+    ));
+
+    assert!(result.is_err());
+    assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err());
+    delete_app_key(&sl.keystore2, alias).unwrap();
+}
+
+/// Generate a key with `APPLICATION_DATA` and `APPLICATION_ID`. Try to create an operation using
+/// only `APPLICATION_DATA`, test should fail to create an operation.
+#[test]
+fn keystore2_op_auth_missing_app_id_test_fail() {
+    let sl = SecLevel::tee();
+    if sl.is_keymaster() && get_vsr_api_level() < 35 {
+        // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+        // test to run on older devices.
+        return;
+    }
+
+    let gen_params = authorizations::AuthSetBuilder::new()
+        .no_auth_required()
+        .algorithm(Algorithm::EC)
+        .purpose(KeyPurpose::SIGN)
+        .purpose(KeyPurpose::VERIFY)
+        .digest(Digest::NONE)
+        .ec_curve(EcCurve::P_256)
+        .app_data(b"app-data".to_vec())
+        .app_id(b"app-id".to_vec());
+
+    let alias = "ks_test_auth_tags_test";
+    let result = key_generations::map_ks_error(key_generations::create_key_and_operation(
+        &sl,
+        &gen_params,
+        &authorizations::AuthSetBuilder::new()
+            .purpose(KeyPurpose::SIGN)
+            .digest(Digest::NONE)
+            .app_data(b"app-data".to_vec()),
+        alias,
+    ));
+    assert!(result.is_err());
+    assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err());
+    delete_app_key(&sl.keystore2, alias).unwrap();
+}
+
 /// Generate an attestation-key without specifying `APPLICATION_ID` and `APPLICATION_DATA`.
 /// Test should be able to generate a new key with specifying app-id and app-data using previously
 /// generated attestation-key.
@@ -749,6 +814,11 @@
 fn keystore2_gen_attested_key_auth_app_id_app_data_test_success() {
     skip_test_if_no_app_attest_key_feature!();
     let sl = SecLevel::tee();
+    if sl.is_keymaster() && get_vsr_api_level() < 35 {
+        // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+        // test to run on older devices.
+        return;
+    }
 
     // Generate attestation key.
     let attest_gen_params = authorizations::AuthSetBuilder::new()
@@ -759,8 +829,11 @@
         .ec_curve(EcCurve::P_256)
         .attestation_challenge(b"foo".to_vec());
     let attest_alias = "ks_test_auth_tags_attest_key";
-    let attest_key_metadata =
-        key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap();
+    let Some(attest_key_metadata) =
+        key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap()
+    else {
+        return;
+    };
 
     // Generate attested key.
     let alias = "ks_test_auth_tags_attested_key";
@@ -795,14 +868,18 @@
 
 /// Generate an attestation-key with specifying `APPLICATION_ID` and `APPLICATION_DATA`.
 /// Test should try to generate an attested key using previously generated attestation-key without
-/// specifying app-id and app-data. Test should fail to generate a new key with error code
-/// `INVALID_KEY_BLOB`.
+/// specifying app-id and app-data. Test should fail to generate a new key.
 /// It is an oversight of the Keystore API that `APPLICATION_ID` and `APPLICATION_DATA` tags cannot
 /// be provided to generateKey for an attestation key that was generated with them.
 #[test]
 fn keystore2_gen_attestation_key_with_auth_app_id_app_data_test_fail() {
     skip_test_if_no_app_attest_key_feature!();
     let sl = SecLevel::tee();
+    if sl.is_keymaster() && get_vsr_api_level() < 35 {
+        // `APPLICATION_DATA` key-parameter is causing the error on older devices, so skipping this
+        // test to run on older devices.
+        return;
+    }
 
     // Generate attestation key.
     let attest_gen_params = authorizations::AuthSetBuilder::new()
@@ -815,8 +892,11 @@
         .app_id(b"app-id".to_vec())
         .app_data(b"app-data".to_vec());
     let attest_alias = "ks_test_auth_tags_attest_key";
-    let attest_key_metadata =
-        key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap();
+    let Some(attest_key_metadata) =
+        key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap()
+    else {
+        return;
+    };
 
     // Generate new key using above generated attestation key without providing app-id and app-data.
     let alias = "ks_test_auth_tags_attested_key";
@@ -862,25 +942,7 @@
 }
 
 #[test]
-fn keystore2_flagged_off_get_last_auth_password_permission_denied() {
-    if aconfig_android_hardware_biometrics_rust::last_authentication_time() {
-        return;
-    }
-
-    let keystore_auth = get_keystore_auth_service();
-
-    let result = keystore_auth.getLastAuthTime(0, &[HardwareAuthenticatorType::PASSWORD]);
-
-    assert!(result.is_err());
-    assert_eq!(result.unwrap_err().service_specific_error(), ResponseCode::PERMISSION_DENIED.0);
-}
-
-#[test]
-fn keystore2_flagged_on_get_last_auth_password_success() {
-    if !aconfig_android_hardware_biometrics_rust::last_authentication_time() {
-        return;
-    }
-
+fn keystore2_get_last_auth_password_success() {
     let keystore_auth = get_keystore_auth_service();
 
     add_hardware_token(HardwareAuthenticatorType::PASSWORD);
@@ -888,11 +950,7 @@
 }
 
 #[test]
-fn keystore2_flagged_on_get_last_auth_fingerprint_success() {
-    if !aconfig_android_hardware_biometrics_rust::last_authentication_time() {
-        return;
-    }
-
+fn keystore2_get_last_auth_fingerprint_success() {
     let keystore_auth = get_keystore_auth_service();
 
     add_hardware_token(HardwareAuthenticatorType::FINGERPRINT);
@@ -923,12 +981,13 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .attestation_challenge(b"foo".to_vec())
         .cert_subject_name(x509_name)
         .cert_serial(serial.to_vec());
 
     let alias = "ks_test_auth_tags_test";
-    let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap();
+    let Some(key_metadata) = key_generations::generate_key(&sl, &gen_params, alias).unwrap() else {
+        return;
+    };
     verify_certificate_subject_name(
         key_metadata.certificate.as_ref().unwrap(),
         cert_subject.as_bytes(),
@@ -936,3 +995,71 @@
     verify_certificate_serial_num(key_metadata.certificate.as_ref().unwrap(), &serial);
     delete_app_key(&sl.keystore2, alias).unwrap();
 }
+
+#[test]
+fn test_supplementary_attestation_info() {
+    if !keystore2_flags::attest_modules() {
+        // Module info is only populated if the flag is set.
+        return;
+    }
+
+    // Test should not run before MODULE_HASH supplementary info is populated.
+    assert!(rustutils::system_properties::read_bool("keystore.module_hash.sent", false)
+        .unwrap_or(false));
+
+    let sl = SecLevel::tee();
+
+    // Retrieve the input value that gets hashed into the attestation.
+    let module_info = sl
+        .keystore2
+        .getSupplementaryAttestationInfo(Tag::MODULE_HASH)
+        .expect("supplementary info for MODULE_HASH should be populated during startup");
+    let again = sl.keystore2.getSupplementaryAttestationInfo(Tag::MODULE_HASH).unwrap();
+    assert_eq!(again, module_info);
+    let want_hash = digest::Sha256::hash(&module_info).to_vec();
+
+    // Requesting other types of information should fail.
+    let result = key_generations::map_ks_error(
+        sl.keystore2.getSupplementaryAttestationInfo(Tag::BLOCK_MODE),
+    );
+    assert!(result.is_err());
+    assert_eq!(result.unwrap_err(), Error::Rc(ResponseCode::INVALID_ARGUMENT));
+
+    if sl.get_keymint_version() < 400 {
+        // Module hash will only be populated in KeyMint if the underlying device is KeyMint V4+.
+        return;
+    }
+
+    // Generate an attestation.
+    let alias = "ks_module_info_test";
+    let params = authorizations::AuthSetBuilder::new()
+        .no_auth_required()
+        .algorithm(Algorithm::EC)
+        .purpose(KeyPurpose::SIGN)
+        .purpose(KeyPurpose::VERIFY)
+        .digest(Digest::SHA_2_256)
+        .ec_curve(EcCurve::P_256)
+        .attestation_challenge(b"froop".to_vec());
+    let metadata = key_generations::generate_key(&sl, &params, alias)
+        .expect("failed key generation")
+        .expect("no metadata");
+    let cert_data = metadata.certificate.as_ref().unwrap();
+    let cert = Certificate::from_der(cert_data).expect("failed to parse X509 cert");
+    let exts = cert.tbs_certificate.extensions.expect("no X.509 extensions");
+    let ext = exts
+        .iter()
+        .find(|ext| ext.extn_id == ATTESTATION_EXTENSION_OID)
+        .expect("no attestation extension");
+    let ext = AttestationExtension::from_der(ext.extn_value.as_bytes())
+        .expect("failed to parse attestation extension");
+
+    // Find the attested module hash value.
+    let mut got_hash = None;
+    for auth in ext.sw_enforced.auths.into_owned().iter() {
+        if let KeyParameter { tag: Tag::MODULE_HASH, value: KeyParameterValue::Blob(hash) } = auth {
+            got_hash = Some(hash.clone());
+        }
+    }
+    let got_hash = got_hash.expect("no MODULE_HASH in sw_enforced");
+    assert_eq!(hex::encode(got_hash), hex::encode(want_hash));
+}
diff --git a/keystore2/tests/keystore2_client_delete_key_tests.rs b/keystore2/tests/keystore2_client_delete_key_tests.rs
index a0fb9c2..13f8eef 100644
--- a/keystore2/tests/keystore2_client_delete_key_tests.rs
+++ b/keystore2/tests/keystore2_client_delete_key_tests.rs
@@ -100,7 +100,7 @@
     )
     .unwrap();
 
-    let result = sl.binder.deleteKey(&key_metadata.key);
+    let result = sl.delete_key(&key_metadata.key);
     assert!(result.is_ok());
 }
 
@@ -110,7 +110,7 @@
 fn keystore2_delete_key_fails_with_missing_key_blob() {
     let sl = SecLevel::tee();
 
-    let result = key_generations::map_ks_error(sl.binder.deleteKey(&KeyDescriptor {
+    let result = key_generations::map_ks_error(sl.delete_key(&KeyDescriptor {
         domain: Domain::BLOB,
         nspace: key_generations::SELINUX_SHELL_NAMESPACE,
         alias: None,
@@ -132,7 +132,7 @@
         key_generations::generate_ec_p256_signing_key(&sl, Domain::APP, -1, Some(alias), None)
             .unwrap();
 
-    let result = key_generations::map_ks_error(sl.binder.deleteKey(&key_metadata.key));
+    let result = key_generations::map_ks_error(sl.delete_key(&key_metadata.key));
     assert!(result.is_err());
     assert_eq!(Error::Km(ErrorCode::INVALID_ARGUMENT), result.unwrap_err());
 }
diff --git a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
index cc5d85c..91370c7 100644
--- a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
+++ b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
@@ -130,7 +130,7 @@
         let alias = "ks_test_device_unique_attest_id_test";
         match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias))
         {
-            Ok(key_metadata) => {
+            Ok(Some(key_metadata)) => {
                 let attest_id_value = get_value_from_attest_record(
                     key_metadata.certificate.as_ref().unwrap(),
                     attest_id,
@@ -140,6 +140,7 @@
                 assert_eq!(attest_id_value, value);
                 delete_app_key(&sl.keystore2, alias).unwrap();
             }
+            Ok(None) => {}
             Err(e) => {
                 assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
             }
@@ -196,7 +197,7 @@
 
     let alias = "ks_device_unique_ec_key_attest_test";
     match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)) {
-        Ok(key_metadata) => {
+        Ok(Some(key_metadata)) => {
             perform_sample_asym_sign_verify_op(
                 &sl.binder,
                 &key_metadata,
@@ -205,6 +206,7 @@
             );
             delete_app_key(&sl.keystore2, alias).unwrap();
         }
+        Ok(None) => {}
         Err(e) => {
             assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
         }
@@ -235,7 +237,7 @@
 
     let alias = "ks_device_unique_rsa_key_attest_test";
     match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)) {
-        Ok(key_metadata) => {
+        Ok(Some(key_metadata)) => {
             perform_sample_asym_sign_verify_op(
                 &sl.binder,
                 &key_metadata,
@@ -244,6 +246,7 @@
             );
             delete_app_key(&sl.keystore2, alias).unwrap();
         }
+        Ok(None) => {}
         Err(e) => {
             assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
         }
@@ -285,7 +288,7 @@
         let result =
             key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias));
         assert!(result.is_err());
-        assert!(matches!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS)));
+        assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
     }
 }
 
diff --git a/keystore2/tests/keystore2_client_ec_key_tests.rs b/keystore2/tests/keystore2_client_ec_key_tests.rs
index 8aa9bc4..17a88e7 100644
--- a/keystore2/tests/keystore2_client_ec_key_tests.rs
+++ b/keystore2/tests/keystore2_client_ec_key_tests.rs
@@ -211,7 +211,7 @@
     assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
 
     // Delete the generated key blob.
-    sl.binder.deleteKey(&key_metadata.key).unwrap();
+    sl.delete_key(&key_metadata.key).unwrap();
 }
 
 /// Try to generate a key with invalid Domain. `INVALID_ARGUMENT` error response is expected.
@@ -425,7 +425,9 @@
     // Client#1: Generate a key and create an operation using generated key.
     // Wait until the parent notifies to continue. Once the parent notifies, this operation
     // is expected to be completed successfully.
-    // SAFETY: The test is run in a separate process with no other threads.
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     let mut child_handle = unsafe {
         execute_op_run_as_child(
             TARGET_CTX,
@@ -446,20 +448,23 @@
     const APPLICATION_ID_2: u32 = 10602;
     let uid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2;
     let gid2 = USER_ID * AID_USER_OFFSET + APPLICATION_ID_2;
-    // SAFETY: The test is run in a separate process with no other threads.
+
+    let get_key_fn = move || {
+        let keystore2_inst = get_keystore_service();
+        let result = key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
+            domain: Domain::APP,
+            nspace: -1,
+            alias: Some(alias.to_string()),
+            blob: None,
+        }));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     unsafe {
-        run_as::run_as(TARGET_CTX, Uid::from_raw(uid2), Gid::from_raw(gid2), move || {
-            let keystore2_inst = get_keystore_service();
-            let result =
-                key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(alias.to_string()),
-                    blob: None,
-                }));
-            assert!(result.is_err());
-            assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
-        });
+        run_as::run_as_app(uid2, gid2, get_key_fn);
     };
 
     // Notify the child process (client#1) to resume and finish.
@@ -509,5 +514,5 @@
     );
 
     // Delete the generated key blob.
-    sl.binder.deleteKey(&key_metadata.key).unwrap();
+    sl.delete_key(&key_metadata.key).unwrap();
 }
diff --git a/keystore2/tests/keystore2_client_grant_key_tests.rs b/keystore2/tests/keystore2_client_grant_key_tests.rs
index 50b87b9..c171ab1 100644
--- a/keystore2/tests/keystore2_client_grant_key_tests.rs
+++ b/keystore2/tests/keystore2_client_grant_key_tests.rs
@@ -19,20 +19,35 @@
     Digest::Digest, KeyPurpose::KeyPurpose,
 };
 use android_system_keystore2::aidl::android::system::keystore2::{
-    Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission,
-    ResponseCode::ResponseCode,
+    Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor,
+    KeyEntryResponse::KeyEntryResponse, KeyPermission::KeyPermission, ResponseCode::ResponseCode,
 };
 use keystore2_test_utils::{
-    authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, SecLevel,
+    authorizations, get_keystore_service, key_generations,
+    key_generations::{map_ks_error, Error},
+    run_as, SecLevel,
 };
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
 use rustutils::users::AID_USER_OFFSET;
 
-/// Generate an EC signing key and grant it to the user with given access vector.
-fn generate_ec_key_and_grant_to_user(
-    grantee_uid: i32,
+/// Produce a [`KeyDescriptor`] for a granted key.
+fn granted_key_descriptor(nspace: i64) -> KeyDescriptor {
+    KeyDescriptor { domain: Domain::GRANT, nspace, alias: None, blob: None }
+}
+
+fn get_granted_key(
+    ks2: &binder::Strong<dyn IKeystoreService>,
+    nspace: i64,
+) -> Result<KeyEntryResponse, Error> {
+    map_ks_error(ks2.getKeyEntry(&granted_key_descriptor(nspace)))
+}
+
+/// Generate an EC signing key in the SELINUX domain and grant it to the user with given access
+/// vector.
+fn generate_and_grant_selinux_key(
+    grantee_uid: u32,
     access_vector: i32,
-) -> binder::Result<KeyDescriptor> {
+) -> Result<KeyDescriptor, Error> {
     let sl = SecLevel::tee();
     let alias = format!("{}{}", "ks_grant_test_key_1", getuid());
 
@@ -45,204 +60,287 @@
     )
     .unwrap();
 
-    sl.keystore2.grant(&key_metadata.key, grantee_uid, access_vector)
+    map_ks_error(sl.keystore2.grant(
+        &key_metadata.key,
+        grantee_uid.try_into().unwrap(),
+        access_vector,
+    ))
 }
 
-fn load_grant_key_and_perform_sign_operation(
-    sl: &SecLevel,
-    grant_key_nspace: i64,
-) -> Result<(), binder::Status> {
-    let key_entry_response = sl.keystore2.getKeyEntry(&KeyDescriptor {
-        domain: Domain::GRANT,
-        nspace: grant_key_nspace,
-        alias: None,
-        blob: None,
-    })?;
+/// Use a granted key to perform a signing operation.
+fn sign_with_granted_key(grant_key_nspace: i64) -> Result<(), Error> {
+    let sl = SecLevel::tee();
+    let key_entry_response = get_granted_key(&sl.keystore2, grant_key_nspace)?;
 
     // Perform sample crypto operation using granted key.
-    let op_response = sl.binder.createOperation(
+    let op_response = map_ks_error(sl.binder.createOperation(
         &key_entry_response.metadata.key,
         &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256),
         false,
-    )?;
+    ))?;
 
     assert!(op_response.iOperation.is_some());
     assert_eq!(
         Ok(()),
-        key_generations::map_ks_error(perform_sample_sign_operation(
-            &op_response.iOperation.unwrap()
-        ))
+        map_ks_error(perform_sample_sign_operation(&op_response.iOperation.unwrap()))
     );
 
     Ok(())
 }
 
-/// Try to grant a key with permission that does not map to any of the `KeyPermission` values.
-/// An error is expected with values that does not map to set of permissions listed in
+/// Try to grant an SELINUX key with permission that does not map to any of the `KeyPermission`
+/// values.  An error is expected with values that does not map to set of permissions listed in
 /// `KeyPermission`.
 #[test]
-fn keystore2_grant_key_with_invalid_perm_expecting_syserror() {
+fn grant_selinux_key_with_invalid_perm() {
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     let grantee_uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     let invalid_access_vector = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0 << 19;
 
-    let result = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
-        grantee_uid.try_into().unwrap(),
-        invalid_access_vector,
-    ));
+    let result = generate_and_grant_selinux_key(grantee_uid, invalid_access_vector);
     assert!(result.is_err());
     assert_eq!(Error::Rc(ResponseCode::SYSTEM_ERROR), result.unwrap_err());
 }
 
-/// Try to grant a key with empty access vector `KeyPermission::NONE`, should be able to grant a
-/// key with empty access vector successfully. In grantee context try to use the granted key, it
-/// should fail to load the key with permission denied error.
+/// Try to grant an SELINUX key with empty access vector `KeyPermission::NONE`, should be able to
+/// grant a key with empty access vector successfully. In grantee context try to use the granted
+/// key, it should fail to load the key with permission denied error.
 #[test]
-fn keystore2_grant_key_with_perm_none() {
-    static TARGET_SU_CTX: &str = "u:r:su:s0";
-
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn grant_selinux_key_with_perm_none() {
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static GRANTEE_GID: u32 = GRANTEE_UID;
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let empty_access_vector = KeyPermission::NONE.0;
+    let grantor_fn = || {
+        let empty_access_vector = KeyPermission::NONE.0;
 
-            let grant_key = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
-                GRANTEE_UID.try_into().unwrap(),
-                empty_access_vector,
-            ))
-            .unwrap();
+        let grant_key = generate_and_grant_selinux_key(GRANTEE_UID, empty_access_vector).unwrap();
 
-            assert_eq!(grant_key.domain, Domain::GRANT);
+        assert_eq!(grant_key.domain, Domain::GRANT);
 
-            grant_key.nspace
-        })
+        grant_key.nspace
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
     // In grantee context try to load the key, it should fail to load the granted key as it is
     // granted with empty access vector.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
 
-                let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
-                    domain: Domain::GRANT,
-                    nspace: grant_key_nspace,
-                    alias: None,
-                    blob: None,
-                }));
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
-            },
-        )
+        let result = get_granted_key(&keystore2, grant_key_nspace);
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
 }
 
-/// Grant a key to the user (grantee) with `GET_INFO|USE` key permissions. Verify whether grantee
-/// can succeed in loading the granted key and try to perform simple operation using this granted
-/// key. Grantee should be able to load the key and use the key to perform crypto operation
+/// Grant an SELINUX key to the user (grantee) with `GET_INFO|USE` key permissions. Verify whether
+/// grantee can succeed in loading the granted key and try to perform simple operation using this
+/// granted key. Grantee should be able to load the key and use the key to perform crypto operation
 /// successfully. Try to delete the granted key in grantee context where it is expected to fail to
 /// delete it as `DELETE` permission is not granted.
 #[test]
-fn keystore2_grant_get_info_use_key_perm() {
-    static TARGET_SU_CTX: &str = "u:r:su:s0";
-
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn grant_selinux_key_get_info_use_perms() {
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static GRANTEE_GID: u32 = GRANTEE_UID;
 
     // Generate a key and grant it to a user with GET_INFO|USE key permissions.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
-            let grant_key = key_generations::map_ks_error(generate_ec_key_and_grant_to_user(
-                GRANTEE_UID.try_into().unwrap(),
-                access_vector,
-            ))
-            .unwrap();
+    let grantor_fn = || {
+        let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
+        let grant_key = generate_and_grant_selinux_key(GRANTEE_UID, access_vector).unwrap();
 
-            assert_eq!(grant_key.domain, Domain::GRANT);
+        assert_eq!(grant_key.domain, Domain::GRANT);
 
-            grant_key.nspace
-        })
+        grant_key.nspace
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
     // In grantee context load the key and try to perform crypto operation.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let sl = SecLevel::tee();
+    let grantee_fn = move || {
+        let sl = SecLevel::tee();
 
-                // Load the granted key.
-                let key_entry_response = sl
-                    .keystore2
-                    .getKeyEntry(&KeyDescriptor {
-                        domain: Domain::GRANT,
-                        nspace: grant_key_nspace,
-                        alias: None,
-                        blob: None,
-                    })
-                    .unwrap();
+        // Load the granted key.
+        let key_entry_response = get_granted_key(&sl.keystore2, grant_key_nspace).unwrap();
 
-                // Perform sample crypto operation using granted key.
-                let op_response = sl
-                    .binder
-                    .createOperation(
-                        &key_entry_response.metadata.key,
-                        &authorizations::AuthSetBuilder::new()
-                            .purpose(KeyPurpose::SIGN)
-                            .digest(Digest::SHA_2_256),
-                        false,
-                    )
-                    .unwrap();
-                assert!(op_response.iOperation.is_some());
-                assert_eq!(
-                    Ok(()),
-                    key_generations::map_ks_error(perform_sample_sign_operation(
-                        &op_response.iOperation.unwrap()
-                    ))
-                );
+        // Perform sample crypto operation using granted key.
+        let op_response = sl
+            .binder
+            .createOperation(
+                &key_entry_response.metadata.key,
+                &authorizations::AuthSetBuilder::new()
+                    .purpose(KeyPurpose::SIGN)
+                    .digest(Digest::SHA_2_256),
+                false,
+            )
+            .unwrap();
+        assert!(op_response.iOperation.is_some());
+        assert_eq!(
+            Ok(()),
+            map_ks_error(perform_sample_sign_operation(&op_response.iOperation.unwrap()))
+        );
 
-                // Try to delete the key, it is expected to be fail with permission denied error.
-                let result =
-                    key_generations::map_ks_error(sl.keystore2.deleteKey(&KeyDescriptor {
-                        domain: Domain::GRANT,
-                        nspace: grant_key_nspace,
-                        alias: None,
-                        blob: None,
-                    }));
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
-            },
-        )
+        // Try to delete the key, it is expected to be fail with permission denied error.
+        let result =
+            map_ks_error(sl.keystore2.deleteKey(&granted_key_descriptor(grant_key_nspace)));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
 }
 
-/// Grant a key to the user with DELETE access. In grantee context load the key and delete it.
+/// Grant an SELINUX key to the user (grantee) with just `GET_INFO` key permissions. Verify whether
+/// grantee can succeed in loading the granted key and try to perform simple operation using this
+/// granted key.
+#[test]
+fn grant_selinux_key_get_info_only() {
+    const USER_ID: u32 = 99;
+    const APPLICATION_ID: u32 = 10001;
+    static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+    static GRANTEE_GID: u32 = GRANTEE_UID;
+
+    // Generate a key and grant it to a user with (just) GET_INFO key permissions.
+    let grantor_fn = || {
+        let access_vector = KeyPermission::GET_INFO.0;
+        let grant_key = generate_and_grant_selinux_key(GRANTEE_UID, access_vector).unwrap();
+
+        assert_eq!(grant_key.domain, Domain::GRANT);
+
+        grant_key.nspace
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
+    // In grantee context load the key and try to perform crypto operation.
+    let grantee_fn = move || {
+        let sl = SecLevel::tee();
+
+        // Load the granted key.
+        let key_entry_response = get_granted_key(&sl.keystore2, grant_key_nspace)
+            .expect("failed to get info for granted key");
+
+        // Attempt to perform sample crypto operation using granted key, now identified by <KEY_ID,
+        // key_id>.
+        let result = map_ks_error(
+            sl.binder.createOperation(
+                &key_entry_response.metadata.key,
+                &authorizations::AuthSetBuilder::new()
+                    .purpose(KeyPurpose::SIGN)
+                    .digest(Digest::SHA_2_256),
+                false,
+            ),
+        );
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+
+        // Try to delete the key using a <GRANT, grant_id> descriptor.
+        let result =
+            map_ks_error(sl.keystore2.deleteKey(&granted_key_descriptor(grant_key_nspace)));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+
+        // Try to delete the key using a <KEY_ID, key_id> descriptor.
+        let result = map_ks_error(sl.keystore2.deleteKey(&key_entry_response.metadata.key));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+}
+
+/// Grant an APP key to the user (grantee) with just `GET_INFO` key permissions. Verify whether
+/// grantee can succeed in loading the granted key and try to perform simple operation using this
+/// granted key.
+#[test]
+fn grant_app_key_get_info_only() {
+    const USER_ID: u32 = 99;
+    const APPLICATION_ID: u32 = 10001;
+    static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+    static GRANTEE_GID: u32 = GRANTEE_UID;
+    static ALIAS: &str = "ks_grant_key_info_only";
+
+    // Generate a key and grant it to a user with (just) GET_INFO key permissions.
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let access_vector = KeyPermission::GET_INFO.0;
+        let mut grant_keys = generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(ALIAS.to_string()),
+            vec![GRANTEE_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap();
+
+        grant_keys.remove(0)
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
+    // In grantee context load the key and try to perform crypto operation.
+    let grantee_fn = move || {
+        let sl = SecLevel::tee();
+
+        // Load the granted key.
+        let key_entry_response = get_granted_key(&sl.keystore2, grant_key_nspace)
+            .expect("failed to get info for granted key");
+
+        // Attempt to perform sample crypto operation using granted key, now identified by <KEY_ID,
+        // key_id>.
+        let result = map_ks_error(
+            sl.binder.createOperation(
+                &key_entry_response.metadata.key,
+                &authorizations::AuthSetBuilder::new()
+                    .purpose(KeyPurpose::SIGN)
+                    .digest(Digest::SHA_2_256),
+                false,
+            ),
+        );
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+
+        // Try to delete the key using a <GRANT, grant_id> descriptor.
+        let result =
+            map_ks_error(sl.keystore2.deleteKey(&granted_key_descriptor(grant_key_nspace)));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+
+        // Try to delete the key using a <KEY_ID, key_id> descriptor.
+        let result = map_ks_error(sl.keystore2.deleteKey(&key_entry_response.metadata.key));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+}
+
+/// Grant an APP key to the user with DELETE access. In grantee context load the key and delete it.
 /// Verify that grantee should succeed in deleting the granted key and in grantor context test
 /// should fail to find the key with error response `KEY_NOT_FOUND`.
 #[test]
-fn keystore2_grant_delete_key_success() {
-    static GRANTOR_SU_CTX: &str = "u:r:su:s0";
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn grant_app_key_delete_success() {
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
@@ -250,70 +348,63 @@
     static ALIAS: &str = "ks_grant_key_delete_success";
 
     // Generate a key and grant it to a user with DELETE permission.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let access_vector = KeyPermission::DELETE.0;
-            let mut grant_keys = generate_ec_key_and_grant_to_users(
-                &sl,
-                Some(ALIAS.to_string()),
-                vec![GRANTEE_UID.try_into().unwrap()],
-                access_vector,
-            )
-            .unwrap();
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let access_vector = KeyPermission::DELETE.0;
+        let mut grant_keys = generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(ALIAS.to_string()),
+            vec![GRANTEE_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap();
 
-            grant_keys.remove(0)
-        })
+        grant_keys.remove(0)
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
     // Grantee context, delete the key.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
-                keystore2
-                    .deleteKey(&KeyDescriptor {
-                        domain: Domain::GRANT,
-                        nspace: grant_key_nspace,
-                        alias: None,
-                        blob: None,
-                    })
-                    .unwrap();
-            },
-        )
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        keystore2.deleteKey(&granted_key_descriptor(grant_key_nspace)).unwrap();
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+
     // Verify whether key got deleted in grantor's context.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), move || {
-            let keystore2_inst = get_keystore_service();
-            let result =
-                key_generations::map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(ALIAS.to_string()),
-                    blob: None,
-                }));
-            assert!(result.is_err());
-            assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
-        })
+    let grantor_fn = move || {
+        let keystore2_inst = get_keystore_service();
+        let result = map_ks_error(keystore2_inst.getKeyEntry(&KeyDescriptor {
+            domain: Domain::APP,
+            nspace: -1,
+            alias: Some(ALIAS.to_string()),
+            blob: None,
+        }));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_root(grantor_fn) };
 }
 
-/// Grant a key to the user. In grantee context load the granted key and try to grant it to second
-/// user. Test should fail with a response code `PERMISSION_DENIED` to grant a key to second user
-/// from grantee context. Test should make sure second grantee should not have a access to granted
-/// key.
+/// Grant an APP key to the user. In grantee context load the granted key and try to grant it to
+/// second user. Test should fail with a response code `PERMISSION_DENIED` to grant a key to second
+/// user from grantee context. Test should make sure second grantee should not have a access to
+/// granted key.
 #[test]
-fn keystore2_grant_key_fails_with_permission_denied() {
-    static GRANTOR_SU_CTX: &str = "u:r:su:s0";
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn grant_granted_app_key_fails() {
+    const GRANTOR_USER_ID: u32 = 97;
+    const GRANTOR_APPLICATION_ID: u32 = 10003;
+    static GRANTOR_UID: u32 = GRANTOR_USER_ID * AID_USER_OFFSET + GRANTOR_APPLICATION_ID;
+    static GRANTOR_GID: u32 = GRANTOR_UID;
+
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
@@ -325,83 +416,148 @@
     static SEC_GRANTEE_GID: u32 = SEC_GRANTEE_UID;
 
     // Generate a key and grant it to a user with GET_INFO permission.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let access_vector = KeyPermission::GET_INFO.0;
-            let alias = format!("ks_grant_perm_denied_key_{}", getuid());
-            let mut grant_keys = generate_ec_key_and_grant_to_users(
-                &sl,
-                Some(alias),
-                vec![GRANTEE_UID.try_into().unwrap()],
-                access_vector,
-            )
-            .unwrap();
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let access_vector = KeyPermission::GET_INFO.0;
+        let alias = format!("ks_grant_perm_denied_key_{}", getuid());
+        let mut grant_keys = generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(alias),
+            vec![GRANTEE_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap();
 
-            grant_keys.remove(0)
-        })
+        grant_keys.remove(0)
     };
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_app(GRANTOR_UID, GRANTOR_GID, grantor_fn) };
 
     // Grantee context, load the granted key and try to grant it to `SEC_GRANTEE_UID` grantee.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
-                let access_vector = KeyPermission::GET_INFO.0;
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        let access_vector = KeyPermission::GET_INFO.0;
 
-                let key_entry_response = keystore2
-                    .getKeyEntry(&KeyDescriptor {
-                        domain: Domain::GRANT,
-                        nspace: grant_key_nspace,
-                        alias: None,
-                        blob: None,
-                    })
-                    .unwrap();
+        // Try to grant when identifying the key with <GRANT, grant_nspace>.
+        let result = map_ks_error(keystore2.grant(
+            &granted_key_descriptor(grant_key_nspace),
+            SEC_GRANTEE_UID.try_into().unwrap(),
+            access_vector,
+        ));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::SYSTEM_ERROR), result.unwrap_err());
 
-                let result = key_generations::map_ks_error(keystore2.grant(
-                    &key_entry_response.metadata.key,
-                    SEC_GRANTEE_UID.try_into().unwrap(),
-                    access_vector,
-                ));
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
-            },
-        )
+        // Load the key info and try to grant when identifying the key with <KEY_ID, keyid>.
+        let key_entry_response = get_granted_key(&keystore2, grant_key_nspace).unwrap();
+        let result = map_ks_error(keystore2.grant(
+            &key_entry_response.metadata.key,
+            SEC_GRANTEE_UID.try_into().unwrap(),
+            access_vector,
+        ));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+
     // Make sure second grantee shouldn't have access to the above granted key.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(SEC_GRANTEE_UID),
-            Gid::from_raw(SEC_GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
-
-                let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
-                    domain: Domain::GRANT,
-                    nspace: grant_key_nspace,
-                    alias: None,
-                    blob: None,
-                }));
-
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
-            },
-        )
+    let grantee2_fn = move || {
+        let keystore2 = get_keystore_service();
+        let result = get_granted_key(&keystore2, grant_key_nspace);
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(SEC_GRANTEE_UID, SEC_GRANTEE_GID, grantee2_fn) };
 }
 
-/// Try to grant a key with `GRANT` access. Keystore2 system shouldn't allow to grant a key with
-/// `GRANT` access. Test should fail to grant a key with `PERMISSION_DENIED` error response code.
+/// Grant an APP key to one user, from a normal user. Check that grantee context can load the
+/// granted key, but that a second unrelated context cannot.
 #[test]
-fn keystore2_grant_key_fails_with_grant_perm_expect_perm_denied() {
+fn grant_app_key_only_to_grantee() {
+    const GRANTOR_USER_ID: u32 = 97;
+    const GRANTOR_APPLICATION_ID: u32 = 10003;
+    static GRANTOR_UID: u32 = GRANTOR_USER_ID * AID_USER_OFFSET + GRANTOR_APPLICATION_ID;
+    static GRANTOR_GID: u32 = GRANTOR_UID;
+
+    const USER_ID: u32 = 99;
+    const APPLICATION_ID: u32 = 10001;
+    static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+    static GRANTEE_GID: u32 = GRANTEE_UID;
+
+    const SEC_USER_ID: u32 = 98;
+    const SEC_APPLICATION_ID: u32 = 10001;
+    static SEC_GRANTEE_UID: u32 = SEC_USER_ID * AID_USER_OFFSET + SEC_APPLICATION_ID;
+    static SEC_GRANTEE_GID: u32 = SEC_GRANTEE_UID;
+
+    // Child function to generate a key and grant it to a user with `GET_INFO` permission.
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let access_vector = KeyPermission::GET_INFO.0;
+        let alias = format!("ks_grant_single_{}", getuid());
+        let mut grant_keys = generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(alias),
+            vec![GRANTEE_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap();
+
+        grant_keys.remove(0)
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    let grant_key_nspace = unsafe { run_as::run_as_app(GRANTOR_UID, GRANTOR_GID, grantor_fn) };
+
+    // Child function for the grantee context: can load the granted key.
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        let rsp = get_granted_key(&keystore2, grant_key_nspace).expect("failed to get granted key");
+
+        // Return the underlying key ID to simulate an ID leak.
+        assert_eq!(rsp.metadata.key.domain, Domain::KEY_ID);
+        rsp.metadata.key.nspace
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    let key_id = unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+
+    // Second context does not have access to the above granted key, because it's identified
+    // by <uid, grant_nspace> and the implicit uid value is different.  Also, even if the
+    // second context gets hold of the key ID somehow, that also doesn't work.
+    let non_grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        let result = get_granted_key(&keystore2, grant_key_nspace);
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+
+        let result = map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
+            domain: Domain::KEY_ID,
+            nspace: key_id,
+            alias: None,
+            blob: None,
+        }));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_app(SEC_GRANTEE_UID, SEC_GRANTEE_GID, non_grantee_fn) };
+}
+
+/// Try to grant an APP key with `GRANT` access. Keystore2 system shouldn't allow to grant a key
+/// with `GRANT` access. Test should fail to grant a key with `PERMISSION_DENIED` error response
+/// code.
+#[test]
+fn grant_app_key_with_grant_perm_fails() {
     let sl = SecLevel::tee();
     let access_vector = KeyPermission::GRANT.0;
     let alias = format!("ks_grant_access_vec_key_{}", getuid());
@@ -409,7 +565,7 @@
     let application_id = 10001;
     let grantee_uid = user_id * AID_USER_OFFSET + application_id;
 
-    let result = key_generations::map_ks_error(generate_ec_key_and_grant_to_users(
+    let result = map_ks_error(generate_ec_key_and_grant_to_users(
         &sl,
         Some(alias),
         vec![grantee_uid.try_into().unwrap()],
@@ -419,10 +575,10 @@
     assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
 }
 
-/// Try to grant a non-existing key to the user. Test should fail with `KEY_NOT_FOUND` error
+/// Try to grant a non-existing SELINUX key to the user. Test should fail with `KEY_NOT_FOUND` error
 /// response.
 #[test]
-fn keystore2_grant_fails_with_non_existing_key_expect_key_not_found_err() {
+fn grant_fails_with_non_existing_selinux_key() {
     let keystore2 = get_keystore_service();
     let alias = format!("ks_grant_test_non_existing_key_5_{}", getuid());
     let user_id = 98;
@@ -430,7 +586,7 @@
     let grantee_uid = user_id * AID_USER_OFFSET + application_id;
     let access_vector = KeyPermission::GET_INFO.0;
 
-    let result = key_generations::map_ks_error(keystore2.grant(
+    let result = map_ks_error(keystore2.grant(
         &KeyDescriptor {
             domain: Domain::SELINUX,
             nspace: key_generations::SELINUX_SHELL_NAMESPACE,
@@ -444,71 +600,56 @@
     assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
 }
 
-/// Grant a key to the user and immediately ungrant the granted key. In grantee context try to load
+/// Grant an APP key to the user and immediately ungrant the granted key. In grantee context try to load
 /// the key. Grantee should fail to load the ungranted key with `KEY_NOT_FOUND` error response.
 #[test]
-fn keystore2_ungrant_key_success() {
-    static GRANTOR_SU_CTX: &str = "u:r:su:s0";
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn ungrant_app_key_success() {
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static GRANTEE_GID: u32 = GRANTEE_UID;
 
     // Generate a key and grant it to a user with GET_INFO permission.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let alias = format!("ks_ungrant_test_key_1{}", getuid());
-            let access_vector = KeyPermission::GET_INFO.0;
-            let mut grant_keys = generate_ec_key_and_grant_to_users(
-                &sl,
-                Some(alias.to_string()),
-                vec![GRANTEE_UID.try_into().unwrap()],
-                access_vector,
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let alias = format!("ks_ungrant_test_key_1{}", getuid());
+        let access_vector = KeyPermission::GET_INFO.0;
+        let mut grant_keys = generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(alias.to_string()),
+            vec![GRANTEE_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap();
+
+        let grant_key_nspace = grant_keys.remove(0);
+
+        // Ungrant above granted key.
+        sl.keystore2
+            .ungrant(
+                &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None },
+                GRANTEE_UID.try_into().unwrap(),
             )
             .unwrap();
 
-            let grant_key_nspace = grant_keys.remove(0);
-
-            // Ungrant above granted key.
-            sl.keystore2
-                .ungrant(
-                    &KeyDescriptor {
-                        domain: Domain::APP,
-                        nspace: -1,
-                        alias: Some(alias),
-                        blob: None,
-                    },
-                    GRANTEE_UID.try_into().unwrap(),
-                )
-                .unwrap();
-
-            grant_key_nspace
-        })
+        grant_key_nspace
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
     // Grantee context, try to load the ungranted key.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
-                let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
-                    domain: Domain::GRANT,
-                    nspace: grant_key_nspace,
-                    alias: None,
-                    blob: None,
-                }));
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
-            },
-        )
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        let result = get_granted_key(&keystore2, grant_key_nspace);
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
 }
 
 /// Generate a key, grant it to the user and then delete the granted key. Try to ungrant
@@ -517,94 +658,78 @@
 /// key in grantee context. Test should fail to load the granted key in grantee context as the
 /// associated key is deleted from grantor context.
 #[test]
-fn keystore2_ungrant_fails_with_non_existing_key_expect_key_not_found_error() {
-    static GRANTOR_SU_CTX: &str = "u:r:su:s0";
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
+fn ungrant_deleted_app_key_fails() {
     const APPLICATION_ID: u32 = 10001;
     const USER_ID: u32 = 99;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static GRANTEE_GID: u32 = GRANTEE_UID;
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let alias = format!("{}{}", "ks_grant_delete_ungrant_test_key_1", getuid());
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let alias = format!("{}{}", "ks_grant_delete_ungrant_test_key_1", getuid());
 
-            let key_metadata = key_generations::generate_ec_p256_signing_key(
-                &sl,
-                Domain::SELINUX,
-                key_generations::SELINUX_SHELL_NAMESPACE,
-                Some(alias.to_string()),
-                None,
-            )
+        let key_metadata = key_generations::generate_ec_p256_signing_key(
+            &sl,
+            Domain::SELINUX,
+            key_generations::SELINUX_SHELL_NAMESPACE,
+            Some(alias.to_string()),
+            None,
+        )
+        .unwrap();
+
+        let access_vector = KeyPermission::GET_INFO.0;
+        let grant_key = sl
+            .keystore2
+            .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
             .unwrap();
+        assert_eq!(grant_key.domain, Domain::GRANT);
 
-            let access_vector = KeyPermission::GET_INFO.0;
-            let grant_key = sl
-                .keystore2
-                .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
-                .unwrap();
-            assert_eq!(grant_key.domain, Domain::GRANT);
+        // Delete above granted key.
+        sl.keystore2.deleteKey(&key_metadata.key).unwrap();
 
-            // Delete above granted key.
-            sl.keystore2.deleteKey(&key_metadata.key).unwrap();
+        // Try to ungrant above granted key.
+        let result =
+            map_ks_error(sl.keystore2.ungrant(&key_metadata.key, GRANTEE_UID.try_into().unwrap()));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
 
-            // Try to ungrant above granted key.
-            let result = key_generations::map_ks_error(
-                sl.keystore2.ungrant(&key_metadata.key, GRANTEE_UID.try_into().unwrap()),
-            );
-            assert!(result.is_err());
-            assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+        // Generate a new key with the same alias and try to access the earlier granted key
+        // in grantee context.
+        let result = key_generations::generate_ec_p256_signing_key(
+            &sl,
+            Domain::SELINUX,
+            key_generations::SELINUX_SHELL_NAMESPACE,
+            Some(alias),
+            None,
+        );
+        assert!(result.is_ok());
 
-            // Generate a new key with the same alias and try to access the earlier granted key
-            // in grantee context.
-            let result = key_generations::generate_ec_p256_signing_key(
-                &sl,
-                Domain::SELINUX,
-                key_generations::SELINUX_SHELL_NAMESPACE,
-                Some(alias),
-                None,
-            );
-            assert!(result.is_ok());
-
-            grant_key.nspace
-        })
+        grant_key.nspace
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
     // Make sure grant did not persist, try to access the earlier granted key in grantee context.
     // Grantee context should fail to load the granted key as its associated key is deleted in
     // grantor context.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
-
-                let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
-                    domain: Domain::GRANT,
-                    nspace: grant_key_nspace,
-                    alias: None,
-                    blob: None,
-                }));
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
-            },
-        )
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        let result = get_granted_key(&keystore2, grant_key_nspace);
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
 }
 
 /// Grant a key to multiple users. Verify that all grantees should succeed in loading the key and
 /// use it for performing an operation successfully.
 #[test]
-fn keystore2_grant_key_to_multi_users_success() {
-    static GRANTOR_SU_CTX: &str = "u:r:su:s0";
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
+fn grant_app_key_to_multi_users_success() {
     const APPLICATION_ID: u32 = 10001;
     const USER_ID_1: u32 = 99;
     static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID;
@@ -615,46 +740,34 @@
     static GRANTEE_2_GID: u32 = GRANTEE_2_UID;
 
     // Generate a key and grant it to multiple users with GET_INFO|USE permissions.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let mut grant_keys = unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let alias = format!("ks_grant_test_key_2{}", getuid());
-            let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let alias = format!("ks_grant_test_key_2{}", getuid());
+        let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0;
 
-            generate_ec_key_and_grant_to_users(
-                &sl,
-                Some(alias),
-                vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()],
-                access_vector,
-            )
-            .unwrap()
-        })
+        generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(alias),
+            vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap()
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let mut grant_keys = unsafe { run_as::run_as_root(grantor_fn) };
+
     for (grantee_uid, grantee_gid) in
         &[(GRANTEE_1_UID, GRANTEE_1_GID), (GRANTEE_2_UID, GRANTEE_2_GID)]
     {
         let grant_key_nspace = grant_keys.remove(0);
-        // SAFETY: The test is run in a separate process with no other threads.
-        unsafe {
-            run_as::run_as(
-                GRANTEE_CTX,
-                Uid::from_raw(*grantee_uid),
-                Gid::from_raw(*grantee_gid),
-                move || {
-                    let sl = SecLevel::tee();
-
-                    assert_eq!(
-                        Ok(()),
-                        key_generations::map_ks_error(load_grant_key_and_perform_sign_operation(
-                            &sl,
-                            grant_key_nspace
-                        ))
-                    );
-                },
-            )
+        let grantee_fn = move || {
+            assert_eq!(Ok(()), sign_with_granted_key(grant_key_nspace));
         };
+        // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+        // `--test-threads=1`), and nothing yet done with binder.
+        unsafe { run_as::run_as_app(*grantee_uid, *grantee_gid, grantee_fn) };
     }
 }
 
@@ -662,10 +775,7 @@
 /// use the key and delete it. Try to load the granted key in another grantee context. Test should
 /// fail to load the granted key with `KEY_NOT_FOUND` error response.
 #[test]
-fn keystore2_grant_key_to_multi_users_delete_fails_with_key_not_found_error() {
-    static GRANTOR_SU_CTX: &str = "u:r:su:s0";
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
+fn grant_app_key_to_multi_users_delete_then_key_not_found() {
     const USER_ID_1: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID;
@@ -676,76 +786,50 @@
     static GRANTEE_2_GID: u32 = GRANTEE_2_UID;
 
     // Generate a key and grant it to multiple users with GET_INFO permission.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let mut grant_keys = unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let alias = format!("ks_grant_test_key_2{}", getuid());
-            let access_vector =
-                KeyPermission::GET_INFO.0 | KeyPermission::USE.0 | KeyPermission::DELETE.0;
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let alias = format!("ks_grant_test_key_2{}", getuid());
+        let access_vector =
+            KeyPermission::GET_INFO.0 | KeyPermission::USE.0 | KeyPermission::DELETE.0;
 
-            generate_ec_key_and_grant_to_users(
-                &sl,
-                Some(alias),
-                vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()],
-                access_vector,
-            )
-            .unwrap()
-        })
+        generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(alias),
+            vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap()
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let mut grant_keys = unsafe { run_as::run_as_root(grantor_fn) };
+
     // Grantee #1 context
     let grant_key1_nspace = grant_keys.remove(0);
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_1_UID),
-            Gid::from_raw(GRANTEE_1_GID),
-            move || {
-                let sl = SecLevel::tee();
+    let grantee1_fn = move || {
+        assert_eq!(Ok(()), sign_with_granted_key(grant_key1_nspace));
 
-                assert_eq!(
-                    Ok(()),
-                    key_generations::map_ks_error(load_grant_key_and_perform_sign_operation(
-                        &sl,
-                        grant_key1_nspace
-                    ))
-                );
-
-                // Delete the granted key.
-                sl.keystore2
-                    .deleteKey(&KeyDescriptor {
-                        domain: Domain::GRANT,
-                        nspace: grant_key1_nspace,
-                        alias: None,
-                        blob: None,
-                    })
-                    .unwrap();
-            },
-        )
+        // Delete the granted key.
+        get_keystore_service().deleteKey(&granted_key_descriptor(grant_key1_nspace)).unwrap();
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_1_UID, GRANTEE_1_GID, grantee1_fn) };
+
     // Grantee #2 context
     let grant_key2_nspace = grant_keys.remove(0);
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_2_UID),
-            Gid::from_raw(GRANTEE_2_GID),
-            move || {
-                let keystore2 = get_keystore_service();
+    let grantee2_fn = move || {
+        let keystore2 = get_keystore_service();
 
-                let result = key_generations::map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
-                    domain: Domain::GRANT,
-                    nspace: grant_key2_nspace,
-                    alias: None,
-                    blob: None,
-                }));
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
-            },
-        )
+        let result =
+            map_ks_error(keystore2.getKeyEntry(&granted_key_descriptor(grant_key2_nspace)));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_2_UID, GRANTEE_2_GID, grantee2_fn) };
 }
diff --git a/keystore2/tests/keystore2_client_import_keys_tests.rs b/keystore2/tests/keystore2_client_import_keys_tests.rs
index b06303e..f3a267b 100644
--- a/keystore2/tests/keystore2_client_import_keys_tests.rs
+++ b/keystore2/tests/keystore2_client_import_keys_tests.rs
@@ -247,8 +247,8 @@
 }
 
 /// Try to import a key with multiple purposes. Test should fail to import a key with
-/// `INCOMPATIBLE_PURPOSE` error code. If the backend is `keymaster` then `importKey` shall be
-/// successful.
+/// `INCOMPATIBLE_PURPOSE` error code. If the backend is `keymaster` or KeyMint-version-1 then
+/// `importKey` shall be successful.
 #[test]
 fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() {
     let sl = SecLevel::tee();
@@ -276,8 +276,14 @@
     ));
 
     if sl.is_keymint() {
-        assert!(result.is_err());
-        assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+        if sl.get_keymint_version() >= 2 {
+            // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined
+            // with other key purposes.  However, this was not checked at the time
+            // so we can only be strict about checking this for implementations of KeyMint
+            // version 2 and above.
+            assert!(result.is_err());
+            assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err());
+        }
     } else {
         assert!(result.is_ok());
     }
diff --git a/keystore2/tests/keystore2_client_key_agreement_tests.rs b/keystore2/tests/keystore2_client_key_agreement_tests.rs
index 19f6210..6744b60 100644
--- a/keystore2/tests/keystore2_client_key_agreement_tests.rs
+++ b/keystore2/tests/keystore2_client_key_agreement_tests.rs
@@ -112,7 +112,7 @@
 test_ec_key_agree!(test_ec_p384_key_agreement, EcCurve::P_384);
 test_ec_key_agree!(test_ec_p521_key_agreement, EcCurve::P_521);
 
-/// Generate two EC keys with curve `CURVE_25519` from KeyMint and OpeanSSL.
+/// Generate two EC keys with curve `CURVE_25519` from KeyMint and OpenSSL.
 /// Perform local ECDH between them and verify that the derived secrets are the same.
 #[test]
 fn keystore2_ec_25519_agree_key_success() {
diff --git a/keystore2/tests/keystore2_client_keystore_engine_tests.rs b/keystore2/tests/keystore2_client_keystore_engine_tests.rs
index 01f8917..a4d7f2c 100644
--- a/keystore2/tests/keystore2_client_keystore_engine_tests.rs
+++ b/keystore2/tests/keystore2_client_keystore_engine_tests.rs
@@ -24,7 +24,6 @@
 use keystore2_test_utils::{
     authorizations::AuthSetBuilder, get_keystore_service, run_as, SecLevel,
 };
-use nix::unistd::{Gid, Uid};
 use openssl::x509::X509;
 use rustutils::users::AID_USER_OFFSET;
 
@@ -152,80 +151,65 @@
 }
 
 #[test]
-fn keystore2_perofrm_crypto_op_using_keystore2_engine_rsa_key_success() {
-    static TARGET_SU_CTX: &str = "u:r:su:s0";
-
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn keystore2_perform_crypto_op_using_keystore2_engine_rsa_key_success() {
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static GRANTEE_GID: u32 = GRANTEE_UID;
 
     // Generate a key and grant it to a user with GET_INFO|USE|DELETE key permissions.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let alias = "keystore2_engine_rsa_key";
-            generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap()
-        })
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let alias = "keystore2_engine_rsa_key";
+        generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap()
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
     // In grantee context load the key and try to perform crypto operation.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
-                perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
-            },
-        )
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
 }
 
 #[test]
-fn keystore2_perofrm_crypto_op_using_keystore2_engine_ec_key_success() {
-    static TARGET_SU_CTX: &str = "u:r:su:s0";
-
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn keystore2_perform_crypto_op_using_keystore2_engine_ec_key_success() {
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static GRANTEE_GID: u32 = GRANTEE_UID;
 
     // Generate a key and grant it to a user with GET_INFO|USE|DELETE key permissions.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let alias = "keystore2_engine_ec_test_key";
-            generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::EC).unwrap()
-        })
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let alias = "keystore2_engine_ec_test_key";
+        generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::EC).unwrap()
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
     // In grantee context load the key and try to perform crypto operation.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
-                perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
-            },
-        )
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
 }
 
 #[test]
-fn keystore2_perofrm_crypto_op_using_keystore2_engine_pem_pub_key_success() {
-    static TARGET_SU_CTX: &str = "u:r:su:s0";
-
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+fn keystore2_perform_crypto_op_using_keystore2_engine_pem_pub_key_success() {
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
@@ -233,46 +217,43 @@
 
     // Generate a key and re-encode it's certificate as PEM and update it and
     // grant it to a user with GET_INFO|USE|DELETE key permissions.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let grant_key_nspace = unsafe {
-        run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let alias = "keystore2_engine_rsa_pem_pub_key";
-            let grant_key_nspace =
-                generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap();
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let alias = "keystore2_engine_rsa_pem_pub_key";
+        let grant_key_nspace =
+            generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap();
 
-            // Update certificate with encodeed PEM data.
-            let key_entry_response = sl
-                .keystore2
-                .getKeyEntry(&KeyDescriptor {
-                    domain: Domain::APP,
-                    nspace: -1,
-                    alias: Some(alias.to_string()),
-                    blob: None,
-                })
-                .unwrap();
-            let cert_bytes = key_entry_response.metadata.certificate.as_ref().unwrap();
-            let cert = X509::from_der(cert_bytes.as_ref()).unwrap();
-            let cert_pem = cert.to_pem().unwrap();
-            sl.keystore2
-                .updateSubcomponent(&key_entry_response.metadata.key, Some(&cert_pem), None)
-                .expect("updateSubcomponent failed.");
+        // Update certificate with encodeed PEM data.
+        let key_entry_response = sl
+            .keystore2
+            .getKeyEntry(&KeyDescriptor {
+                domain: Domain::APP,
+                nspace: -1,
+                alias: Some(alias.to_string()),
+                blob: None,
+            })
+            .unwrap();
+        let cert_bytes = key_entry_response.metadata.certificate.as_ref().unwrap();
+        let cert = X509::from_der(cert_bytes.as_ref()).unwrap();
+        let cert_pem = cert.to_pem().unwrap();
+        sl.keystore2
+            .updateSubcomponent(&key_entry_response.metadata.key, Some(&cert_pem), None)
+            .expect("updateSubcomponent failed.");
 
-            grant_key_nspace
-        })
+        grant_key_nspace
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let grant_key_nspace = unsafe { run_as::run_as_root(grantor_fn) };
+
     // In grantee context load the key and try to perform crypto operation.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let keystore2 = get_keystore_service();
-                perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
-            },
-        )
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        perform_crypto_op_using_granted_key(&keystore2, grant_key_nspace);
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
 }
diff --git a/keystore2/tests/keystore2_client_list_entries_tests.rs b/keystore2/tests/keystore2_client_list_entries_tests.rs
index 539dac2..bb1d6cf 100644
--- a/keystore2/tests/keystore2_client_list_entries_tests.rs
+++ b/keystore2/tests/keystore2_client_list_entries_tests.rs
@@ -20,7 +20,7 @@
 use keystore2_test_utils::{
     get_keystore_service, key_generations, key_generations::Error, run_as, SecLevel,
 };
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
 use rustutils::users::AID_USER_OFFSET;
 use std::collections::HashSet;
 use std::fmt::Write;
@@ -51,103 +51,97 @@
 ///    context. GRANT keys shouldn't be part of this list.
 #[test]
 fn keystore2_list_entries_success() {
-    static GRANTOR_SU_CTX: &str = "u:r:su:s0";
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
     const USER_ID: u32 = 91;
     const APPLICATION_ID: u32 = 10006;
     static GRANTEE_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static GRANTEE_GID: u32 = GRANTEE_UID;
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
+    let gen_key_fn = || {
+        let sl = SecLevel::tee();
 
-            let alias = format!("list_entries_grant_key1_{}", getuid());
+        let alias = format!("list_entries_grant_key1_{}", getuid());
 
-            // Make sure there is no key exist with this `alias` in `SELINUX` domain and
-            // `SELINUX_SHELL_NAMESPACE` namespace.
-            if key_alias_exists(
-                &sl.keystore2,
-                Domain::SELINUX,
-                key_generations::SELINUX_SHELL_NAMESPACE,
-                alias.to_string(),
-            ) {
-                sl.keystore2
-                    .deleteKey(&KeyDescriptor {
-                        domain: Domain::SELINUX,
-                        nspace: key_generations::SELINUX_SHELL_NAMESPACE,
-                        alias: Some(alias.to_string()),
-                        blob: None,
-                    })
-                    .unwrap();
-            }
-
-            // Generate a key with above defined `alias`.
-            let key_metadata = key_generations::generate_ec_p256_signing_key(
-                &sl,
-                Domain::SELINUX,
-                key_generations::SELINUX_SHELL_NAMESPACE,
-                Some(alias.to_string()),
-                None,
-            )
-            .unwrap();
-
-            // Verify that above generated key entry is listed with domain SELINUX and
-            // namespace SELINUX_SHELL_NAMESPACE
-            assert!(key_alias_exists(
-                &sl.keystore2,
-                Domain::SELINUX,
-                key_generations::SELINUX_SHELL_NAMESPACE,
-                alias,
-            ));
-
-            // Grant a key with GET_INFO permission.
-            let access_vector = KeyPermission::GET_INFO.0;
+        // Make sure there is no key exist with this `alias` in `SELINUX` domain and
+        // `SELINUX_SHELL_NAMESPACE` namespace.
+        if key_alias_exists(
+            &sl.keystore2,
+            Domain::SELINUX,
+            key_generations::SELINUX_SHELL_NAMESPACE,
+            alias.to_string(),
+        ) {
             sl.keystore2
-                .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
+                .deleteKey(&KeyDescriptor {
+                    domain: Domain::SELINUX,
+                    nspace: key_generations::SELINUX_SHELL_NAMESPACE,
+                    alias: Some(alias.to_string()),
+                    blob: None,
+                })
                 .unwrap();
-        })
+        }
+
+        // Generate a key with above defined `alias`.
+        let key_metadata = key_generations::generate_ec_p256_signing_key(
+            &sl,
+            Domain::SELINUX,
+            key_generations::SELINUX_SHELL_NAMESPACE,
+            Some(alias.to_string()),
+            None,
+        )
+        .unwrap();
+
+        // Verify that above generated key entry is listed with domain SELINUX and
+        // namespace SELINUX_SHELL_NAMESPACE
+        assert!(key_alias_exists(
+            &sl.keystore2,
+            Domain::SELINUX,
+            key_generations::SELINUX_SHELL_NAMESPACE,
+            alias,
+        ));
+
+        // Grant a key with GET_INFO permission.
+        let access_vector = KeyPermission::GET_INFO.0;
+        sl.keystore2
+            .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector)
+            .unwrap();
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_root(gen_key_fn) };
+
     // In user context validate list of key entries associated with it.
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_UID),
-            Gid::from_raw(GRANTEE_GID),
-            move || {
-                let sl = SecLevel::tee();
-                let alias = format!("list_entries_success_key{}", getuid());
+    let list_keys_fn = move || {
+        let sl = SecLevel::tee();
+        let alias = format!("list_entries_success_key{}", getuid());
 
-                let key_metadata = key_generations::generate_ec_p256_signing_key(
-                    &sl,
-                    Domain::APP,
-                    -1,
-                    Some(alias.to_string()),
-                    None,
-                )
-                .unwrap();
-
-                // Make sure there is only one key entry exist and that should be the same key
-                // generated in this user context. Granted key shouldn't be included in this list.
-                let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
-                assert_eq!(1, key_descriptors.len());
-
-                let key = key_descriptors.first().unwrap();
-                assert_eq!(key.alias, Some(alias));
-                assert_eq!(key.nspace, GRANTEE_UID.try_into().unwrap());
-                assert_eq!(key.domain, Domain::APP);
-
-                sl.keystore2.deleteKey(&key_metadata.key).unwrap();
-
-                let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
-                assert_eq!(0, key_descriptors.len());
-            },
+        let key_metadata = key_generations::generate_ec_p256_signing_key(
+            &sl,
+            Domain::APP,
+            -1,
+            Some(alias.to_string()),
+            None,
         )
+        .unwrap();
+
+        // Make sure there is only one existing key entry and that should be the same key
+        // generated in this user context. Granted key shouldn't be included in this list.
+        let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
+        assert_eq!(1, key_descriptors.len());
+
+        let key = key_descriptors.first().unwrap();
+        assert_eq!(key.alias, Some(alias));
+        assert_eq!(key.nspace, GRANTEE_UID.try_into().unwrap());
+        assert_eq!(key.domain, Domain::APP);
+
+        sl.keystore2.deleteKey(&key_metadata.key).unwrap();
+
+        let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
+        assert_eq!(0, key_descriptors.len());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, list_keys_fn) };
 }
 
 /// Try to list the key entries with domain SELINUX from user context where user doesn't possesses
@@ -157,20 +151,19 @@
 fn keystore2_list_entries_fails_perm_denied() {
     let auid = 91 * AID_USER_OFFSET + 10001;
     let agid = 91 * AID_USER_OFFSET + 10001;
-    static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+    let list_keys_fn = move || {
+        let keystore2 = get_keystore_service();
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
-            let keystore2 = get_keystore_service();
-
-            let result = key_generations::map_ks_error(
-                keystore2.listEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
-            );
-            assert!(result.is_err());
-            assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
-        })
+        let result = key_generations::map_ks_error(
+            keystore2.listEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
+        );
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(auid, agid, list_keys_fn) };
 }
 
 /// Try to list key entries with domain BLOB. Test should fail with error repose code
@@ -190,64 +183,63 @@
 /// of all the entries in the keystore.
 #[test]
 fn keystore2_list_entries_with_long_aliases_success() {
-    static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
     const USER_ID: u32 = 92;
     const APPLICATION_ID: u32 = 10002;
     static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static CLIENT_GID: u32 = CLIENT_UID;
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
-            let sl = SecLevel::tee();
+    let import_keys_fn = || {
+        let sl = SecLevel::tee();
 
-            // Make sure there are no keystore entries exist before adding new entries.
+        // Make sure there are no keystore entries exist before adding new entries.
+        let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
+        if !key_descriptors.is_empty() {
+            key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
+                delete_app_key(&sl.keystore2, &alias).unwrap();
+            });
+        }
+
+        let mut imported_key_aliases = HashSet::new();
+
+        // Import 100 keys with aliases of length 6000.
+        for count in 1..101 {
+            let mut alias = String::new();
+            write!(alias, "{}_{}", "X".repeat(6000), count).unwrap();
+            imported_key_aliases.insert(alias.clone());
+
+            let result = key_generations::import_aes_key(&sl, Domain::APP, -1, Some(alias));
+            assert!(result.is_ok());
+        }
+
+        // b/222287335 Limiting Keystore `listEntries` API to return subset of the Keystore
+        // entries to avoid running out of binder buffer space.
+        // To verify that all the imported key aliases are present in Keystore,
+        //  - get the list of entries from Keystore
+        //  - check whether the retrieved key entries list is a subset of imported key aliases
+        //  - delete this subset of keystore entries from Keystore as well as from imported
+        //    list of key aliases
+        //  - continue above steps till it cleanup all the imported keystore entries.
+        while !imported_key_aliases.is_empty() {
             let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
-            if !key_descriptors.is_empty() {
-                key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
-                    delete_app_key(&sl.keystore2, &alias).unwrap();
-                });
-            }
 
-            let mut imported_key_aliases = HashSet::new();
+            // Check retrieved key entries list is a subset of imported keys list.
+            assert!(key_descriptors
+                .iter()
+                .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
 
-            // Import 100 keys with aliases of length 6000.
-            for count in 1..101 {
-                let mut alias = String::new();
-                write!(alias, "{}_{}", "X".repeat(6000), count).unwrap();
-                imported_key_aliases.insert(alias.clone());
+            // Delete the listed key entries from Keystore as well as from imported keys list.
+            key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
+                delete_app_key(&sl.keystore2, &alias).unwrap();
+                assert!(imported_key_aliases.remove(&alias));
+            });
+        }
 
-                let result = key_generations::import_aes_key(&sl, Domain::APP, -1, Some(alias));
-                assert!(result.is_ok());
-            }
-
-            // b/222287335 Limiting Keystore `listEntries` API to return subset of the Keystore
-            // entries to avoid running out of binder buffer space.
-            // To verify that all the imported key aliases are present in Keystore,
-            //  - get the list of entries from Keystore
-            //  - check whether the retrieved key entries list is a subset of imported key aliases
-            //  - delete this subset of keystore entries from Keystore as well as from imported
-            //    list of key aliases
-            //  - continue above steps till it cleanup all the imported keystore entries.
-            while !imported_key_aliases.is_empty() {
-                let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap();
-
-                // Check retrieved key entries list is a subset of imported keys list.
-                assert!(key_descriptors
-                    .iter()
-                    .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
-
-                // Delete the listed key entries from Keystore as well as from imported keys list.
-                key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
-                    delete_app_key(&sl.keystore2, &alias).unwrap();
-                    assert!(imported_key_aliases.remove(&alias));
-                });
-            }
-
-            assert!(imported_key_aliases.is_empty());
-        })
+        assert!(imported_key_aliases.is_empty());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, import_keys_fn) };
 }
 
 /// Import large number of Keystore entries with long aliases such that the
@@ -255,58 +247,57 @@
 /// Try to list aliases of all the entries in the keystore using `listEntriesBatched` API.
 #[test]
 fn keystore2_list_entries_batched_with_long_aliases_success() {
-    static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
     const USER_ID: u32 = 92;
     const APPLICATION_ID: u32 = 10002;
     static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static CLIENT_GID: u32 = CLIENT_UID;
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
-            let sl = SecLevel::tee();
+    let import_keys_fn = || {
+        let sl = SecLevel::tee();
 
-            // Make sure there are no keystore entries exist before adding new entries.
-            delete_all_entries(&sl.keystore2);
+        // Make sure there are no keystore entries exist before adding new entries.
+        delete_all_entries(&sl.keystore2);
 
-            // Import 100 keys with aliases of length 6000.
-            let mut imported_key_aliases =
-                key_generations::import_aes_keys(&sl, "X".repeat(6000), 1..101).unwrap();
-            assert_eq!(
-                sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
-                100,
-                "Error while importing keys"
-            );
+        // Import 100 keys with aliases of length 6000.
+        let mut imported_key_aliases =
+            key_generations::import_aes_keys(&sl, "X".repeat(6000), 1..101).unwrap();
+        assert_eq!(
+            sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+            100,
+            "Error while importing keys"
+        );
 
-            let mut start_past_alias = None;
-            let mut alias;
-            while !imported_key_aliases.is_empty() {
-                let key_descriptors =
-                    sl.keystore2.listEntriesBatched(Domain::APP, -1, start_past_alias).unwrap();
+        let mut start_past_alias = None;
+        let mut alias;
+        while !imported_key_aliases.is_empty() {
+            let key_descriptors =
+                sl.keystore2.listEntriesBatched(Domain::APP, -1, start_past_alias).unwrap();
 
-                // Check retrieved key entries list is a subset of imported keys list.
-                assert!(key_descriptors
-                    .iter()
-                    .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
+            // Check retrieved key entries list is a subset of imported keys list.
+            assert!(key_descriptors
+                .iter()
+                .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
 
-                alias = key_descriptors.last().unwrap().alias.clone().unwrap();
-                start_past_alias = Some(alias.as_ref());
-                // Delete the listed key entries from imported keys list.
-                key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
-                    assert!(imported_key_aliases.remove(&alias));
-                });
-            }
+            alias = key_descriptors.last().unwrap().alias.clone().unwrap();
+            start_past_alias = Some(alias.as_ref());
+            // Delete the listed key entries from imported keys list.
+            key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
+                assert!(imported_key_aliases.remove(&alias));
+            });
+        }
 
-            assert!(imported_key_aliases.is_empty());
-            delete_all_entries(&sl.keystore2);
-            assert_eq!(
-                sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
-                0,
-                "Error while doing cleanup"
-            );
-        })
+        assert!(imported_key_aliases.is_empty());
+        delete_all_entries(&sl.keystore2);
+        assert_eq!(
+            sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+            0,
+            "Error while doing cleanup"
+        );
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, import_keys_fn) };
 }
 
 /// Import keys from multiple processes with same user context and try to list the keystore entries
@@ -321,128 +312,127 @@
 ///    `startingPastAlias` as None. It should list all the keys imported in process-1 and process-2.
 #[test]
 fn keystore2_list_entries_batched_with_multi_procs_success() {
-    static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
     const USER_ID: u32 = 92;
     const APPLICATION_ID: u32 = 10002;
     static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static CLIENT_GID: u32 = CLIENT_UID;
     static ALIAS_PREFIX: &str = "key_test_batch_list";
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
-            let sl = SecLevel::tee();
+    let import_keys_fn = || {
+        let sl = SecLevel::tee();
 
-            // Make sure there are no keystore entries exist before adding new entries.
-            delete_all_entries(&sl.keystore2);
+        // Make sure there are no keystore entries exist before adding new entries.
+        delete_all_entries(&sl.keystore2);
 
-            // Import 3 keys with below aliases -
-            // [key_test_batch_list_1, key_test_batch_list_2, key_test_batch_list_3]
-            let imported_key_aliases =
-                key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..4).unwrap();
-            assert_eq!(
-                sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
-                3,
-                "Error while importing keys"
-            );
+        // Import 3 keys with below aliases -
+        // [key_test_batch_list_1, key_test_batch_list_2, key_test_batch_list_3]
+        let imported_key_aliases =
+            key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..4).unwrap();
+        assert_eq!(
+            sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+            3,
+            "Error while importing keys"
+        );
 
-            // List all entries in keystore for this user-id.
-            let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
-            assert_eq!(key_descriptors.len(), 3);
+        // List all entries in keystore for this user-id.
+        let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
+        assert_eq!(key_descriptors.len(), 3);
 
-            // Makes sure all listed aliases are matching with imported keys aliases.
-            assert!(key_descriptors
-                .iter()
-                .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
-        })
+        // Makes sure all listed aliases are matching with imported keys aliases.
+        assert!(key_descriptors
+            .iter()
+            .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
     };
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
-            let sl = SecLevel::tee();
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, import_keys_fn) };
 
-            // Import another 5 keys with below aliases -
-            // [ key_test_batch_list_4, key_test_batch_list_5, key_test_batch_list_6,
-            //   key_test_batch_list_7, key_test_batch_list_8 ]
-            let mut imported_key_aliases =
-                key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 4..9).unwrap();
+    let import_more_fn = || {
+        let sl = SecLevel::tee();
 
-            // Above context already 3 keys are imported, in this context 5 keys are imported,
-            // total 8 keystore entries are expected to be present in Keystore for this user-id.
-            assert_eq!(
-                sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
-                8,
-                "Error while importing keys"
-            );
+        // Import another 5 keys with below aliases -
+        // [ key_test_batch_list_4, key_test_batch_list_5, key_test_batch_list_6,
+        //   key_test_batch_list_7, key_test_batch_list_8 ]
+        let mut imported_key_aliases =
+            key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 4..9).unwrap();
 
-            // List keystore entries with `start_past_alias` as "key_test_batch_list_3".
-            // `listEntriesBatched` should list all the keystore entries with
-            // alias > "key_test_batch_list_3".
-            let key_descriptors = sl
-                .keystore2
-                .listEntriesBatched(Domain::APP, -1, Some("key_test_batch_list_3"))
-                .unwrap();
-            assert_eq!(key_descriptors.len(), 5);
+        // Above context already 3 keys are imported, in this context 5 keys are imported,
+        // total 8 keystore entries are expected to be present in Keystore for this user-id.
+        assert_eq!(
+            sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+            8,
+            "Error while importing keys"
+        );
 
-            // Make sure above listed aliases are matching with imported keys aliases.
-            assert!(key_descriptors
-                .iter()
-                .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
+        // List keystore entries with `start_past_alias` as "key_test_batch_list_3".
+        // `listEntriesBatched` should list all the keystore entries with
+        // alias > "key_test_batch_list_3".
+        let key_descriptors = sl
+            .keystore2
+            .listEntriesBatched(Domain::APP, -1, Some("key_test_batch_list_3"))
+            .unwrap();
+        assert_eq!(key_descriptors.len(), 5);
 
-            // List all keystore entries with `start_past_alias` as `None`.
-            // `listEntriesBatched` should list all the keystore entries.
-            let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
-            assert_eq!(key_descriptors.len(), 8);
+        // Make sure above listed aliases are matching with imported keys aliases.
+        assert!(key_descriptors
+            .iter()
+            .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
 
-            // Include previously imported keys aliases as well
-            imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_1");
-            imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_2");
-            imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_3");
+        // List all keystore entries with `start_past_alias` as `None`.
+        // `listEntriesBatched` should list all the keystore entries.
+        let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
+        assert_eq!(key_descriptors.len(), 8);
 
-            // Make sure all the above listed aliases are matching with imported keys aliases.
-            assert!(key_descriptors
-                .iter()
-                .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
+        // Include previously imported keys aliases as well
+        imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_1");
+        imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_2");
+        imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_3");
 
-            delete_all_entries(&sl.keystore2);
-            assert_eq!(
-                sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
-                0,
-                "Error while doing cleanup"
-            );
-        })
+        // Make sure all the above listed aliases are matching with imported keys aliases.
+        assert!(key_descriptors
+            .iter()
+            .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
+
+        delete_all_entries(&sl.keystore2);
+        assert_eq!(
+            sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+            0,
+            "Error while doing cleanup"
+        );
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, import_more_fn) };
 }
 
 #[test]
 fn keystore2_list_entries_batched_with_empty_keystore_success() {
-    static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
     const USER_ID: u32 = 92;
     const APPLICATION_ID: u32 = 10002;
     static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static CLIENT_GID: u32 = CLIENT_UID;
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
-            let keystore2 = get_keystore_service();
+    let list_keys_fn = || {
+        let keystore2 = get_keystore_service();
 
-            // Make sure there are no keystore entries exist before adding new entries.
-            delete_all_entries(&keystore2);
+        // Make sure there are no keystore entries exist before adding new entries.
+        delete_all_entries(&keystore2);
 
-            // List all entries in keystore for this user-id, pass startingPastAlias = None
-            let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
-            assert_eq!(key_descriptors.len(), 0);
+        // List all entries in keystore for this user-id, pass startingPastAlias = None
+        let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
+        assert_eq!(key_descriptors.len(), 0);
 
-            // List all entries in keystore for this user-id, pass startingPastAlias = <random value>
-            let key_descriptors =
-                keystore2.listEntriesBatched(Domain::APP, -1, Some("startingPastAlias")).unwrap();
-            assert_eq!(key_descriptors.len(), 0);
-        })
+        // List all entries in keystore for this user-id, pass startingPastAlias = <random value>
+        let key_descriptors =
+            keystore2.listEntriesBatched(Domain::APP, -1, Some("startingPastAlias")).unwrap();
+        assert_eq!(key_descriptors.len(), 0);
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, list_keys_fn) };
 }
 
 /// Import a key with SELINUX as domain, list aliases using `listEntriesBatched`.
@@ -502,140 +492,135 @@
 
 #[test]
 fn keystore2_list_entries_batched_validate_count_and_order_success() {
-    static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
     const USER_ID: u32 = 92;
     const APPLICATION_ID: u32 = 10002;
     static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     static CLIENT_GID: u32 = CLIENT_UID;
     static ALIAS_PREFIX: &str = "key_test_batch_list";
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
-            let sl = SecLevel::tee();
+    let list_keys_fn = || {
+        let sl = SecLevel::tee();
 
-            // Make sure there are no keystore entries exist before adding new entries.
-            delete_all_entries(&sl.keystore2);
+        // Make sure there are no keystore entries exist before adding new entries.
+        delete_all_entries(&sl.keystore2);
 
-            // Import keys with below mentioned aliases -
-            // [
-            //   key_test_batch_list_1,
-            //   key_test_batch_list_2,
-            //   key_test_batch_list_3,
-            //   key_test_batch_list_4,
-            //   key_test_batch_list_5,
-            //   key_test_batch_list_10,
-            //   key_test_batch_list_11,
-            //   key_test_batch_list_12,
-            //   key_test_batch_list_21,
-            //   key_test_batch_list_22,
-            // ]
-            let _imported_key_aliases =
-                key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..6).unwrap();
-            assert_eq!(
-                sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
-                5,
-                "Error while importing keys"
-            );
-            let _imported_key_aliases =
-                key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 10..13).unwrap();
-            assert_eq!(
-                sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
-                8,
-                "Error while importing keys"
-            );
-            let _imported_key_aliases =
-                key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 21..23).unwrap();
-            assert_eq!(
-                sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
-                10,
-                "Error while importing keys"
-            );
+        // Import keys with below mentioned aliases -
+        // [
+        //   key_test_batch_list_1,
+        //   key_test_batch_list_2,
+        //   key_test_batch_list_3,
+        //   key_test_batch_list_4,
+        //   key_test_batch_list_5,
+        //   key_test_batch_list_10,
+        //   key_test_batch_list_11,
+        //   key_test_batch_list_12,
+        //   key_test_batch_list_21,
+        //   key_test_batch_list_22,
+        // ]
+        let _imported_key_aliases =
+            key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..6).unwrap();
+        assert_eq!(
+            sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+            5,
+            "Error while importing keys"
+        );
+        let _imported_key_aliases =
+            key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 10..13).unwrap();
+        assert_eq!(
+            sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+            8,
+            "Error while importing keys"
+        );
+        let _imported_key_aliases =
+            key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 21..23).unwrap();
+        assert_eq!(
+            sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
+            10,
+            "Error while importing keys"
+        );
 
-            // List the aliases using given `startingPastAlias` and verify the listed
-            // aliases with the expected list of aliases.
-            verify_aliases(
-                &sl.keystore2,
-                Some(format!("{}{}", ALIAS_PREFIX, "_5").as_str()),
-                vec![],
-            );
+        // List the aliases using given `startingPastAlias` and verify the listed
+        // aliases with the expected list of aliases.
+        verify_aliases(&sl.keystore2, Some(format!("{}{}", ALIAS_PREFIX, "_5").as_str()), vec![]);
 
-            verify_aliases(
-                &sl.keystore2,
-                Some(format!("{}{}", ALIAS_PREFIX, "_4").as_str()),
-                vec![ALIAS_PREFIX.to_owned() + "_5"],
-            );
+        verify_aliases(
+            &sl.keystore2,
+            Some(format!("{}{}", ALIAS_PREFIX, "_4").as_str()),
+            vec![ALIAS_PREFIX.to_owned() + "_5"],
+        );
 
-            verify_aliases(
-                &sl.keystore2,
-                Some(format!("{}{}", ALIAS_PREFIX, "_3").as_str()),
-                vec![ALIAS_PREFIX.to_owned() + "_4", ALIAS_PREFIX.to_owned() + "_5"],
-            );
+        verify_aliases(
+            &sl.keystore2,
+            Some(format!("{}{}", ALIAS_PREFIX, "_3").as_str()),
+            vec![ALIAS_PREFIX.to_owned() + "_4", ALIAS_PREFIX.to_owned() + "_5"],
+        );
 
-            verify_aliases(
-                &sl.keystore2,
-                Some(format!("{}{}", ALIAS_PREFIX, "_2").as_str()),
-                vec![
-                    ALIAS_PREFIX.to_owned() + "_21",
-                    ALIAS_PREFIX.to_owned() + "_22",
-                    ALIAS_PREFIX.to_owned() + "_3",
-                    ALIAS_PREFIX.to_owned() + "_4",
-                    ALIAS_PREFIX.to_owned() + "_5",
-                ],
-            );
+        verify_aliases(
+            &sl.keystore2,
+            Some(format!("{}{}", ALIAS_PREFIX, "_2").as_str()),
+            vec![
+                ALIAS_PREFIX.to_owned() + "_21",
+                ALIAS_PREFIX.to_owned() + "_22",
+                ALIAS_PREFIX.to_owned() + "_3",
+                ALIAS_PREFIX.to_owned() + "_4",
+                ALIAS_PREFIX.to_owned() + "_5",
+            ],
+        );
 
-            verify_aliases(
-                &sl.keystore2,
-                Some(format!("{}{}", ALIAS_PREFIX, "_1").as_str()),
-                vec![
-                    ALIAS_PREFIX.to_owned() + "_10",
-                    ALIAS_PREFIX.to_owned() + "_11",
-                    ALIAS_PREFIX.to_owned() + "_12",
-                    ALIAS_PREFIX.to_owned() + "_2",
-                    ALIAS_PREFIX.to_owned() + "_21",
-                    ALIAS_PREFIX.to_owned() + "_22",
-                    ALIAS_PREFIX.to_owned() + "_3",
-                    ALIAS_PREFIX.to_owned() + "_4",
-                    ALIAS_PREFIX.to_owned() + "_5",
-                ],
-            );
+        verify_aliases(
+            &sl.keystore2,
+            Some(format!("{}{}", ALIAS_PREFIX, "_1").as_str()),
+            vec![
+                ALIAS_PREFIX.to_owned() + "_10",
+                ALIAS_PREFIX.to_owned() + "_11",
+                ALIAS_PREFIX.to_owned() + "_12",
+                ALIAS_PREFIX.to_owned() + "_2",
+                ALIAS_PREFIX.to_owned() + "_21",
+                ALIAS_PREFIX.to_owned() + "_22",
+                ALIAS_PREFIX.to_owned() + "_3",
+                ALIAS_PREFIX.to_owned() + "_4",
+                ALIAS_PREFIX.to_owned() + "_5",
+            ],
+        );
 
-            verify_aliases(
-                &sl.keystore2,
-                Some(ALIAS_PREFIX),
-                vec![
-                    ALIAS_PREFIX.to_owned() + "_1",
-                    ALIAS_PREFIX.to_owned() + "_10",
-                    ALIAS_PREFIX.to_owned() + "_11",
-                    ALIAS_PREFIX.to_owned() + "_12",
-                    ALIAS_PREFIX.to_owned() + "_2",
-                    ALIAS_PREFIX.to_owned() + "_21",
-                    ALIAS_PREFIX.to_owned() + "_22",
-                    ALIAS_PREFIX.to_owned() + "_3",
-                    ALIAS_PREFIX.to_owned() + "_4",
-                    ALIAS_PREFIX.to_owned() + "_5",
-                ],
-            );
+        verify_aliases(
+            &sl.keystore2,
+            Some(ALIAS_PREFIX),
+            vec![
+                ALIAS_PREFIX.to_owned() + "_1",
+                ALIAS_PREFIX.to_owned() + "_10",
+                ALIAS_PREFIX.to_owned() + "_11",
+                ALIAS_PREFIX.to_owned() + "_12",
+                ALIAS_PREFIX.to_owned() + "_2",
+                ALIAS_PREFIX.to_owned() + "_21",
+                ALIAS_PREFIX.to_owned() + "_22",
+                ALIAS_PREFIX.to_owned() + "_3",
+                ALIAS_PREFIX.to_owned() + "_4",
+                ALIAS_PREFIX.to_owned() + "_5",
+            ],
+        );
 
-            verify_aliases(
-                &sl.keystore2,
-                None,
-                vec![
-                    ALIAS_PREFIX.to_owned() + "_1",
-                    ALIAS_PREFIX.to_owned() + "_10",
-                    ALIAS_PREFIX.to_owned() + "_11",
-                    ALIAS_PREFIX.to_owned() + "_12",
-                    ALIAS_PREFIX.to_owned() + "_2",
-                    ALIAS_PREFIX.to_owned() + "_21",
-                    ALIAS_PREFIX.to_owned() + "_22",
-                    ALIAS_PREFIX.to_owned() + "_3",
-                    ALIAS_PREFIX.to_owned() + "_4",
-                    ALIAS_PREFIX.to_owned() + "_5",
-                ],
-            );
-        })
+        verify_aliases(
+            &sl.keystore2,
+            None,
+            vec![
+                ALIAS_PREFIX.to_owned() + "_1",
+                ALIAS_PREFIX.to_owned() + "_10",
+                ALIAS_PREFIX.to_owned() + "_11",
+                ALIAS_PREFIX.to_owned() + "_12",
+                ALIAS_PREFIX.to_owned() + "_2",
+                ALIAS_PREFIX.to_owned() + "_21",
+                ALIAS_PREFIX.to_owned() + "_22",
+                ALIAS_PREFIX.to_owned() + "_3",
+                ALIAS_PREFIX.to_owned() + "_4",
+                ALIAS_PREFIX.to_owned() + "_5",
+            ],
+        );
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(CLIENT_UID, CLIENT_GID, list_keys_fn) };
 }
 
 /// Try to list the key entries with domain SELINUX from user context where user doesn't possesses
@@ -645,22 +630,21 @@
 fn keystore2_list_entries_batched_fails_perm_denied() {
     let auid = 91 * AID_USER_OFFSET + 10001;
     let agid = 91 * AID_USER_OFFSET + 10001;
-    static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+    let list_keys_fn = move || {
+        let keystore2 = get_keystore_service();
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
-            let keystore2 = get_keystore_service();
-
-            let result = key_generations::map_ks_error(keystore2.listEntriesBatched(
-                Domain::SELINUX,
-                key_generations::SELINUX_SHELL_NAMESPACE,
-                None,
-            ));
-            assert!(result.is_err());
-            assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
-        })
+        let result = key_generations::map_ks_error(keystore2.listEntriesBatched(
+            Domain::SELINUX,
+            key_generations::SELINUX_SHELL_NAMESPACE,
+            None,
+        ));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(auid, agid, list_keys_fn) };
 }
 
 /// Try to list key entries with domain BLOB. Test should fail with error response code
@@ -685,21 +669,19 @@
 fn keystore2_get_number_of_entries_fails_perm_denied() {
     let auid = 91 * AID_USER_OFFSET + 10001;
     let agid = 91 * AID_USER_OFFSET + 10001;
-    static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+    let get_num_fn = move || {
+        let keystore2 = get_keystore_service();
 
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
-            let keystore2 = get_keystore_service();
-
-            let result = key_generations::map_ks_error(
-                keystore2
-                    .getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
-            );
-            assert!(result.is_err());
-            assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
-        })
+        let result = key_generations::map_ks_error(
+            keystore2.getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
+        );
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(auid, agid, get_num_fn) };
 }
 
 /// Try to get number of key entries with domain BLOB. Test should fail with error response code
diff --git a/keystore2/tests/keystore2_client_operation_tests.rs b/keystore2/tests/keystore2_client_operation_tests.rs
index 95888a6..1f8396e 100644
--- a/keystore2/tests/keystore2_client_operation_tests.rs
+++ b/keystore2/tests/keystore2_client_operation_tests.rs
@@ -28,6 +28,10 @@
 };
 use nix::unistd::{getuid, Gid, Uid};
 use rustutils::users::AID_USER_OFFSET;
+use std::sync::{
+    atomic::{AtomicBool, Ordering},
+    Arc,
+};
 use std::thread;
 use std::thread::JoinHandle;
 
@@ -36,7 +40,8 @@
 ///
 /// # Safety
 ///
-/// Must be called from a process with no other threads.
+/// Must only be called from a single-threaded process (e.g. as enforced by `AndroidTest.xml`
+/// setting `--test-threads=1`).
 pub unsafe fn create_operations(
     target_ctx: &'static str,
     forced_op: ForcedOp,
@@ -46,7 +51,7 @@
     let base_gid = 99 * AID_USER_OFFSET + 10001;
     let base_uid = 99 * AID_USER_OFFSET + 10001;
     (0..max_ops)
-        // SAFETY: The caller guarantees that there are no other threads.
+        // Safety: The caller guarantees that there are no other threads.
         .map(|i| unsafe {
             execute_op_run_as_child(
                 target_ctx,
@@ -89,7 +94,8 @@
     const MAX_OPS: i32 = 100;
     static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
 
-    // SAFETY: The test is run in a separate process with no other threads.
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     let mut child_handles = unsafe { create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS) };
 
     // Wait until all child procs notifies us to continue,
@@ -123,7 +129,8 @@
     static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
 
     // Create regular operations.
-    // SAFETY: The test is run in a separate process with no other threads.
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     let mut child_handles = unsafe { create_operations(TARGET_CTX, ForcedOp(false), MAX_OPS) };
 
     // Wait until all child procs notifies us to continue, so that there are enough
@@ -135,28 +142,31 @@
     // Create a forced operation.
     let auid = 99 * AID_USER_OFFSET + 10604;
     let agid = 99 * AID_USER_OFFSET + 10604;
-    // SAFETY: The test is run in a separate process with no other threads.
+    let force_op_fn = move || {
+        let alias = format!("ks_prune_forced_op_key_{}", getuid());
+
+        // To make room for this forced op, system should be able to prune one of the
+        // above created regular operations and create a slot for this forced operation
+        // successfully.
+        create_signing_operation(
+            ForcedOp(true),
+            KeyPurpose::SIGN,
+            Digest::SHA_2_256,
+            Domain::SELINUX,
+            100,
+            Some(alias),
+        )
+        .expect("Client failed to create forced operation after BACKEND_BUSY state.");
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     unsafe {
         run_as::run_as(
             key_generations::TARGET_VOLD_CTX,
             Uid::from_raw(auid),
             Gid::from_raw(agid),
-            move || {
-                let alias = format!("ks_prune_forced_op_key_{}", getuid());
-
-                // To make room for this forced op, system should be able to prune one of the
-                // above created regular operations and create a slot for this forced operation
-                // successfully.
-                create_signing_operation(
-                    ForcedOp(true),
-                    KeyPurpose::SIGN,
-                    Digest::SHA_2_256,
-                    Domain::SELINUX,
-                    100,
-                    Some(alias),
-                )
-                .expect("Client failed to create forced operation after BACKEND_BUSY state.");
-            },
+            force_op_fn,
         );
     };
 
@@ -208,7 +218,9 @@
     // Create initial forced operation in a child process
     // and wait for the parent to notify to perform operation.
     let alias = format!("ks_forced_op_key_{}", getuid());
-    // SAFETY: The test is run in a separate process with no other threads.
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     let mut first_op_handle = unsafe {
         execute_op_run_as_child(
             key_generations::TARGET_SU_CTX,
@@ -227,7 +239,8 @@
 
     // Create MAX_OPS number of forced operations.
     let mut child_handles =
-    // SAFETY: The test is run in a separate process with no other threads.
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
         unsafe { create_operations(key_generations::TARGET_SU_CTX, ForcedOp(true), MAX_OPS) };
 
     // Wait until all child procs notifies us to continue, so that  there are enough operations
@@ -291,7 +304,9 @@
     // Create an operation in an untrusted_app context. Wait until the parent notifies to continue.
     // Once the parent notifies, this operation is expected to be completed successfully.
     let alias = format!("ks_reg_op_key_{}", getuid());
-    // SAFETY: The test is run in a separate process with no other threads.
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     let mut child_handle = unsafe {
         execute_op_run_as_child(
             TARGET_CTX,
@@ -376,6 +391,7 @@
 ///   - untrusted_app
 ///   - system_server
 ///   - priv_app
+///
 /// `PERMISSION_DENIED` error response is expected.
 #[test]
 fn keystore2_forced_op_perm_denied_test() {
@@ -388,21 +404,24 @@
     let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
 
     for context in TARGET_CTXS.iter() {
-        // SAFETY: The test is run in a separate process with no other threads.
+        let forced_op_fn = move || {
+            let alias = format!("ks_app_forced_op_test_key_{}", getuid());
+            let result = key_generations::map_ks_error(create_signing_operation(
+                ForcedOp(true),
+                KeyPurpose::SIGN,
+                Digest::SHA_2_256,
+                Domain::APP,
+                -1,
+                Some(alias),
+            ));
+            assert!(result.is_err());
+            assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+        };
+
+        // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+        // `--test-threads=1`), and nothing yet done with binder.
         unsafe {
-            run_as::run_as(context, Uid::from_raw(uid), Gid::from_raw(gid), move || {
-                let alias = format!("ks_app_forced_op_test_key_{}", getuid());
-                let result = key_generations::map_ks_error(create_signing_operation(
-                    ForcedOp(true),
-                    KeyPurpose::SIGN,
-                    Digest::SHA_2_256,
-                    Domain::APP,
-                    -1,
-                    Some(alias),
-                ));
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
-            });
+            run_as::run_as(context, Uid::from_raw(uid), Gid::from_raw(gid), forced_op_fn);
         }
     }
 }
@@ -411,27 +430,29 @@
 /// Should be able to create forced operation with `vold` context successfully.
 #[test]
 fn keystore2_forced_op_success_test() {
-    static TARGET_CTX: &str = "u:r:vold:s0";
+    static TARGET_VOLD_CTX: &str = "u:r:vold:s0";
     const USER_ID: u32 = 99;
     const APPLICATION_ID: u32 = 10601;
 
     let uid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
     let gid = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
+    let forced_op_fn = move || {
+        let alias = format!("ks_vold_forced_op_key_{}", getuid());
+        create_signing_operation(
+            ForcedOp(true),
+            KeyPurpose::SIGN,
+            Digest::SHA_2_256,
+            Domain::SELINUX,
+            key_generations::SELINUX_VOLD_NAMESPACE,
+            Some(alias),
+        )
+        .expect("Client with vold context failed to create forced operation.");
+    };
 
-    // SAFETY: The test is run in a separate process with no other threads.
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     unsafe {
-        run_as::run_as(TARGET_CTX, Uid::from_raw(uid), Gid::from_raw(gid), move || {
-            let alias = format!("ks_vold_forced_op_key_{}", getuid());
-            create_signing_operation(
-                ForcedOp(true),
-                KeyPurpose::SIGN,
-                Digest::SHA_2_256,
-                Domain::SELINUX,
-                key_generations::SELINUX_VOLD_NAMESPACE,
-                Some(alias),
-            )
-            .expect("Client with vold context failed to create forced operation.");
-        });
+        run_as::run_as(TARGET_VOLD_CTX, Uid::from_raw(uid), Gid::from_raw(gid), forced_op_fn);
     }
 }
 
@@ -460,3 +481,120 @@
 
     assert!(result1 || result2);
 }
+
+/// Create an operation and use it for performing sign operation. After completing the operation
+/// try to abort the operation. Test should fail to abort already finalized operation with error
+/// code `INVALID_OPERATION_HANDLE`.
+#[test]
+fn keystore2_abort_finalized_op_fail_test() {
+    let op_response = create_signing_operation(
+        ForcedOp(false),
+        KeyPurpose::SIGN,
+        Digest::SHA_2_256,
+        Domain::APP,
+        -1,
+        Some("ks_op_abort_fail_test_key".to_string()),
+    )
+    .unwrap();
+
+    let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+    perform_sample_sign_operation(&op).unwrap();
+    let result = key_generations::map_ks_error(op.abort());
+    assert!(result.is_err());
+    assert_eq!(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE), result.unwrap_err());
+}
+
+/// Create an operation and use it for performing sign operation. Before finishing the operation
+/// try to abort the operation. Test should successfully abort the operation. After aborting try to
+/// use the operation handle, test should fail to use already aborted operation handle with error
+/// code `INVALID_OPERATION_HANDLE`.
+#[test]
+fn keystore2_op_abort_success_test() {
+    let op_response = create_signing_operation(
+        ForcedOp(false),
+        KeyPurpose::SIGN,
+        Digest::SHA_2_256,
+        Domain::APP,
+        -1,
+        Some("ks_op_abort_success_key".to_string()),
+    )
+    .unwrap();
+
+    let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+    op.update(b"my message").unwrap();
+    let result = key_generations::map_ks_error(op.abort());
+    assert!(result.is_ok());
+
+    // Try to use the op handle after abort.
+    let result = key_generations::map_ks_error(op.finish(None, None));
+    assert!(result.is_err());
+    assert_eq!(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE), result.unwrap_err());
+}
+
+/// Executes an operation in a thread. Performs an `update` operation repeatedly till the user
+/// interrupts it or encounters any error other than `OPERATION_BUSY`.
+/// Return `false` in case of any error other than `OPERATION_BUSY`, otherwise it returns true.
+fn perform_abort_op_busy_in_thread(
+    op: binder::Strong<dyn IKeystoreOperation>,
+    should_exit_clone: Arc<AtomicBool>,
+) -> JoinHandle<bool> {
+    thread::spawn(move || {
+        loop {
+            if should_exit_clone.load(Ordering::Relaxed) {
+                // Caller requested to exit the thread.
+                return true;
+            }
+
+            match key_generations::map_ks_error(op.update(b"my message")) {
+                Ok(_) => continue,
+                Err(Error::Rc(ResponseCode::OPERATION_BUSY)) => continue,
+                Err(_) => return false,
+            }
+        }
+    })
+}
+
+/// Create an operation and try to use same operation handle in multiple threads to perform
+/// operations. Test tries to abort the operation and expects `abort` call to fail with the error
+/// response `OPERATION_BUSY` as multiple threads try to access the same operation handle
+/// simultaneously. Test tries to simulate `OPERATION_BUSY` error response from `abort` api.
+#[test]
+fn keystore2_op_abort_fails_with_operation_busy_error_test() {
+    loop {
+        let op_response = create_signing_operation(
+            ForcedOp(false),
+            KeyPurpose::SIGN,
+            Digest::SHA_2_256,
+            Domain::APP,
+            -1,
+            Some("op_abort_busy_alias_test_key".to_string()),
+        )
+        .unwrap();
+        let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+
+        let should_exit = Arc::new(AtomicBool::new(false));
+
+        let update_t_handle1 = perform_abort_op_busy_in_thread(op.clone(), should_exit.clone());
+        let update_t_handle2 = perform_abort_op_busy_in_thread(op.clone(), should_exit.clone());
+
+        // Attempt to abort the operation and anticipate an 'OPERATION_BUSY' error, as multiple
+        // threads are concurrently accessing the same operation handle.
+        let result = match op.abort() {
+            Ok(_) => 0, // Operation successfully aborted.
+            Err(e) => e.service_specific_error(),
+        };
+
+        // Notify threads to stop performing `update` operation.
+        should_exit.store(true, Ordering::Relaxed);
+
+        let _update_op_result = update_t_handle1.join().unwrap();
+        let _update_op_result2 = update_t_handle2.join().unwrap();
+
+        if result == ResponseCode::OPERATION_BUSY.0 {
+            // The abort call failed with an OPERATION_BUSY error, as anticipated, due to multiple
+            // threads competing for access to the same operation handle.
+            return;
+        }
+        assert_eq!(result, 0);
+    }
+}
diff --git a/keystore2/tests/keystore2_client_rsa_key_tests.rs b/keystore2/tests/keystore2_client_rsa_key_tests.rs
index 1559008..cb8729f 100644
--- a/keystore2/tests/keystore2_client_rsa_key_tests.rs
+++ b/keystore2/tests/keystore2_client_rsa_key_tests.rs
@@ -78,9 +78,12 @@
     key_params: &key_generations::KeyParams,
     op_purpose: KeyPurpose,
     forced_op: ForcedOp,
-) -> binder::Result<CreateOperationResponse> {
-    let key_metadata =
-        key_generations::generate_rsa_key(sl, domain, nspace, alias, key_params, None)?;
+) -> binder::Result<Option<CreateOperationResponse>> {
+    let Some(key_metadata) =
+        key_generations::generate_rsa_key(sl, domain, nspace, alias, key_params, None)?
+    else {
+        return Ok(None);
+    };
 
     let mut op_params = authorizations::AuthSetBuilder::new().purpose(op_purpose);
 
@@ -97,7 +100,7 @@
         op_params = op_params.block_mode(value)
     }
 
-    sl.binder.createOperation(&key_metadata.key, &op_params, forced_op.0)
+    sl.binder.createOperation(&key_metadata.key, &op_params, forced_op.0).map(Some)
 }
 
 /// Generate RSA signing key with given parameters and perform signing operation.
@@ -109,7 +112,7 @@
 ) {
     let sl = SecLevel::tee();
 
-    let op_response = create_rsa_key_and_operation(
+    let Some(op_response) = create_rsa_key_and_operation(
         &sl,
         Domain::APP,
         -1,
@@ -126,7 +129,9 @@
         KeyPurpose::SIGN,
         ForcedOp(false),
     )
-    .expect("Failed to create an operation.");
+    .expect("Failed to create an operation.") else {
+        return;
+    };
 
     assert!(op_response.iOperation.is_some());
     assert_eq!(
diff --git a/keystore2/tests/keystore2_client_test_utils.rs b/keystore2/tests/keystore2_client_test_utils.rs
index d4ca2ae..1bbdc91 100644
--- a/keystore2/tests/keystore2_client_test_utils.rs
+++ b/keystore2/tests/keystore2_client_test_utils.rs
@@ -25,7 +25,11 @@
 };
 use binder::wait_for_interface;
 use keystore2_test_utils::{
-    authorizations, key_generations, key_generations::Error, run_as, SecLevel,
+    authorizations, key_generations,
+    key_generations::Error,
+    run_as,
+    run_as::{ChannelReader, ChannelWriter},
+    SecLevel,
 };
 use nix::unistd::{Gid, Uid};
 use openssl::bn::BigNum;
@@ -40,7 +44,6 @@
 use openssl::x509::X509;
 use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
 use serde::{Deserialize, Serialize};
-use std::path::PathBuf;
 use std::process::{Command, Output};
 
 /// This enum is used to communicate between parent and child processes.
@@ -52,10 +55,21 @@
     OtherErr,
 }
 
-/// This is used to notify the child or parent process that the expected state is reched.
+/// This is used to notify the child or parent process that the expected state is reached.
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
 pub struct BarrierReached;
 
+/// This is used to notify the child or parent process that the expected state is reached,
+/// passing a value
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub struct BarrierReachedWithData<T: Send + Sync>(pub T);
+
+impl<T: Send + Sync> BarrierReachedWithData<T> {
+    pub fn new(val: T) -> Self {
+        Self(val)
+    }
+}
+
 /// Forced operation.
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub struct ForcedOp(pub bool);
@@ -100,10 +114,7 @@
     // (ro.product.*_for_attestation) reading logic would not be available for such devices
     // hence skipping this test for such scenario.
 
-    // This file is only present on GSI builds.
-    let gsi_marker = PathBuf::from("/system/system_ext/etc/init/init.gsi.rc");
-
-    get_vsr_api_level() < 34 && gsi_marker.as_path().is_file()
+    get_vsr_api_level() < 34 && key_generations::is_gsi()
 }
 
 #[macro_export]
@@ -298,7 +309,8 @@
 ///
 /// # Safety
 ///
-/// Must only be called from a single-threaded process.
+/// Must only be called from a single-threaded process (e.g. as enforced by `AndroidTest.xml`
+/// setting `--test-threads=1`).
 pub unsafe fn execute_op_run_as_child(
     target_ctx: &'static str,
     domain: Domain,
@@ -308,41 +320,44 @@
     agid: Gid,
     forced_op: ForcedOp,
 ) -> run_as::ChildHandle<TestOutcome, BarrierReached> {
-    // SAFETY: The caller guarantees that there are no other threads.
-    unsafe {
-        run_as::run_as_child(target_ctx, auid, agid, move |reader, writer| {
-            let result = key_generations::map_ks_error(create_signing_operation(
-                forced_op,
-                KeyPurpose::SIGN,
-                Digest::SHA_2_256,
-                domain,
-                nspace,
-                alias,
-            ));
+    let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
+                         writer: &mut ChannelWriter<BarrierReached>| {
+        let result = key_generations::map_ks_error(create_signing_operation(
+            forced_op,
+            KeyPurpose::SIGN,
+            Digest::SHA_2_256,
+            domain,
+            nspace,
+            alias,
+        ));
 
-            // Let the parent know that an operation has been started, then
-            // wait until the parent notifies us to continue, so the operation
-            // remains open.
-            writer.send(&BarrierReached {});
-            reader.recv();
+        // Let the parent know that an operation has been started, then
+        // wait until the parent notifies us to continue, so the operation
+        // remains open.
+        writer.send(&BarrierReached {});
+        reader.recv();
 
-            // Continue performing the operation after parent notifies.
-            match &result {
-                Ok(CreateOperationResponse { iOperation: Some(op), .. }) => {
-                    match key_generations::map_ks_error(perform_sample_sign_operation(op)) {
-                        Ok(()) => TestOutcome::Ok,
-                        Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {
-                            TestOutcome::InvalidHandle
-                        }
-                        Err(e) => panic!("Error in performing op: {:#?}", e),
+        // Continue performing the operation after parent notifies.
+        match &result {
+            Ok(CreateOperationResponse { iOperation: Some(op), .. }) => {
+                match key_generations::map_ks_error(perform_sample_sign_operation(op)) {
+                    Ok(()) => TestOutcome::Ok,
+                    Err(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {
+                        TestOutcome::InvalidHandle
                     }
+                    Err(e) => panic!("Error in performing op: {:#?}", e),
                 }
-                Ok(_) => TestOutcome::OtherErr,
-                Err(Error::Rc(ResponseCode::BACKEND_BUSY)) => TestOutcome::BackendBusy,
-                _ => TestOutcome::OtherErr,
             }
-        })
-        .expect("Failed to create an operation.")
+            Ok(_) => TestOutcome::OtherErr,
+            Err(Error::Rc(ResponseCode::BACKEND_BUSY)) => TestOutcome::BackendBusy,
+            _ => TestOutcome::OtherErr,
+        }
+    };
+
+    // Safety: The caller guarantees that there are no other threads.
+    unsafe {
+        run_as::run_as_child(target_ctx, auid, agid, child_fn)
+            .expect("Failed to create an operation.")
     }
 }
 
@@ -503,9 +518,7 @@
 // then returns an empty byte vector.
 pub fn get_system_prop(name: &str) -> Vec<u8> {
     match rustutils::system_properties::read(name) {
-        Ok(Some(value)) => {
-            return value.as_bytes().to_vec();
-        }
+        Ok(Some(value)) => value.as_bytes().to_vec(),
         _ => {
             vec![]
         }
diff --git a/keystore2/tests/keystore2_client_update_subcomponent_tests.rs b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs
index e25e52a..0e38298 100644
--- a/keystore2/tests/keystore2_client_update_subcomponent_tests.rs
+++ b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs
@@ -22,7 +22,7 @@
 use keystore2_test_utils::{
     get_keystore_service, key_generations, key_generations::Error, run_as, SecLevel,
 };
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
 use rustutils::users::AID_USER_OFFSET;
 
 /// Generate a key and update its public certificate and certificate chain. Test should be able to
@@ -153,9 +153,6 @@
 /// permissions, test should be able to update public certificate and cert-chain successfully.
 #[test]
 fn keystore2_update_subcomponent_fails_permission_denied() {
-    static GRANTOR_SU_CTX: &str = "u:r:su:s0";
-    static GRANTEE_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-
     const USER_ID_1: u32 = 99;
     const APPLICATION_ID: u32 = 10001;
     static GRANTEE_1_UID: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID;
@@ -166,117 +163,102 @@
     static GRANTEE_2_GID: u32 = GRANTEE_2_UID;
 
     // Generate a key and grant it to multiple users with different access permissions.
-    // SAFETY: The test is run in a separate process with no other threads.
-    let mut granted_keys = unsafe {
-        run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            let sl = SecLevel::tee();
-            let alias = format!("ks_update_subcompo_test_1_{}", getuid());
-            let mut granted_keys = Vec::new();
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let alias = format!("ks_update_subcompo_test_1_{}", getuid());
+        let mut granted_keys = Vec::new();
 
-            let key_metadata = key_generations::generate_ec_p256_signing_key(
-                &sl,
-                Domain::APP,
-                -1,
-                Some(alias),
-                None,
-            )
+        let key_metadata =
+            key_generations::generate_ec_p256_signing_key(&sl, Domain::APP, -1, Some(alias), None)
+                .unwrap();
+
+        // Grant a key without update permission.
+        let access_vector = KeyPermission::GET_INFO.0;
+        let granted_key = sl
+            .keystore2
+            .grant(&key_metadata.key, GRANTEE_1_UID.try_into().unwrap(), access_vector)
             .unwrap();
+        assert_eq!(granted_key.domain, Domain::GRANT);
+        granted_keys.push(granted_key.nspace);
 
-            // Grant a key without update permission.
-            let access_vector = KeyPermission::GET_INFO.0;
-            let granted_key = sl
-                .keystore2
-                .grant(&key_metadata.key, GRANTEE_1_UID.try_into().unwrap(), access_vector)
-                .unwrap();
-            assert_eq!(granted_key.domain, Domain::GRANT);
-            granted_keys.push(granted_key.nspace);
+        // Grant a key with update permission.
+        let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::UPDATE.0;
+        let granted_key = sl
+            .keystore2
+            .grant(&key_metadata.key, GRANTEE_2_UID.try_into().unwrap(), access_vector)
+            .unwrap();
+        assert_eq!(granted_key.domain, Domain::GRANT);
+        granted_keys.push(granted_key.nspace);
 
-            // Grant a key with update permission.
-            let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::UPDATE.0;
-            let granted_key = sl
-                .keystore2
-                .grant(&key_metadata.key, GRANTEE_2_UID.try_into().unwrap(), access_vector)
-                .unwrap();
-            assert_eq!(granted_key.domain, Domain::GRANT);
-            granted_keys.push(granted_key.nspace);
-
-            granted_keys
-        })
+        granted_keys
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let mut granted_keys = unsafe { run_as::run_as_root(grantor_fn) };
+
     // Grantee context, try to update the key public certs, permission denied error is expected.
     let granted_key1_nspace = granted_keys.remove(0);
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_1_UID),
-            Gid::from_raw(GRANTEE_1_GID),
-            move || {
-                let keystore2 = get_keystore_service();
+    let grantee1_fn = move || {
+        let keystore2 = get_keystore_service();
 
-                let other_cert: [u8; 32] = [123; 32];
-                let other_cert_chain: [u8; 32] = [12; 32];
+        let other_cert: [u8; 32] = [123; 32];
+        let other_cert_chain: [u8; 32] = [12; 32];
 
-                let result = key_generations::map_ks_error(keystore2.updateSubcomponent(
-                    &KeyDescriptor {
-                        domain: Domain::GRANT,
-                        nspace: granted_key1_nspace,
-                        alias: None,
-                        blob: None,
-                    },
-                    Some(&other_cert),
-                    Some(&other_cert_chain),
-                ));
-                assert!(result.is_err());
-                assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+        let result = key_generations::map_ks_error(keystore2.updateSubcomponent(
+            &KeyDescriptor {
+                domain: Domain::GRANT,
+                nspace: granted_key1_nspace,
+                alias: None,
+                blob: None,
             },
-        )
+            Some(&other_cert),
+            Some(&other_cert_chain),
+        ));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_1_UID, GRANTEE_1_GID, grantee1_fn) };
+
     // Grantee context, update granted key public certs. Update should happen successfully.
     let granted_key2_nspace = granted_keys.remove(0);
-    // SAFETY: The test is run in a separate process with no other threads.
-    unsafe {
-        run_as::run_as(
-            GRANTEE_CTX,
-            Uid::from_raw(GRANTEE_2_UID),
-            Gid::from_raw(GRANTEE_2_GID),
-            move || {
-                let keystore2 = get_keystore_service();
+    let grantee2_fn = move || {
+        let keystore2 = get_keystore_service();
 
-                let other_cert: [u8; 32] = [124; 32];
-                let other_cert_chain: [u8; 32] = [13; 32];
+        let other_cert: [u8; 32] = [124; 32];
+        let other_cert_chain: [u8; 32] = [13; 32];
 
-                keystore2
-                    .updateSubcomponent(
-                        &KeyDescriptor {
-                            domain: Domain::GRANT,
-                            nspace: granted_key2_nspace,
-                            alias: None,
-                            blob: None,
-                        },
-                        Some(&other_cert),
-                        Some(&other_cert_chain),
-                    )
-                    .expect("updateSubcomponent should have succeeded.");
+        keystore2
+            .updateSubcomponent(
+                &KeyDescriptor {
+                    domain: Domain::GRANT,
+                    nspace: granted_key2_nspace,
+                    alias: None,
+                    blob: None,
+                },
+                Some(&other_cert),
+                Some(&other_cert_chain),
+            )
+            .expect("updateSubcomponent should have succeeded.");
 
-                let key_entry_response = keystore2
-                    .getKeyEntry(&KeyDescriptor {
-                        domain: Domain::GRANT,
-                        nspace: granted_key2_nspace,
-                        alias: None,
-                        blob: None,
-                    })
-                    .unwrap();
-                assert_eq!(Some(other_cert.to_vec()), key_entry_response.metadata.certificate);
-                assert_eq!(
-                    Some(other_cert_chain.to_vec()),
-                    key_entry_response.metadata.certificateChain
-                );
-            },
-        )
+        let key_entry_response = keystore2
+            .getKeyEntry(&KeyDescriptor {
+                domain: Domain::GRANT,
+                nspace: granted_key2_nspace,
+                alias: None,
+                blob: None,
+            })
+            .unwrap();
+        assert_eq!(Some(other_cert.to_vec()), key_entry_response.metadata.certificate);
+        assert_eq!(Some(other_cert_chain.to_vec()), key_entry_response.metadata.certificateChain);
     };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(GRANTEE_2_UID, GRANTEE_2_GID, grantee2_fn) };
 }
 
 #[test]
diff --git a/keystore2/tests/legacy_blobs/Android.bp b/keystore2/tests/legacy_blobs/Android.bp
index 0f310f5..92d1307 100644
--- a/keystore2/tests/legacy_blobs/Android.bp
+++ b/keystore2/tests/legacy_blobs/Android.bp
@@ -38,7 +38,6 @@
         "libkeystore2_crypto_rust",
         "libkeystore2_test_utils",
         "libkeystore2_with_test_utils",
-        "liblazy_static",
         "liblibc",
         "libnix",
         "librustutils",
diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
index 11a4c0b..d71f463 100644
--- a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
+++ b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
@@ -27,7 +27,7 @@
 use keystore2::utils::AesGcm;
 use keystore2_crypto::{Password, ZVec};
 use keystore2_test_utils::{get_keystore_service, key_generations, run_as, SecLevel};
-use nix::unistd::{getuid, Gid, Uid};
+use nix::unistd::getuid;
 use rustutils::users::AID_USER_OFFSET;
 use serde::{Deserialize, Serialize};
 use std::ops::Deref;
@@ -128,8 +128,6 @@
 fn keystore2_encrypted_characteristics() -> anyhow::Result<()> {
     let auid = 99 * AID_USER_OFFSET + 10001;
     let agid = 99 * AID_USER_OFFSET + 10001;
-    static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-    static TARGET_SU_CTX: &str = "u:r:su:s0";
 
     // Cleanup user directory if it exists
     let path_buf = PathBuf::from("/data/misc/keystore/user_99");
@@ -137,205 +135,202 @@
         std::fs::remove_dir_all(path_buf.as_path()).unwrap();
     }
 
-    // Safety: run_as must be called from a single threaded process.
-    // This device test is run as a separate single threaded process.
-    let mut gen_key_result = unsafe {
-        run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            // Remove user if already exist.
-            let maint_service = get_maintenance();
-            match maint_service.onUserRemoved(99) {
-                Ok(_) => {
-                    println!("User was existed, deleted successfully");
-                }
-                Err(e) => {
-                    println!("onUserRemoved error: {:#?}", e);
-                }
+    let gen_key_fn = || {
+        // Remove user if already exist.
+        let maint_service = get_maintenance();
+        match maint_service.onUserRemoved(99) {
+            Ok(_) => {
+                println!("User did exist, deleted successfully");
             }
-            let sl = SecLevel::tee();
+            Err(e) => {
+                println!("onUserRemoved error: {:#?}", e);
+            }
+        }
+        let sl = SecLevel::tee();
 
-            // Generate Key BLOB and prepare legacy keystore blob files.
-            let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") };
-            let key_metadata = key_generations::generate_ec_p256_signing_key(
-                &sl,
-                Domain::BLOB,
-                SELINUX_SHELL_NAMESPACE,
-                None,
-                att_challenge,
+        // Generate Key BLOB and prepare legacy keystore blob files.
+        let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") };
+        let key_metadata = key_generations::generate_ec_p256_signing_key(
+            &sl,
+            Domain::BLOB,
+            SELINUX_SHELL_NAMESPACE,
+            None,
+            att_challenge,
+        )
+        .expect("Failed to generate key blob");
+
+        // Create keystore file layout for user_99.
+        let pw: Password = PASSWORD.into();
+        let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
+        let super_key =
+            TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap());
+
+        let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
+        if !path_buf.as_path().is_dir() {
+            std::fs::create_dir(path_buf.as_path()).unwrap();
+        }
+        path_buf.push(".masterkey");
+        if !path_buf.as_path().is_file() {
+            std::fs::write(path_buf.as_path(), SUPERKEY).unwrap();
+        }
+
+        let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
+        path_buf.push("9910001_USRPKEY_authbound");
+        if !path_buf.as_path().is_file() {
+            make_encrypted_key_file(
+                path_buf.as_path(),
+                &super_key,
+                &key_metadata.key.blob.unwrap(),
             )
-            .expect("Failed to generate key blob");
+            .unwrap();
+        }
 
-            // Create keystore file layout for user_99.
-            let pw: Password = PASSWORD.into();
-            let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
-            let super_key =
-                TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap());
-
-            let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
-            if !path_buf.as_path().is_dir() {
-                std::fs::create_dir(path_buf.as_path()).unwrap();
-            }
-            path_buf.push(".masterkey");
-            if !path_buf.as_path().is_file() {
-                std::fs::write(path_buf.as_path(), SUPERKEY).unwrap();
-            }
-
-            let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
-            path_buf.push("9910001_USRPKEY_authbound");
-            if !path_buf.as_path().is_file() {
-                make_encrypted_key_file(
-                    path_buf.as_path(),
-                    &super_key,
-                    &key_metadata.key.blob.unwrap(),
-                )
+        let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
+        path_buf.push(".9910001_chr_USRPKEY_authbound");
+        if !path_buf.as_path().is_file() {
+            make_encrypted_characteristics_file(path_buf.as_path(), &super_key, KEY_PARAMETERS)
                 .unwrap();
-            }
+        }
 
+        let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
+        path_buf.push("9910001_USRCERT_authbound");
+        if !path_buf.as_path().is_file() {
+            make_cert_blob_file(path_buf.as_path(), key_metadata.certificate.as_ref().unwrap())
+                .unwrap();
+        }
+
+        if let Some(chain) = key_metadata.certificateChain.as_ref() {
             let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
-            path_buf.push(".9910001_chr_USRPKEY_authbound");
+            path_buf.push("9910001_CACERT_authbound");
             if !path_buf.as_path().is_file() {
-                make_encrypted_characteristics_file(path_buf.as_path(), &super_key, KEY_PARAMETERS)
-                    .unwrap();
+                make_cert_blob_file(path_buf.as_path(), chain).unwrap();
             }
+        }
 
-            let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
-            path_buf.push("9910001_USRCERT_authbound");
-            if !path_buf.as_path().is_file() {
-                make_cert_blob_file(path_buf.as_path(), key_metadata.certificate.as_ref().unwrap())
-                    .unwrap();
+        // Keystore2 disables the legacy importer when it finds the legacy database empty.
+        // However, if the device boots with an empty legacy database, the optimization kicks in
+        // and keystore2 never checks the legacy file system layout.
+        // So, restart keystore2 service to detect populated legacy database.
+        keystore2_restart_service();
+
+        let auth_service = get_authorization();
+        match auth_service.onDeviceUnlocked(99, Some(PASSWORD)) {
+            Ok(result) => {
+                println!("Unlock Result: {:?}", result);
             }
-
-            if let Some(chain) = key_metadata.certificateChain.as_ref() {
-                let mut path_buf = PathBuf::from("/data/misc/keystore/user_99");
-                path_buf.push("9910001_CACERT_authbound");
-                if !path_buf.as_path().is_file() {
-                    make_cert_blob_file(path_buf.as_path(), chain).unwrap();
-                }
+            Err(e) => {
+                panic!("Unlock should have succeeded: {:?}", e);
             }
+        }
 
-            // Keystore2 disables the legacy importer when it finds the legacy database empty.
-            // However, if the device boots with an empty legacy database, the optimization kicks in
-            // and keystore2 never checks the legacy file system layout.
-            // So, restart keystore2 service to detect populated legacy database.
-            keystore2_restart_service();
+        let mut key_params: Vec<KsKeyparameter> = Vec::new();
+        for param in key_metadata.authorizations {
+            let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
+            key_params.push(key_param);
+        }
 
-            let auth_service = get_authorization();
-            match auth_service.onDeviceUnlocked(99, Some(PASSWORD)) {
-                Ok(result) => {
-                    println!("Unlock Result: {:?}", result);
-                }
-                Err(e) => {
-                    panic!("Unlock should have succeeded: {:?}", e);
-                }
-            }
-
-            let mut key_params: Vec<KsKeyparameter> = Vec::new();
-            for param in key_metadata.authorizations {
-                let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
-                key_params.push(key_param);
-            }
-
-            KeygenResult {
-                cert: key_metadata.certificate.unwrap(),
-                cert_chain: key_metadata.certificateChain.unwrap_or_default(),
-                key_parameters: key_params,
-            }
-        })
+        KeygenResult {
+            cert: key_metadata.certificate.unwrap(),
+            cert_chain: key_metadata.certificateChain.unwrap_or_default(),
+            key_parameters: key_params,
+        }
     };
 
-    // Safety: run_as must be called from a single threaded process.
-    // This device test is run as a separate single threaded process.
-    unsafe {
-        run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
-            println!("UID: {}", getuid());
-            println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9910001));
-            println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9910001));
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let mut gen_key_result = unsafe { run_as::run_as_root(gen_key_fn) };
 
-            let test_alias = "authbound";
-            let keystore2 = get_keystore_service();
+    let use_key_fn = move || {
+        println!("UID: {}", getuid());
+        println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9910001));
+        println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9910001));
 
-            match keystore2.getKeyEntry(&KeyDescriptor {
-                domain: Domain::APP,
-                nspace: SELINUX_SHELL_NAMESPACE,
-                alias: Some(test_alias.to_string()),
-                blob: None,
-            }) {
-                Ok(key_entry_response) => {
-                    assert_eq!(
-                        key_entry_response.metadata.certificate.unwrap(),
-                        gen_key_result.cert
-                    );
-                    assert_eq!(
-                        key_entry_response.metadata.certificateChain.unwrap_or_default(),
-                        gen_key_result.cert_chain
-                    );
-                    assert_eq!(key_entry_response.metadata.key.domain, Domain::KEY_ID);
-                    assert_ne!(key_entry_response.metadata.key.nspace, 0);
-                    assert_eq!(
-                        key_entry_response.metadata.keySecurityLevel,
-                        SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT
-                    );
+        let test_alias = "authbound";
+        let keystore2 = get_keystore_service();
 
-                    // Preapare KsKeyParameter list from getKeEntry response Authorizations.
-                    let mut key_params: Vec<KsKeyparameter> = Vec::new();
-                    for param in key_entry_response.metadata.authorizations {
-                        let key_param =
-                            KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
-                        key_params.push(key_param);
-                    }
+        match keystore2.getKeyEntry(&KeyDescriptor {
+            domain: Domain::APP,
+            nspace: SELINUX_SHELL_NAMESPACE,
+            alias: Some(test_alias.to_string()),
+            blob: None,
+        }) {
+            Ok(key_entry_response) => {
+                assert_eq!(key_entry_response.metadata.certificate.unwrap(), gen_key_result.cert);
+                assert_eq!(
+                    key_entry_response.metadata.certificateChain.unwrap_or_default(),
+                    gen_key_result.cert_chain
+                );
+                assert_eq!(key_entry_response.metadata.key.domain, Domain::KEY_ID);
+                assert_ne!(key_entry_response.metadata.key.nspace, 0);
+                assert_eq!(
+                    key_entry_response.metadata.keySecurityLevel,
+                    SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT
+                );
 
-                    // Combine keyparameters from gen_key_result and keyparameters
-                    // from legacy key-char file.
-                    let mut legacy_file_key_params: Vec<KsKeyparameter> = Vec::new();
-                    match structured_test_params() {
-                        LegacyKeyCharacteristics::File(legacy_key_params) => {
-                            for param in &legacy_key_params {
-                                let mut present_in_gen_params = false;
-                                for gen_param in &gen_key_result.key_parameters {
-                                    if param.get_tag() == gen_param.get_tag() {
-                                        present_in_gen_params = true;
-                                    }
-                                }
-                                if !present_in_gen_params {
-                                    legacy_file_key_params.push(param.clone());
+                // Preapare KsKeyParameter list from getKeEntry response Authorizations.
+                let mut key_params: Vec<KsKeyparameter> = Vec::new();
+                for param in key_entry_response.metadata.authorizations {
+                    let key_param =
+                        KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
+                    key_params.push(key_param);
+                }
+
+                // Combine keyparameters from gen_key_result and keyparameters
+                // from legacy key-char file.
+                let mut legacy_file_key_params: Vec<KsKeyparameter> = Vec::new();
+                match structured_test_params() {
+                    LegacyKeyCharacteristics::File(legacy_key_params) => {
+                        for param in &legacy_key_params {
+                            let mut present_in_gen_params = false;
+                            for gen_param in &gen_key_result.key_parameters {
+                                if param.get_tag() == gen_param.get_tag() {
+                                    present_in_gen_params = true;
                                 }
                             }
-                        }
-                        _ => {
-                            panic!("Expecting file characteristics");
+                            if !present_in_gen_params {
+                                legacy_file_key_params.push(param.clone());
+                            }
                         }
                     }
-
-                    // Remove Key-Params which have security levels other than TRUSTED_ENVIRONMENT
-                    gen_key_result.key_parameters.retain(|in_element| {
-                        *in_element.security_level()
-                            == SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT
-                    });
-
-                    println!("GetKeyEntry response key params: {:#?}", key_params);
-                    println!("Generated key params: {:#?}", gen_key_result.key_parameters);
-
-                    gen_key_result.key_parameters.append(&mut legacy_file_key_params);
-
-                    println!("Combined key params: {:#?}", gen_key_result.key_parameters);
-
-                    // Validate all keyparameters present in getKeyEntry response.
-                    for param in &key_params {
-                        gen_key_result.key_parameters.retain(|in_element| *in_element != *param);
+                    _ => {
+                        panic!("Expecting file characteristics");
                     }
+                }
 
-                    println!(
-                        "GetKeyEntry response unmatched key params: {:#?}",
-                        gen_key_result.key_parameters
-                    );
-                    assert_eq!(gen_key_result.key_parameters.len(), 0);
+                // Remove Key-Params which have security levels other than TRUSTED_ENVIRONMENT
+                gen_key_result.key_parameters.retain(|in_element| {
+                    *in_element.security_level()
+                        == SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT
+                });
+
+                println!("GetKeyEntry response key params: {:#?}", key_params);
+                println!("Generated key params: {:#?}", gen_key_result.key_parameters);
+
+                gen_key_result.key_parameters.append(&mut legacy_file_key_params);
+
+                println!("Combined key params: {:#?}", gen_key_result.key_parameters);
+
+                // Validate all keyparameters present in getKeyEntry response.
+                for param in &key_params {
+                    gen_key_result.key_parameters.retain(|in_element| *in_element != *param);
                 }
-                Err(s) => {
-                    panic!("getKeyEntry should have succeeded. {:?}", s);
-                }
-            };
-        })
+
+                println!(
+                    "GetKeyEntry response unmatched key params: {:#?}",
+                    gen_key_result.key_parameters
+                );
+                assert_eq!(gen_key_result.key_parameters.len(), 0);
+            }
+            Err(s) => {
+                panic!("getKeyEntry should have succeeded. {:?}", s);
+            }
+        };
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(auid, agid, use_key_fn) };
+
     // Make sure keystore2 clean up imported legacy db.
     let path_buf = PathBuf::from("/data/misc/keystore/user_99");
     if path_buf.as_path().is_dir() {
@@ -367,7 +362,7 @@
 ///     6. To load and import the legacy key using its alias.
 ///     7. After successful key import validate the user cert and cert-chain with initially
 ///        generated blobs.
-///     8. Validate imported key perameters. Imported key parameters list should be the combination
+///     8. Validate imported key parameters. Imported key parameters list should be the combination
 ///        of the key-parameters in characteristics file and the characteristics according to
 ///        the augmentation rules. There might be duplicate entries with different values for the
 ///        parameters like OS_VERSION, OS_VERSION, BOOT_PATCHLEVEL, VENDOR_PATCHLEVEL etc.
@@ -376,8 +371,6 @@
 fn keystore2_encrypted_certificates() -> anyhow::Result<()> {
     let auid = 98 * AID_USER_OFFSET + 10001;
     let agid = 98 * AID_USER_OFFSET + 10001;
-    static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-    static TARGET_SU_CTX: &str = "u:r:su:s0";
 
     // Cleanup user directory if it exists
     let path_buf = PathBuf::from("/data/misc/keystore/user_98");
@@ -385,177 +378,171 @@
         std::fs::remove_dir_all(path_buf.as_path()).unwrap();
     }
 
-    // Safety: run_as must be called from a single threaded process.
-    // This device test is run as a separate single threaded process.
-    let gen_key_result = unsafe {
-        run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || {
-            // Remove user if already exist.
-            let maint_service = get_maintenance();
-            match maint_service.onUserRemoved(98) {
-                Ok(_) => {
-                    println!("User was existed, deleted successfully");
-                }
-                Err(e) => {
-                    println!("onUserRemoved error: {:#?}", e);
-                }
+    let gen_key_fn = || {
+        // Remove user if already exist.
+        let maint_service = get_maintenance();
+        match maint_service.onUserRemoved(98) {
+            Ok(_) => {
+                println!("User did exist, deleted successfully");
             }
+            Err(e) => {
+                println!("onUserRemoved error: {:#?}", e);
+            }
+        }
 
-            let sl = SecLevel::tee();
-            // Generate Key BLOB and prepare legacy keystore blob files.
-            let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") };
-            let key_metadata = key_generations::generate_ec_p256_signing_key(
-                &sl,
-                Domain::BLOB,
-                SELINUX_SHELL_NAMESPACE,
-                None,
-                att_challenge,
+        let sl = SecLevel::tee();
+        // Generate Key BLOB and prepare legacy keystore blob files.
+        let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") };
+        let key_metadata = key_generations::generate_ec_p256_signing_key(
+            &sl,
+            Domain::BLOB,
+            SELINUX_SHELL_NAMESPACE,
+            None,
+            att_challenge,
+        )
+        .expect("Failed to generate key blob");
+
+        // Create keystore file layout for user_98.
+        let pw: Password = PASSWORD.into();
+        let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
+        let super_key =
+            TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap());
+
+        let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
+        if !path_buf.as_path().is_dir() {
+            std::fs::create_dir(path_buf.as_path()).unwrap();
+        }
+        path_buf.push(".masterkey");
+        if !path_buf.as_path().is_file() {
+            std::fs::write(path_buf.as_path(), SUPERKEY).unwrap();
+        }
+
+        let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
+        path_buf.push("9810001_USRPKEY_authboundcertenc");
+        if !path_buf.as_path().is_file() {
+            make_encrypted_key_file(
+                path_buf.as_path(),
+                &super_key,
+                &key_metadata.key.blob.unwrap(),
             )
-            .expect("Failed to generate key blob");
+            .unwrap();
+        }
 
-            // Create keystore file layout for user_98.
-            let pw: Password = PASSWORD.into();
-            let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap());
-            let super_key =
-                TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap());
+        let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
+        path_buf.push(".9810001_chr_USRPKEY_authboundcertenc");
+        if !path_buf.as_path().is_file() {
+            std::fs::write(path_buf.as_path(), USRPKEY_AUTHBOUND_CHR).unwrap();
+        }
 
+        let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
+        path_buf.push("9810001_USRCERT_authboundcertenc");
+        if !path_buf.as_path().is_file() {
+            make_encrypted_usr_cert_file(
+                path_buf.as_path(),
+                &super_key,
+                key_metadata.certificate.as_ref().unwrap(),
+            )
+            .unwrap();
+        }
+
+        if let Some(chain) = key_metadata.certificateChain.as_ref() {
             let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
-            if !path_buf.as_path().is_dir() {
-                std::fs::create_dir(path_buf.as_path()).unwrap();
-            }
-            path_buf.push(".masterkey");
+            path_buf.push("9810001_CACERT_authboundcertenc");
             if !path_buf.as_path().is_file() {
-                std::fs::write(path_buf.as_path(), SUPERKEY).unwrap();
+                make_encrypted_ca_cert_file(path_buf.as_path(), &super_key, chain).unwrap();
             }
+        }
 
-            let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
-            path_buf.push("9810001_USRPKEY_authboundcertenc");
-            if !path_buf.as_path().is_file() {
-                make_encrypted_key_file(
-                    path_buf.as_path(),
-                    &super_key,
-                    &key_metadata.key.blob.unwrap(),
-                )
-                .unwrap();
+        // Keystore2 disables the legacy importer when it finds the legacy database empty.
+        // However, if the device boots with an empty legacy database, the optimization kicks in
+        // and keystore2 never checks the legacy file system layout.
+        // So, restart keystore2 service to detect populated legacy database.
+        keystore2_restart_service();
+
+        let auth_service = get_authorization();
+        match auth_service.onDeviceUnlocked(98, Some(PASSWORD)) {
+            Ok(result) => {
+                println!("Unlock Result: {:?}", result);
             }
-
-            let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
-            path_buf.push(".9810001_chr_USRPKEY_authboundcertenc");
-            if !path_buf.as_path().is_file() {
-                std::fs::write(path_buf.as_path(), USRPKEY_AUTHBOUND_CHR).unwrap();
+            Err(e) => {
+                panic!("Unlock should have succeeded: {:?}", e);
             }
+        }
 
-            let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
-            path_buf.push("9810001_USRCERT_authboundcertenc");
-            if !path_buf.as_path().is_file() {
-                make_encrypted_usr_cert_file(
-                    path_buf.as_path(),
-                    &super_key,
-                    key_metadata.certificate.as_ref().unwrap(),
-                )
-                .unwrap();
-            }
+        let mut key_params: Vec<KsKeyparameter> = Vec::new();
+        for param in key_metadata.authorizations {
+            let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
+            key_params.push(key_param);
+        }
 
-            if let Some(chain) = key_metadata.certificateChain.as_ref() {
-                let mut path_buf = PathBuf::from("/data/misc/keystore/user_98");
-                path_buf.push("9810001_CACERT_authboundcertenc");
-                if !path_buf.as_path().is_file() {
-                    make_encrypted_ca_cert_file(path_buf.as_path(), &super_key, chain).unwrap();
-                }
-            }
-
-            // Keystore2 disables the legacy importer when it finds the legacy database empty.
-            // However, if the device boots with an empty legacy database, the optimization kicks in
-            // and keystore2 never checks the legacy file system layout.
-            // So, restart keystore2 service to detect populated legacy database.
-            keystore2_restart_service();
-
-            let auth_service = get_authorization();
-            match auth_service.onDeviceUnlocked(98, Some(PASSWORD)) {
-                Ok(result) => {
-                    println!("Unlock Result: {:?}", result);
-                }
-                Err(e) => {
-                    panic!("Unlock should have succeeded: {:?}", e);
-                }
-            }
-
-            let mut key_params: Vec<KsKeyparameter> = Vec::new();
-            for param in key_metadata.authorizations {
-                let key_param = KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
-                key_params.push(key_param);
-            }
-
-            KeygenResult {
-                cert: key_metadata.certificate.unwrap(),
-                cert_chain: key_metadata.certificateChain.unwrap_or_default(),
-                key_parameters: key_params,
-            }
-        })
+        KeygenResult {
+            cert: key_metadata.certificate.unwrap(),
+            cert_chain: key_metadata.certificateChain.unwrap_or_default(),
+            key_parameters: key_params,
+        }
     };
 
-    // Safety: run_as must be called from a single threaded process.
-    // This device test is run as a separate single threaded process.
-    unsafe {
-        run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
-            println!("UID: {}", getuid());
-            println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9810001));
-            println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9810001));
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let gen_key_result = unsafe { run_as::run_as_root(gen_key_fn) };
 
-            let test_alias = "authboundcertenc";
-            let keystore2 = get_keystore_service();
+    let use_key_fn = move || {
+        println!("UID: {}", getuid());
+        println!("Android User ID: {}", rustutils::users::multiuser_get_user_id(9810001));
+        println!("Android app ID: {}", rustutils::users::multiuser_get_app_id(9810001));
 
-            match keystore2.getKeyEntry(&KeyDescriptor {
-                domain: Domain::APP,
-                nspace: SELINUX_SHELL_NAMESPACE,
-                alias: Some(test_alias.to_string()),
-                blob: None,
-            }) {
-                Ok(key_entry_response) => {
-                    assert_eq!(
-                        key_entry_response.metadata.certificate.unwrap(),
-                        gen_key_result.cert
-                    );
-                    assert_eq!(
-                        key_entry_response.metadata.certificateChain.unwrap_or_default(),
-                        gen_key_result.cert_chain
-                    );
+        let test_alias = "authboundcertenc";
+        let keystore2 = get_keystore_service();
 
-                    // Preapare KsKeyParameter list from getKeEntry response Authorizations.
-                    let mut key_params: Vec<KsKeyparameter> = Vec::new();
-                    for param in key_entry_response.metadata.authorizations {
-                        let key_param =
-                            KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
-                        key_params.push(key_param);
+        match keystore2.getKeyEntry(&KeyDescriptor {
+            domain: Domain::APP,
+            nspace: SELINUX_SHELL_NAMESPACE,
+            alias: Some(test_alias.to_string()),
+            blob: None,
+        }) {
+            Ok(key_entry_response) => {
+                assert_eq!(key_entry_response.metadata.certificate.unwrap(), gen_key_result.cert);
+                assert_eq!(
+                    key_entry_response.metadata.certificateChain.unwrap_or_default(),
+                    gen_key_result.cert_chain
+                );
+
+                // Preapare KsKeyParameter list from getKeEntry response Authorizations.
+                let mut key_params: Vec<KsKeyparameter> = Vec::new();
+                for param in key_entry_response.metadata.authorizations {
+                    let key_param =
+                        KsKeyparameter::new(param.keyParameter.into(), param.securityLevel);
+                    key_params.push(key_param);
+                }
+
+                println!("GetKeyEntry response key params: {:#?}", key_params);
+                println!("Generated key params: {:#?}", gen_key_result.key_parameters);
+                match structured_test_params_cache() {
+                    LegacyKeyCharacteristics::Cache(legacy_key_params) => {
+                        println!("Legacy key-char cache: {:#?}", legacy_key_params);
+                        // Validate all keyparameters present in getKeyEntry response.
+                        for param in &legacy_key_params {
+                            key_params.retain(|in_element| *in_element != *param);
+                        }
+
+                        println!("GetKeyEntry response unmatched key params: {:#?}", key_params);
+                        assert_eq!(key_params.len(), 0);
                     }
-
-                    println!("GetKeyEntry response key params: {:#?}", key_params);
-                    println!("Generated key params: {:#?}", gen_key_result.key_parameters);
-                    match structured_test_params_cache() {
-                        LegacyKeyCharacteristics::Cache(legacy_key_params) => {
-                            println!("Legacy key-char cache: {:#?}", legacy_key_params);
-                            // Validate all keyparameters present in getKeyEntry response.
-                            for param in &legacy_key_params {
-                                key_params.retain(|in_element| *in_element != *param);
-                            }
-
-                            println!(
-                                "GetKeyEntry response unmatched key params: {:#?}",
-                                key_params
-                            );
-                            assert_eq!(key_params.len(), 0);
-                        }
-                        _ => {
-                            panic!("Expecting file characteristics");
-                        }
+                    _ => {
+                        panic!("Expecting file characteristics");
                     }
                 }
-                Err(s) => {
-                    panic!("getKeyEntry should have succeeded. {:?}", s);
-                }
-            };
-        })
+            }
+            Err(s) => {
+                panic!("getKeyEntry should have succeeded. {:?}", s);
+            }
+        };
     };
 
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    unsafe { run_as::run_as_app(auid, agid, use_key_fn) };
+
     // Make sure keystore2 clean up imported legacy db.
     let path_buf = PathBuf::from("/data/misc/keystore/user_98");
     if path_buf.as_path().is_dir() {
diff --git a/keystore2/tests/user_auth.rs b/keystore2/tests/user_auth.rs
index 263ad56..789f54b 100644
--- a/keystore2/tests/user_auth.rs
+++ b/keystore2/tests/user_auth.rs
@@ -14,7 +14,9 @@
 
 //! Tests for user authentication interactions (via `IKeystoreAuthorization`).
 
-use crate::keystore2_client_test_utils::BarrierReached;
+use crate::keystore2_client_test_utils::{
+    BarrierReached, BarrierReachedWithData, get_vsr_api_level
+};
 use android_security_authorization::aidl::android::security::authorization::{
     IKeystoreAuthorization::IKeystoreAuthorization
 };
@@ -22,39 +24,50 @@
      IKeystoreMaintenance,
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-    Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, HardwareAuthToken::HardwareAuthToken,
-    HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel,
-    KeyPurpose::KeyPurpose
+    Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode,
+    HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
+    KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_hardware_gatekeeper::aidl::android::hardware::gatekeeper::{
+    IGatekeeper::IGatekeeper, IGatekeeper::ERROR_RETRY_TIMEOUT,
 };
 use android_system_keystore2::aidl::android::system::keystore2::{
     CreateOperationResponse::CreateOperationResponse, Domain::Domain, KeyDescriptor::KeyDescriptor,
     KeyMetadata::KeyMetadata,
 };
+use android_system_keystore2::binder::{ExceptionCode, Result as BinderResult};
 use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
     Timestamp::Timestamp,
 };
+use anyhow::Context;
 use keystore2_test_utils::{
-    get_keystore_service, run_as, authorizations::AuthSetBuilder,
+    authorizations::AuthSetBuilder, expect, get_keystore_service, run_as,
+    run_as::{ChannelReader, ChannelWriter}, expect_km_error,
 };
 use log::{warn, info};
-use nix::unistd::{Gid, Uid};
 use rustutils::users::AID_USER_OFFSET;
+use std::{time::Duration, thread::sleep};
 
 /// Test user ID.
 const TEST_USER_ID: i32 = 100;
-/// Fake password blob.
-static PASSWORD: &[u8] = &[
+/// Corresponding uid value.
+const UID: u32 = TEST_USER_ID as u32 * AID_USER_OFFSET + 1001;
+/// Fake synthetic password blob.
+static SYNTHETIC_PASSWORD: &[u8] = &[
     0x42, 0x39, 0x30, 0x37, 0x44, 0x37, 0x32, 0x37, 0x39, 0x39, 0x43, 0x42, 0x39, 0x41, 0x42, 0x30,
     0x34, 0x31, 0x30, 0x38, 0x46, 0x44, 0x33, 0x45, 0x39, 0x42, 0x32, 0x38, 0x36, 0x35, 0x41, 0x36,
     0x33, 0x44, 0x42, 0x42, 0x43, 0x36, 0x33, 0x42, 0x34, 0x39, 0x37, 0x33, 0x35, 0x45, 0x41, 0x41,
     0x32, 0x45, 0x31, 0x35, 0x43, 0x43, 0x46, 0x32, 0x39, 0x36, 0x33, 0x34, 0x31, 0x32, 0x41, 0x39,
 ];
-/// Fake SID value corresponding to Gatekeeper.
-static GK_SID: i64 = 123456;
-/// Fake SID value corresponding to a biometric authenticator.
-static BIO_SID1: i64 = 345678;
-/// Fake SID value corresponding to a biometric authenticator.
-static BIO_SID2: i64 = 456789;
+/// Gatekeeper password.
+static GK_PASSWORD: &[u8] = b"correcthorsebatterystaple";
+/// Fake SID base value corresponding to Gatekeeper.  Individual tests use different SIDs to reduce
+/// the chances of cross-contamination, calculated statically (because each test is forked into a
+/// separate process).
+static GK_FAKE_SID_BASE: i64 = 123400;
+/// Fake SID base value corresponding to a biometric authenticator.  Individual tests use different
+/// SIDs to reduce the chances of cross-contamination.
+static BIO_FAKE_SID_BASE: i64 = 345600;
 
 const WEAK_UNLOCK_ENABLED: bool = true;
 const WEAK_UNLOCK_DISABLED: bool = false;
@@ -68,6 +81,18 @@
     binder::get_interface("android.security.maintenance").unwrap()
 }
 
+/// Get the default Gatekeeper instance. This may fail on older devices where Gatekeeper is still a
+/// HIDL interface rather than AIDL.
+fn get_gatekeeper() -> Option<binder::Strong<dyn IGatekeeper>> {
+    binder::get_interface("android.hardware.gatekeeper.IGatekeeper/default").ok()
+}
+
+/// Indicate whether a Gatekeeper result indicates a delayed-retry is needed.
+fn is_gk_retry<T: std::fmt::Debug>(result: &BinderResult<T>) -> bool {
+    matches!(result, Err(s) if s.exception_code() == ExceptionCode::SERVICE_SPECIFIC
+                 && s.service_specific_error() == ERROR_RETRY_TIMEOUT)
+}
+
 fn abort_op(result: binder::Result<CreateOperationResponse>) {
     if let Ok(rsp) = result {
         if let Some(op) = rsp.iOperation {
@@ -86,156 +111,780 @@
 struct TestUser {
     id: i32,
     maint: binder::Strong<dyn IKeystoreMaintenance>,
+    gk: Option<binder::Strong<dyn IGatekeeper>>,
+    gk_sid: Option<i64>,
+    gk_handle: Vec<u8>,
 }
 
 impl TestUser {
     fn new() -> Self {
-        Self::new_user(TEST_USER_ID, PASSWORD)
+        Self::new_user(TEST_USER_ID, SYNTHETIC_PASSWORD)
     }
-    fn new_user(user_id: i32, password: &[u8]) -> Self {
+    fn new_user(user_id: i32, sp: &[u8]) -> Self {
         let maint = get_maintenance();
         maint.onUserAdded(user_id).expect("failed to add test user");
         maint
-            .initUserSuperKeys(user_id, password, /* allowExisting= */ false)
+            .initUserSuperKeys(user_id, sp, /* allowExisting= */ false)
             .expect("failed to init test user");
-        Self { id: user_id, maint }
+        let gk = get_gatekeeper();
+        let (gk_sid, gk_handle) = if let Some(gk) = &gk {
+            // AIDL Gatekeeper is available, so enroll a password.
+            loop {
+                let result = gk.enroll(user_id, &[], &[], GK_PASSWORD);
+                if is_gk_retry(&result) {
+                    sleep(Duration::from_secs(1));
+                    continue;
+                }
+                let rsp = result.expect("gk.enroll() failed");
+                info!("registered test user {user_id} as sid {} with GK", rsp.secureUserId);
+                break (Some(rsp.secureUserId), rsp.data);
+            }
+        } else {
+            (None, vec![])
+        };
+        Self { id: user_id, maint, gk, gk_sid, gk_handle }
+    }
+
+    /// Perform Gatekeeper verification, which will return a HAT on success.
+    fn gk_verify(&self, challenge: i64) -> Option<HardwareAuthToken> {
+        let Some(gk) = &self.gk else { return None };
+        loop {
+            let result = gk.verify(self.id, challenge, &self.gk_handle, GK_PASSWORD);
+            if is_gk_retry(&result) {
+                sleep(Duration::from_secs(1));
+                continue;
+            }
+            let rsp = result.expect("gk.verify failed");
+            break Some(rsp.hardwareAuthToken);
+        }
     }
 }
 
 impl Drop for TestUser {
     fn drop(&mut self) {
         let _ = self.maint.onUserRemoved(self.id);
+        if let Some(gk) = &self.gk {
+            info!("deregister test user {} with GK", self.id);
+            if let Err(e) = gk.deleteUser(self.id) {
+                warn!("failed to deregister test user {}: {e:?}", self.id);
+            }
+        }
     }
 }
 
 #[test]
-fn keystore2_test_unlocked_device_required() {
+fn test_auth_bound_timeout_with_gk() {
+    let bio_fake_sid1 = BIO_FAKE_SID_BASE + 1;
+    let bio_fake_sid2 = BIO_FAKE_SID_BASE + 2;
+    type Barrier = BarrierReachedWithData<Option<i64>>;
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("keystore2_client_tests")
             .with_max_level(log::LevelFilter::Debug),
     );
-    static CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
-    const UID: u32 = TEST_USER_ID as u32 * AID_USER_OFFSET + 1001;
 
-    // Safety: only one thread at this point, and nothing yet done with binder.
+    let child_fn = move |reader: &mut ChannelReader<Barrier>,
+                         writer: &mut ChannelWriter<Barrier>|
+          -> Result<(), run_as::Error> {
+        // Now we're in a new process, wait to be notified before starting.
+        let gk_sid: i64 = match reader.recv().0 {
+            Some(sid) => sid,
+            None => {
+                // There is no AIDL Gatekeeper available, so abandon the test.  It would be nice to
+                // know this before starting the child process, but finding it out requires Binder,
+                // which can't be used until after the child has forked.
+                return Ok(());
+            }
+        };
+
+        // Action A: create a new auth-bound key which requires auth in the last 3 seconds,
+        // and fail to start an operation using it.
+        let ks2 = get_keystore_service();
+        let sec_level =
+            ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+        let params = AuthSetBuilder::new()
+            .user_secure_id(gk_sid)
+            .user_secure_id(bio_fake_sid1)
+            .user_secure_id(bio_fake_sid2)
+            .user_auth_type(HardwareAuthenticatorType::ANY)
+            .auth_timeout(3)
+            .algorithm(Algorithm::EC)
+            .purpose(KeyPurpose::SIGN)
+            .purpose(KeyPurpose::VERIFY)
+            .digest(Digest::SHA_2_256)
+            .ec_curve(EcCurve::P_256);
+
+        let KeyMetadata { key, .. } = sec_level
+            .generateKey(
+                &KeyDescriptor {
+                    domain: Domain::APP,
+                    nspace: -1,
+                    alias: Some("auth-bound-timeout-1".to_string()),
+                    blob: None,
+                },
+                None,
+                &params,
+                0,
+                b"entropy",
+            )
+            .context("key generation failed")?;
+        info!("A: created auth-timeout key {key:?} bound to sids {gk_sid}, {bio_fake_sid1}, {bio_fake_sid2}");
+
+        // No HATs so cannot create an operation using the key.
+        let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        info!("A: failed auth-bound operation (no HAT) as expected {result:?}");
+
+        writer.send(&Barrier::new(None)); // A done.
+
+        // Action B: succeed when a valid HAT is available.
+        reader.recv();
+
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        expect!(result.is_ok());
+        let op = result.unwrap().iOperation.context("no operation in result")?;
+        let result = op.finish(Some(b"data"), None);
+        expect!(result.is_ok());
+        info!("B: performed auth-bound operation (with valid GK HAT) as expected");
+
+        writer.send(&Barrier::new(None)); // B done.
+
+        // Action C: fail again when the HAT is old enough to not even be checked.
+        reader.recv();
+        info!("C: wait so that any HAT times out");
+        sleep(Duration::from_secs(4));
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        info!("C: failed auth-bound operation (HAT is too old) as expected {result:?}");
+        writer.send(&Barrier::new(None)); // C done.
+
+        Ok(())
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
     let mut child_handle = unsafe {
         // Perform keystore actions while running as the test user.
-        run_as::run_as_child(
-            CTX,
-            Uid::from_raw(UID),
-            Gid::from_raw(UID),
-            move |reader, writer| -> Result<(), String> {
-                // Now we're in a new process, wait to be notified before starting.
-                reader.recv();
-
-                // Action A: create a new unlocked-device-required key (which thus requires
-                // super-encryption), while the device is unlocked.
-                let ks2 = get_keystore_service();
-                let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
-                let params = AuthSetBuilder::new()
-                    .unlocked_device_required()
-                    .algorithm(Algorithm::EC)
-                    .purpose(KeyPurpose::SIGN)
-                    .purpose(KeyPurpose::VERIFY)
-                    .digest(Digest::SHA_2_256)
-                    .ec_curve(EcCurve::P_256);
-
-                let KeyMetadata { key, .. } = sec_level
-                    .generateKey(
-                        &KeyDescriptor {
-                            domain: Domain::APP,
-                            nspace: -1,
-                            alias: Some("unlocked-device-required".to_string()),
-                            blob: None,
-                        },
-                        None,
-                        &params,
-                        0,
-                        b"entropy",
-                    )
-                    .expect("key generation failed");
-                info!("A: created unlocked-device-required key while unlocked {key:?}");
-                writer.send(&BarrierReached {}); // A done.
-
-                // Action B: fail to use the unlocked-device-required key while locked.
-                reader.recv();
-                let params =
-                    AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
-                let result = sec_level.createOperation(&key, &params, UNFORCED);
-                info!("B: use unlocked-device-required key while locked => {result:?}");
-                assert!(result.is_err());
-                writer.send(&BarrierReached {}); // B done.
-
-                // Action C: try to use the unlocked-device-required key while unlocked with a
-                // password.
-                reader.recv();
-                let result = sec_level.createOperation(&key, &params, UNFORCED);
-                info!("C: use unlocked-device-required key while lskf-unlocked => {result:?}");
-                assert!(result.is_ok(), "failed with {result:?}");
-                abort_op(result);
-                writer.send(&BarrierReached {}); // C done.
-
-                // Action D: try to use the unlocked-device-required key while unlocked with a weak
-                // biometric.
-                reader.recv();
-                let result = sec_level.createOperation(&key, &params, UNFORCED);
-                info!("D: use unlocked-device-required key while weak-locked => {result:?}");
-                assert!(result.is_ok(), "createOperation failed: {result:?}");
-                abort_op(result);
-                writer.send(&BarrierReached {}); // D done.
-
-                let _ = sec_level.deleteKey(&key);
-                Ok(())
-            },
-        )
+        run_as::run_as_child_app(UID, UID, child_fn)
     }
     .unwrap();
 
+    // Now that the separate process has been forked off, it's safe to use binder to setup a test
+    // user.
+    let _ks2 = get_keystore_service();
+    let user = TestUser::new();
+    if user.gk.is_none() {
+        // Can't run this test if there's no AIDL Gatekeeper.
+        child_handle.send(&Barrier::new(None));
+        assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+        return;
+    }
+    let user_id = user.id;
+    let auth_service = get_authorization();
+
+    // Lock and unlock to ensure super keys are already created.
+    auth_service
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+        .unwrap();
+    auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+
+    info!("trigger child process action A and wait for completion");
+    child_handle.send(&Barrier::new(Some(user.gk_sid.unwrap())));
+    child_handle.recv_or_die();
+
+    // Unlock with GK password to get a genuine auth token.
+    let real_hat = user.gk_verify(0).expect("failed to perform GK verify");
+    auth_service.addAuthToken(&real_hat).unwrap();
+
+    info!("trigger child process action B and wait for completion");
+    child_handle.send(&Barrier::new(None));
+    child_handle.recv_or_die();
+
+    info!("trigger child process action C and wait for completion");
+    child_handle.send(&Barrier::new(None));
+    child_handle.recv_or_die();
+
+    assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+#[test]
+fn test_auth_bound_timeout_failure() {
+    let gk_fake_sid = GK_FAKE_SID_BASE + 1;
+    let bio_fake_sid1 = BIO_FAKE_SID_BASE + 3;
+    let bio_fake_sid2 = BIO_FAKE_SID_BASE + 4;
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("keystore2_client_tests")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+
+    let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
+                         writer: &mut ChannelWriter<BarrierReached>|
+          -> Result<(), run_as::Error> {
+        // Now we're in a new process, wait to be notified before starting.
+        reader.recv();
+
+        // Action A: create a new auth-bound key which requires auth in the last 3 seconds,
+        // and fail to start an operation using it.
+        let ks2 = get_keystore_service();
+
+        let sec_level =
+            ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+        let params = AuthSetBuilder::new()
+            .user_secure_id(bio_fake_sid1)
+            .user_secure_id(bio_fake_sid2)
+            .user_auth_type(HardwareAuthenticatorType::ANY)
+            .auth_timeout(3)
+            .algorithm(Algorithm::EC)
+            .purpose(KeyPurpose::SIGN)
+            .purpose(KeyPurpose::VERIFY)
+            .digest(Digest::SHA_2_256)
+            .ec_curve(EcCurve::P_256);
+
+        let KeyMetadata { key, .. } = sec_level
+            .generateKey(
+                &KeyDescriptor {
+                    domain: Domain::APP,
+                    nspace: -1,
+                    alias: Some("auth-bound-timeout-2".to_string()),
+                    blob: None,
+                },
+                None,
+                &params,
+                0,
+                b"entropy",
+            )
+            .context("key generation failed")?;
+        info!("A: created auth-timeout key {key:?} bound to sids {bio_fake_sid1}, {bio_fake_sid2}");
+
+        // No HATs so cannot create an operation using the key.
+        let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        info!("A: failed auth-bound operation (no HAT) as expected {result:?}");
+
+        writer.send(&BarrierReached {}); // A done.
+
+        // Action B: fail again when an invalid HAT is available.
+        reader.recv();
+
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        expect!(result.is_err());
+        if get_vsr_api_level() >= 35 {
+            // Older devices may report an incorrect error code when presented with an invalid auth
+            // token.
+            expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        }
+        info!("B: failed auth-bound operation (HAT is invalid) as expected {result:?}");
+
+        writer.send(&BarrierReached {}); // B done.
+
+        // Action C: fail again when the HAT is old enough to not even be checked.
+        reader.recv();
+        info!("C: wait so that any HAT times out");
+        sleep(Duration::from_secs(4));
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        info!("C: failed auth-bound operation (HAT is too old) as expected {result:?}");
+        writer.send(&BarrierReached {}); // C done.
+
+        Ok(())
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let mut child_handle = unsafe {
+        // Perform keystore actions while running as the test user.
+        run_as::run_as_child_app(UID, UID, child_fn)
+    }
+    .unwrap();
+
+    // Now that the separate process has been forked off, it's safe to use binder to setup a test
+    // user.
+    let _ks2 = get_keystore_service();
+    let user = TestUser::new();
+    let user_id = user.id;
+    let auth_service = get_authorization();
+
+    // Lock and unlock to ensure super keys are already created.
+    auth_service
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+        .unwrap();
+    auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+    auth_service.addAuthToken(&fake_lskf_token(gk_fake_sid)).unwrap();
+
+    info!("trigger child process action A and wait for completion");
+    child_handle.send(&BarrierReached {});
+    child_handle.recv_or_die();
+
+    // Unlock with password and a fake auth token that matches the key
+    auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+    auth_service.addAuthToken(&fake_bio_lskf_token(gk_fake_sid, bio_fake_sid1)).unwrap();
+
+    info!("trigger child process action B and wait for completion");
+    child_handle.send(&BarrierReached {});
+    child_handle.recv_or_die();
+
+    info!("trigger child process action C and wait for completion");
+    child_handle.send(&BarrierReached {});
+    child_handle.recv_or_die();
+
+    assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+#[test]
+fn test_auth_bound_per_op_with_gk() {
+    let bio_fake_sid1 = BIO_FAKE_SID_BASE + 5;
+    let bio_fake_sid2 = BIO_FAKE_SID_BASE + 6;
+    type Barrier = BarrierReachedWithData<Option<i64>>;
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("keystore2_client_tests")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+
+    let child_fn = move |reader: &mut ChannelReader<Barrier>,
+                         writer: &mut ChannelWriter<Barrier>|
+          -> Result<(), run_as::Error> {
+        // Now we're in a new process, wait to be notified before starting.
+        let gk_sid: i64 = match reader.recv().0 {
+            Some(sid) => sid,
+            None => {
+                // There is no AIDL Gatekeeper available, so abandon the test.  It would be nice to
+                // know this before starting the child process, but finding it out requires Binder,
+                // which can't be used until after the child has forked.
+                return Ok(());
+            }
+        };
+
+        // Action A: create a new auth-bound key which requires auth-per-operation (because
+        // AUTH_TIMEOUT is not specified), and fail to finish an operation using it.
+        let ks2 = get_keystore_service();
+        let sec_level =
+            ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+        let params = AuthSetBuilder::new()
+            .user_secure_id(gk_sid)
+            .user_secure_id(bio_fake_sid1)
+            .user_auth_type(HardwareAuthenticatorType::ANY)
+            .algorithm(Algorithm::EC)
+            .purpose(KeyPurpose::SIGN)
+            .purpose(KeyPurpose::VERIFY)
+            .digest(Digest::SHA_2_256)
+            .ec_curve(EcCurve::P_256);
+
+        let KeyMetadata { key, .. } = sec_level
+            .generateKey(
+                &KeyDescriptor {
+                    domain: Domain::APP,
+                    nspace: -1,
+                    alias: Some("auth-per-op-1".to_string()),
+                    blob: None,
+                },
+                None,
+                &params,
+                0,
+                b"entropy",
+            )
+            .context("key generation failed")?;
+        info!("A: created auth-per-op key {key:?} bound to sids {gk_sid}, {bio_fake_sid1}");
+
+        // We can create an operation using the key...
+        let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+        let result = sec_level
+            .createOperation(&key, &params, UNFORCED)
+            .expect("failed to create auth-per-op operation");
+        let op = result.iOperation.context("no operation in result")?;
+        info!("A: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+
+        // .. but attempting to finish the operation fails because Keystore can't find a HAT.
+        let result = op.finish(Some(b"data"), None);
+        expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        info!("A: failed auth-per-op op (no HAT) as expected {result:?}");
+
+        writer.send(&Barrier::new(None)); // A done.
+
+        // Action B: start an operation and pass out the challenge
+        reader.recv();
+        let result = sec_level
+            .createOperation(&key, &params, UNFORCED)
+            .expect("failed to create auth-per-op operation");
+        let op = result.iOperation.context("no operation in result")?;
+        info!("B: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+        writer.send(&Barrier::new(Some(result.operationChallenge.unwrap().challenge))); // B done.
+
+        // Action C: finishing the operation succeeds now there's a per-op HAT.
+        reader.recv();
+        let result = op.finish(Some(b"data"), None);
+        expect!(result.is_ok());
+        info!("C: performed auth-per-op op expected");
+        writer.send(&Barrier::new(None)); // D done.
+
+        Ok(())
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let mut child_handle = unsafe {
+        // Perform keystore actions while running as the test user.
+        run_as::run_as_child_app(UID, UID, child_fn)
+    }
+    .unwrap();
+
+    // Now that the separate process has been forked off, it's safe to use binder to setup a test
+    // user.
+    let _ks2 = get_keystore_service();
+    let user = TestUser::new();
+    if user.gk.is_none() {
+        // Can't run this test if there's no AIDL Gatekeeper.
+        child_handle.send(&Barrier::new(None));
+        assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+        return;
+    }
+    let user_id = user.id;
+    let auth_service = get_authorization();
+
+    // Lock and unlock to ensure super keys are already created.
+    auth_service
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+        .unwrap();
+    auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+
+    info!("trigger child process action A and wait for completion");
+    child_handle.send(&Barrier::new(Some(user.gk_sid.unwrap())));
+    child_handle.recv_or_die();
+
+    info!("trigger child process action B and wait for completion");
+    child_handle.send(&Barrier::new(None));
+    let challenge = child_handle.recv_or_die().0.expect("no challenge");
+
+    // Unlock with GK and the challenge to get a genuine per-op auth token
+    let real_hat = user.gk_verify(challenge).expect("failed to perform GK verify");
+    auth_service.addAuthToken(&real_hat).unwrap();
+
+    info!("trigger child process action C and wait for completion");
+    child_handle.send(&Barrier::new(None));
+    child_handle.recv_or_die();
+
+    assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+#[test]
+fn test_auth_bound_per_op_with_gk_failure() {
+    let bio_fake_sid1 = BIO_FAKE_SID_BASE + 7;
+    let bio_fake_sid2 = BIO_FAKE_SID_BASE + 8;
+    type Barrier = BarrierReachedWithData<Option<i64>>;
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("keystore2_client_tests")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+
+    let child_fn = move |reader: &mut ChannelReader<Barrier>,
+                         writer: &mut ChannelWriter<Barrier>|
+          -> Result<(), run_as::Error> {
+        // Now we're in a new process, wait to be notified before starting.
+        let gk_sid: i64 = match reader.recv().0 {
+            Some(sid) => sid,
+            None => {
+                // There is no AIDL Gatekeeper available, so abandon the test.  It would be nice to
+                // know this before starting the child process, but finding it out requires Binder,
+                // which can't be used until after the child has forked.
+                return Ok(());
+            }
+        };
+
+        // Action A: create a new auth-bound key which requires auth-per-operation (because
+        // AUTH_TIMEOUT is not specified), and fail to finish an operation using it.
+        let ks2 = get_keystore_service();
+        let sec_level =
+            ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+        let params = AuthSetBuilder::new()
+            .user_secure_id(gk_sid)
+            .user_secure_id(bio_fake_sid1)
+            .user_auth_type(HardwareAuthenticatorType::ANY)
+            .algorithm(Algorithm::EC)
+            .purpose(KeyPurpose::SIGN)
+            .purpose(KeyPurpose::VERIFY)
+            .digest(Digest::SHA_2_256)
+            .ec_curve(EcCurve::P_256);
+
+        let KeyMetadata { key, .. } = sec_level
+            .generateKey(
+                &KeyDescriptor {
+                    domain: Domain::APP,
+                    nspace: -1,
+                    alias: Some("auth-per-op-2".to_string()),
+                    blob: None,
+                },
+                None,
+                &params,
+                0,
+                b"entropy",
+            )
+            .context("key generation failed")?;
+        info!("A: created auth-per-op key {key:?} bound to sids {gk_sid}, {bio_fake_sid1}");
+
+        // We can create an operation using the key...
+        let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+        let result = sec_level
+            .createOperation(&key, &params, UNFORCED)
+            .expect("failed to create auth-per-op operation");
+        let op = result.iOperation.context("no operation in result")?;
+        info!("A: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+
+        // .. but attempting to finish the operation fails because Keystore can't find a HAT.
+        let result = op.finish(Some(b"data"), None);
+        expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        info!("A: failed auth-per-op op (no HAT) as expected {result:?}");
+
+        writer.send(&Barrier::new(None)); // A done.
+
+        // Action B: fail again when an irrelevant HAT is available.
+        reader.recv();
+
+        let result = sec_level
+            .createOperation(&key, &params, UNFORCED)
+            .expect("failed to create auth-per-op operation");
+        let op = result.iOperation.context("no operation in result")?;
+        info!("B: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+        // The operation fails because the HAT that Keystore received is not related to the
+        // challenge.
+        let result = op.finish(Some(b"data"), None);
+        expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        info!("B: failed auth-per-op op (HAT is not per-op) as expected {result:?}");
+
+        writer.send(&Barrier::new(None)); // B done.
+
+        // Action C: start an operation and pass out the challenge
+        reader.recv();
+        let result = sec_level
+            .createOperation(&key, &params, UNFORCED)
+            .expect("failed to create auth-per-op operation");
+        let op = result.iOperation.context("no operation in result")?;
+        info!("C: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
+        writer.send(&Barrier::new(Some(result.operationChallenge.unwrap().challenge))); // C done.
+
+        // Action D: finishing the operation still fails because the per-op HAT
+        // is invalid (the HMAC signature is faked and so the secure world
+        // rejects the HAT).
+        reader.recv();
+        let result = op.finish(Some(b"data"), None);
+        expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        info!("D: failed auth-per-op op (HAT is per-op but invalid) as expected {result:?}");
+        writer.send(&Barrier::new(None)); // D done.
+
+        Ok(())
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let mut child_handle = unsafe {
+        // Perform keystore actions while running as the test user.
+        run_as::run_as_child_app(UID, UID, child_fn)
+    }
+    .unwrap();
+
+    // Now that the separate process has been forked off, it's safe to use binder to setup a test
+    // user.
+    let _ks2 = get_keystore_service();
+    let user = TestUser::new();
+    if user.gk.is_none() {
+        // Can't run this test if there's no AIDL Gatekeeper.
+        child_handle.send(&Barrier::new(None));
+        assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+        return;
+    }
+    let user_id = user.id;
+    let auth_service = get_authorization();
+
+    // Lock and unlock to ensure super keys are already created.
+    auth_service
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+        .unwrap();
+    auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+
+    info!("trigger child process action A and wait for completion");
+    let gk_sid = user.gk_sid.unwrap();
+    child_handle.send(&Barrier::new(Some(gk_sid)));
+    child_handle.recv_or_die();
+
+    // Unlock with password and a fake auth token.
+    auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+    auth_service.addAuthToken(&fake_lskf_token(gk_sid)).unwrap();
+
+    info!("trigger child process action B and wait for completion");
+    child_handle.send(&Barrier::new(None));
+    child_handle.recv_or_die();
+
+    info!("trigger child process action C and wait for completion");
+    child_handle.send(&Barrier::new(None));
+    let challenge = child_handle.recv_or_die().0.expect("no challenge");
+
+    // Add a fake auth token with the challenge value.
+    auth_service.addAuthToken(&fake_lskf_token_with_challenge(gk_sid, challenge)).unwrap();
+
+    info!("trigger child process action D and wait for completion");
+    child_handle.send(&Barrier::new(None));
+    child_handle.recv_or_die();
+
+    assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+#[test]
+fn test_unlocked_device_required() {
+    let gk_fake_sid = GK_FAKE_SID_BASE + 3;
+    let bio_fake_sid1 = BIO_FAKE_SID_BASE + 9;
+    let bio_fake_sid2 = BIO_FAKE_SID_BASE + 10;
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("keystore2_client_tests")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+
+    let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
+                         writer: &mut ChannelWriter<BarrierReached>|
+          -> Result<(), run_as::Error> {
+        let ks2 = get_keystore_service();
+        if ks2.getInterfaceVersion().unwrap() < 4 {
+            // Assuming `IKeystoreAuthorization::onDeviceLocked` and
+            // `IKeystoreAuthorization::onDeviceUnlocked` APIs will be supported on devices
+            // with `IKeystoreService` >= 4.
+            return Ok(());
+        }
+
+        // Now we're in a new process, wait to be notified before starting.
+        reader.recv();
+
+        // Action A: create a new unlocked-device-required key (which thus requires
+        // super-encryption), while the device is unlocked.
+        let sec_level =
+            ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).context("no TEE")?;
+        let params = AuthSetBuilder::new()
+            .no_auth_required()
+            .unlocked_device_required()
+            .algorithm(Algorithm::EC)
+            .purpose(KeyPurpose::SIGN)
+            .purpose(KeyPurpose::VERIFY)
+            .digest(Digest::SHA_2_256)
+            .ec_curve(EcCurve::P_256);
+
+        let KeyMetadata { key, .. } = sec_level
+            .generateKey(
+                &KeyDescriptor {
+                    domain: Domain::APP,
+                    nspace: -1,
+                    alias: Some("unlocked-device-required".to_string()),
+                    blob: None,
+                },
+                None,
+                &params,
+                0,
+                b"entropy",
+            )
+            .context("key generation failed")?;
+        info!("A: created unlocked-device-required key while unlocked {key:?}");
+        writer.send(&BarrierReached {}); // A done.
+
+        // Action B: fail to use the unlocked-device-required key while locked.
+        reader.recv();
+        let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        info!("B: use unlocked-device-required key while locked => {result:?}");
+        expect_km_error!(&result, ErrorCode::DEVICE_LOCKED);
+        writer.send(&BarrierReached {}); // B done.
+
+        // Action C: try to use the unlocked-device-required key while unlocked with a
+        // password.
+        reader.recv();
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        info!("C: use unlocked-device-required key while lskf-unlocked => {result:?}");
+        expect!(result.is_ok(), "failed with {result:?}");
+        abort_op(result);
+        writer.send(&BarrierReached {}); // C done.
+
+        // Action D: try to use the unlocked-device-required key while unlocked with a weak
+        // biometric.
+        reader.recv();
+        let result = sec_level.createOperation(&key, &params, UNFORCED);
+        info!("D: use unlocked-device-required key while weak-locked => {result:?}");
+        expect!(result.is_ok(), "createOperation failed: {result:?}");
+        abort_op(result);
+        writer.send(&BarrierReached {}); // D done.
+
+        Ok(())
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder.
+    let mut child_handle = unsafe {
+        // Perform keystore actions while running as the test user.
+        run_as::run_as_child_app(UID, UID, child_fn)
+    }
+    .unwrap();
+
+    let ks2 = get_keystore_service();
+    if ks2.getInterfaceVersion().unwrap() < 4 {
+        // Assuming `IKeystoreAuthorization::onDeviceLocked` and
+        // `IKeystoreAuthorization::onDeviceUnlocked` APIs will be supported on devices
+        // with `IKeystoreService` >= 4.
+        assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+        return;
+    }
     // Now that the separate process has been forked off, it's safe to use binder.
     let user = TestUser::new();
     let user_id = user.id;
     let auth_service = get_authorization();
 
     // Lock and unlock to ensure super keys are already created.
-    auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_DISABLED).unwrap();
-    auth_service.onDeviceUnlocked(user_id, Some(PASSWORD)).unwrap();
-    auth_service.addAuthToken(&fake_lskf_token(GK_SID)).unwrap();
+    auth_service
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+        .unwrap();
+    auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+    auth_service.addAuthToken(&fake_lskf_token(gk_fake_sid)).unwrap();
 
     info!("trigger child process action A while unlocked and wait for completion");
     child_handle.send(&BarrierReached {});
-    child_handle.recv();
+    child_handle.recv_or_die();
 
     // Move to locked and don't allow weak unlock, so super keys are wiped.
-    auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_DISABLED).unwrap();
+    auth_service
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
+        .unwrap();
 
     info!("trigger child process action B while locked and wait for completion");
     child_handle.send(&BarrierReached {});
-    child_handle.recv();
+    child_handle.recv_or_die();
 
     // Unlock with password => loads super key from database.
-    auth_service.onDeviceUnlocked(user_id, Some(PASSWORD)).unwrap();
-    auth_service.addAuthToken(&fake_lskf_token(GK_SID)).unwrap();
+    auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
+    auth_service.addAuthToken(&fake_lskf_token(gk_fake_sid)).unwrap();
 
     info!("trigger child process action C while lskf-unlocked and wait for completion");
     child_handle.send(&BarrierReached {});
-    child_handle.recv();
+    child_handle.recv_or_die();
 
     // Move to locked and allow weak unlock, then do a weak unlock.
-    auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_ENABLED).unwrap();
+    auth_service
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_ENABLED)
+        .unwrap();
     auth_service.onDeviceUnlocked(user_id, None).unwrap();
 
     info!("trigger child process action D while weak-unlocked and wait for completion");
     child_handle.send(&BarrierReached {});
-    child_handle.recv();
+    child_handle.recv_or_die();
 
     assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
 }
 
 /// Generate a fake [`HardwareAuthToken`] for the given sid.
 fn fake_lskf_token(gk_sid: i64) -> HardwareAuthToken {
+    fake_lskf_token_with_challenge(gk_sid, 0)
+}
+
+/// Generate a fake [`HardwareAuthToken`] for the given sid and challenge.
+fn fake_lskf_token_with_challenge(gk_sid: i64, challenge: i64) -> HardwareAuthToken {
     HardwareAuthToken {
-        challenge: 0,
+        challenge,
         userId: gk_sid,
         authenticatorId: 0,
         authenticatorType: HardwareAuthenticatorType::PASSWORD,
@@ -243,3 +892,15 @@
         mac: vec![1, 2, 3],
     }
 }
+
+/// Generate a fake [`HardwareAuthToken`] for the given sids
+fn fake_bio_lskf_token(gk_sid: i64, bio_sid: i64) -> HardwareAuthToken {
+    HardwareAuthToken {
+        challenge: 0,
+        userId: gk_sid,
+        authenticatorId: bio_sid,
+        authenticatorType: HardwareAuthenticatorType::FINGERPRINT,
+        timestamp: Timestamp { milliSeconds: 123 },
+        mac: vec![1, 2, 3],
+    }
+}
diff --git a/keystore2/watchdog/Android.bp b/keystore2/watchdog/Android.bp
index 5074388..9a99f10 100644
--- a/keystore2/watchdog/Android.bp
+++ b/keystore2/watchdog/Android.bp
@@ -26,6 +26,7 @@
     crate_name: "watchdog_rs",
     srcs: ["src/lib.rs"],
     rustlibs: [
+        "libchrono",
         "liblog_rust",
     ],
 }
diff --git a/keystore2/watchdog/src/lib.rs b/keystore2/watchdog/src/lib.rs
index baed5ca..f6a1291 100644
--- a/keystore2/watchdog/src/lib.rs
+++ b/keystore2/watchdog/src/lib.rs
@@ -29,6 +29,9 @@
     time::{Duration, Instant},
 };
 
+#[cfg(test)]
+mod tests;
+
 /// Represents a Watchdog record. It can be created with `Watchdog::watch` or
 /// `Watchdog::watch_with`. It disarms the record when dropped.
 pub struct WatchPoint {
@@ -61,6 +64,18 @@
     context: Option<Box<dyn std::fmt::Debug + Send + 'static>>,
 }
 
+impl Record {
+    // Return a string representation of the start time of the record.
+    //
+    // Times are hard. This may not be accurate (e.g. if the system clock has been modified since
+    // the watchdog started), but it's _really_ useful to get a starting wall time for overrunning
+    // watchdogs.
+    fn started_utc(&self) -> String {
+        let started_utc = chrono::Utc::now() - self.started.elapsed();
+        format!("{}", started_utc.format("%m-%d %H:%M:%S%.3f UTC"))
+    }
+}
+
 struct WatchdogState {
     state: State,
     thread: Option<thread::JoinHandle<()>>,
@@ -134,8 +149,13 @@
             .filter(|(_, r)| r.deadline.saturating_duration_since(now) == Duration::new(0, 0))
             .collect();
 
-        log::warn!("When extracting from a bug report, please include this header");
-        log::warn!("and all {} records below.", overdue_records.len());
+        log::warn!(
+            concat!(
+                "When extracting from a bug report, please include this header ",
+                "and all {} records below (to footer)"
+            ),
+            overdue_records.len()
+        );
 
         // Watch points can be nested, i.e., a single thread may have multiple armed
         // watch points. And the most recent on each thread (thread recent) is closest to the point
@@ -166,9 +186,10 @@
                 match &r.context {
                     Some(ctx) => {
                         log::warn!(
-                            "{:?} {} Pending: {:?} Overdue {:?} for {:?}",
+                            "{:?} {} Started: {} Pending: {:?} Overdue {:?} for {:?}",
                             i.tid,
                             i.id,
+                            r.started_utc(),
                             r.started.elapsed(),
                             r.deadline.elapsed(),
                             ctx
@@ -176,9 +197,10 @@
                     }
                     None => {
                         log::warn!(
-                            "{:?} {} Pending: {:?} Overdue {:?}",
+                            "{:?} {} Started: {} Pending: {:?} Overdue {:?}",
                             i.tid,
                             i.id,
+                            r.started_utc(),
                             r.started.elapsed(),
                             r.deadline.elapsed()
                         );
@@ -190,7 +212,32 @@
     }
 
     fn disarm(&mut self, index: Index) {
-        self.records.remove(&index);
+        let result = self.records.remove(&index);
+        if let Some(record) = result {
+            let now = Instant::now();
+            let timeout_left = record.deadline.saturating_duration_since(now);
+            if timeout_left == Duration::new(0, 0) {
+                match &record.context {
+                    Some(ctx) => log::info!(
+                        "Watchdog complete for: {:?} {} Started: {} Pending: {:?} Overdue {:?} for {:?}",
+                        index.tid,
+                        index.id,
+                        record.started_utc(),
+                        record.started.elapsed(),
+                        record.deadline.elapsed(),
+                        ctx
+                    ),
+                    None => log::info!(
+                        "Watchdog complete for: {:?} {} Started: {} Pending: {:?} Overdue {:?}",
+                        index.tid,
+                        index.id,
+                        record.started_utc(),
+                        record.started.elapsed(),
+                        record.deadline.elapsed()
+                    ),
+                }
+            }
+        }
     }
 
     fn arm(&mut self, index: Index, record: Record) {
@@ -332,86 +379,3 @@
         state.state = State::Running;
     }
 }
-
-#[cfg(test)]
-mod tests {
-
-    use super::*;
-    use std::sync::atomic;
-    use std::thread;
-    use std::time::Duration;
-
-    /// Count the number of times `Debug::fmt` is invoked.
-    #[derive(Default, Clone)]
-    struct DebugCounter(Arc<atomic::AtomicU8>);
-    impl DebugCounter {
-        fn value(&self) -> u8 {
-            self.0.load(atomic::Ordering::Relaxed)
-        }
-    }
-    impl std::fmt::Debug for DebugCounter {
-        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-            let count = self.0.fetch_add(1, atomic::Ordering::Relaxed);
-            write!(f, "hit_count: {count}")
-        }
-    }
-
-    #[test]
-    fn test_watchdog() {
-        android_logger::init_once(
-            android_logger::Config::default()
-                .with_tag("keystore2_watchdog_tests")
-                .with_max_level(log::LevelFilter::Debug),
-        );
-
-        let wd = Watchdog::new(Duration::from_secs(3));
-        let hit_counter = DebugCounter::default();
-        let wp = Watchdog::watch_with(
-            &wd,
-            "test_watchdog",
-            Duration::from_millis(100),
-            hit_counter.clone(),
-        );
-        assert_eq!(0, hit_counter.value());
-        thread::sleep(Duration::from_millis(500));
-        assert_eq!(1, hit_counter.value());
-        thread::sleep(Duration::from_secs(1));
-        assert_eq!(1, hit_counter.value());
-
-        drop(wp);
-        thread::sleep(Duration::from_secs(10));
-        assert_eq!(1, hit_counter.value());
-        let (_, ref state) = *wd.state;
-        let state = state.lock().unwrap();
-        assert_eq!(state.state, State::NotRunning);
-    }
-
-    #[test]
-    fn test_watchdog_backoff() {
-        android_logger::init_once(
-            android_logger::Config::default()
-                .with_tag("keystore2_watchdog_tests")
-                .with_max_level(log::LevelFilter::Debug),
-        );
-
-        let wd = Watchdog::new(Duration::from_secs(3));
-        let hit_counter = DebugCounter::default();
-        let wp = Watchdog::watch_with(
-            &wd,
-            "test_watchdog",
-            Duration::from_millis(100),
-            hit_counter.clone(),
-        );
-        assert_eq!(0, hit_counter.value());
-        thread::sleep(Duration::from_millis(500));
-        assert_eq!(1, hit_counter.value());
-        thread::sleep(Duration::from_secs(6));
-        assert_eq!(2, hit_counter.value());
-        thread::sleep(Duration::from_secs(11));
-        assert_eq!(3, hit_counter.value());
-
-        drop(wp);
-        thread::sleep(Duration::from_secs(4));
-        assert_eq!(3, hit_counter.value());
-    }
-}
diff --git a/keystore2/watchdog/src/tests.rs b/keystore2/watchdog/src/tests.rs
new file mode 100644
index 0000000..d35c0dd
--- /dev/null
+++ b/keystore2/watchdog/src/tests.rs
@@ -0,0 +1,86 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Watchdog tests.
+
+use super::*;
+use std::sync::atomic;
+use std::thread;
+use std::time::Duration;
+
+/// Count the number of times `Debug::fmt` is invoked.
+#[derive(Default, Clone)]
+struct DebugCounter(Arc<atomic::AtomicU8>);
+impl DebugCounter {
+    fn value(&self) -> u8 {
+        self.0.load(atomic::Ordering::Relaxed)
+    }
+}
+impl std::fmt::Debug for DebugCounter {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        let count = self.0.fetch_add(1, atomic::Ordering::Relaxed);
+        write!(f, "hit_count: {count}")
+    }
+}
+
+#[test]
+fn test_watchdog() {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("keystore2_watchdog_tests")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+
+    let wd = Watchdog::new(Duration::from_secs(3));
+    let hit_counter = DebugCounter::default();
+    let wp =
+        Watchdog::watch_with(&wd, "test_watchdog", Duration::from_millis(100), hit_counter.clone());
+    assert_eq!(0, hit_counter.value());
+    thread::sleep(Duration::from_millis(500));
+    assert_eq!(1, hit_counter.value());
+    thread::sleep(Duration::from_secs(1));
+    assert_eq!(1, hit_counter.value());
+
+    drop(wp);
+    thread::sleep(Duration::from_secs(10));
+    assert_eq!(1, hit_counter.value());
+    let (_, ref state) = *wd.state;
+    let state = state.lock().unwrap();
+    assert_eq!(state.state, State::NotRunning);
+}
+
+#[test]
+fn test_watchdog_backoff() {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("keystore2_watchdog_tests")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+
+    let wd = Watchdog::new(Duration::from_secs(3));
+    let hit_counter = DebugCounter::default();
+    let wp =
+        Watchdog::watch_with(&wd, "test_watchdog", Duration::from_millis(100), hit_counter.clone());
+    assert_eq!(0, hit_counter.value());
+    thread::sleep(Duration::from_millis(500));
+    assert_eq!(1, hit_counter.value());
+    thread::sleep(Duration::from_secs(6));
+    assert_eq!(2, hit_counter.value());
+    thread::sleep(Duration::from_secs(11));
+    assert_eq!(3, hit_counter.value());
+
+    drop(wp);
+    thread::sleep(Duration::from_secs(4));
+    assert_eq!(3, hit_counter.value());
+}
diff --git a/prng_seeder/Android.bp b/prng_seeder/Android.bp
index 4f9b7e1..b56a405 100644
--- a/prng_seeder/Android.bp
+++ b/prng_seeder/Android.bp
@@ -19,19 +19,6 @@
     default_applicable_licenses: ["system_security_license"],
 }
 
-rust_bindgen {
-    name: "libcutils_socket_bindgen",
-    crate_name: "cutils_socket_bindgen",
-    wrapper_src: "cutils_wrapper.h",
-    source_stem: "bindings",
-    bindgen_flags: [
-        "--allowlist-function=android_get_control_socket",
-    ],
-    shared_libs: [
-        "libcutils",
-    ],
-}
-
 rust_defaults {
     name: "prng_seeder_defaults",
     edition: "2021",
@@ -39,10 +26,10 @@
         "libanyhow",
         "libbssl_sys",
         "libclap",
-        "libcutils_socket_bindgen",
         "liblogger",
         "liblog_rust",
         "libnix",
+        "librustutils",
         "libtokio",
     ],
 
@@ -73,10 +60,10 @@
         "libanyhow",
         "libbssl_sys",
         "libclap",
-        "libcutils_socket_bindgen",
         "liblogger",
         "liblog_rust",
         "libnix",
+        "librustutils",
         "libtokio",
     ],
     test_suites: ["general-tests"],
diff --git a/prng_seeder/cutils_wrapper.h b/prng_seeder/cutils_wrapper.h
deleted file mode 100644
index 9c1fe56..0000000
--- a/prng_seeder/cutils_wrapper.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (C) 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cutils/sockets.h>
diff --git a/prng_seeder/src/cutils_socket.rs b/prng_seeder/src/cutils_socket.rs
deleted file mode 100644
index b408be6..0000000
--- a/prng_seeder/src/cutils_socket.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2022 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use std::ffi::CString;
-use std::os::unix::{net::UnixListener, prelude::FromRawFd};
-
-use anyhow::{ensure, Result};
-
-pub fn android_get_control_socket(name: &str) -> Result<UnixListener> {
-    let name = CString::new(name)?;
-    // SAFETY: name is a valid C string, and android_get_control_socket doesn't retain it after it
-    // returns.
-    let fd = unsafe { cutils_socket_bindgen::android_get_control_socket(name.as_ptr()) };
-    ensure!(fd >= 0, "android_get_control_socket failed");
-    // SAFETY: android_get_control_socket either returns a valid and open FD or -1, and we checked
-    // that it's not -1.
-    Ok(unsafe { UnixListener::from_raw_fd(fd) })
-}
diff --git a/prng_seeder/src/main.rs b/prng_seeder/src/main.rs
index cb7f38d..d112d61 100644
--- a/prng_seeder/src/main.rs
+++ b/prng_seeder/src/main.rs
@@ -18,7 +18,6 @@
 //! by init.
 
 mod conditioner;
-mod cutils_socket;
 mod drbg;
 
 use std::{
@@ -70,6 +69,9 @@
 }
 
 fn setup() -> Result<(ConditionerBuilder, UnixListener)> {
+    // SAFETY: nobody has taken ownership of the inherited FDs yet.
+    unsafe { rustutils::inherited_fd::init_once() }
+        .context("In setup, failed to own inherited FDs")?;
     configure_logging()?;
     let cli = Cli::try_parse()?;
     // SAFETY: Nothing else sets the signal handler, so either it was set here or it is the default.
@@ -78,8 +80,9 @@
 
     let listener = match cli.socket {
         Some(path) => get_socket(path.as_path())?,
-        None => cutils_socket::android_get_control_socket("prng_seeder")
-            .context("In setup, calling android_get_control_socket")?,
+        None => rustutils::sockets::android_get_control_socket("prng_seeder")
+            .context("In setup, calling android_get_control_socket")?
+            .into(),
     };
     let hwrng = std::fs::File::open(&cli.source)
         .with_context(|| format!("Unable to open hwrng {}", cli.source.display()))?;
diff --git a/provisioner/Android.bp b/provisioner/Android.bp
index ede1ae6..6a4dc24 100644
--- a/provisioner/Android.bp
+++ b/provisioner/Android.bp
@@ -36,6 +36,7 @@
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
+        "android.hardware.drm.common-V1-ndk",
         "android.hardware.drm-V1-ndk",
         "android.hardware.security.rkp-V3-ndk",
         "libbase",
diff --git a/provisioner/rkp_factory_extraction_lib.cpp b/provisioner/rkp_factory_extraction_lib.cpp
index ec70d08..9b04626 100644
--- a/provisioner/rkp_factory_extraction_lib.cpp
+++ b/provisioner/rkp_factory_extraction_lib.cpp
@@ -25,7 +25,6 @@
 #include <cstring>
 #include <iterator>
 #include <keymaster/cppcose/cppcose.h>
-#include <openssl/base64.h>
 #include <remote_prov/remote_prov_utils.h>
 #include <sys/random.h>
 
@@ -33,6 +32,7 @@
 #include <optional>
 #include <string>
 #include <string_view>
+#include <unordered_set>
 #include <vector>
 
 #include "cppbor_parse.h"
@@ -42,6 +42,7 @@
 using aidl::android::hardware::security::keymint::MacedPublicKey;
 using aidl::android::hardware::security::keymint::ProtectedData;
 using aidl::android::hardware::security::keymint::RpcHardwareInfo;
+using aidl::android::hardware::security::keymint::remote_prov::BccEntryData;
 using aidl::android::hardware::security::keymint::remote_prov::EekChain;
 using aidl::android::hardware::security::keymint::remote_prov::generateEekChain;
 using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain;
@@ -50,35 +51,13 @@
 using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryCsr;
 using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryProtectedData;
 
-using namespace cppbor;
-using namespace cppcose;
+using cppbor::Array;
+using cppbor::Map;
+using cppbor::Null;
+template <class T> using ErrMsgOr = cppcose::ErrMsgOr<T>;
 
 constexpr size_t kVersionWithoutSuperencryption = 3;
 
-std::string toBase64(const std::vector<uint8_t>& buffer) {
-    size_t base64Length;
-    int rc = EVP_EncodedLength(&base64Length, buffer.size());
-    if (!rc) {
-        std::cerr << "Error getting base64 length. Size overflow?" << std::endl;
-        exit(-1);
-    }
-
-    std::string base64(base64Length, ' ');
-    rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size());
-    ++rc;  // Account for NUL, which BoringSSL does not for some reason.
-    if (rc != base64Length) {
-        std::cerr << "Error writing base64. Expected " << base64Length
-                  << " bytes to be written, but " << rc << " bytes were actually written."
-                  << std::endl;
-        exit(-1);
-    }
-
-    // BoringSSL automatically adds a NUL -- remove it from the string data
-    base64.pop_back();
-
-    return base64;
-}
-
 std::vector<uint8_t> generateChallenge() {
     std::vector<uint8_t> challenge(kChallengeSize);
 
@@ -90,7 +69,8 @@
             if (errno == EINTR) {
                 continue;
             } else {
-                std::cerr << errno << ": " << strerror(errno) << std::endl;
+                std::cerr << "generateChallenge: getrandom returned an error with errno " << errno
+                          << ": " << strerror(errno) << ". Exiting..." << std::endl;
                 exit(-1);
             }
         }
@@ -105,7 +85,7 @@
                                               const DeviceInfo& verifiedDeviceInfo,
                                               const std::vector<uint8_t>& challenge,
                                               const std::vector<uint8_t>& keysToSignMac,
-                                              IRemotelyProvisionedComponent* provisionable) {
+                                              const RpcHardwareInfo& rpcHardwareInfo) {
     Array macedKeysToSign = Array()
                                 .add(Map().add(1, 5).encode())  // alg: hmac-sha256
                                 .add(Map())                     // empty unprotected headers
@@ -113,12 +93,12 @@
                                 .add(keysToSignMac);            // MAC as returned from the HAL
 
     ErrMsgOr<std::unique_ptr<Map>> parsedVerifiedDeviceInfo =
-        parseAndValidateFactoryDeviceInfo(verifiedDeviceInfo.deviceInfo, provisionable);
+        parseAndValidateFactoryDeviceInfo(verifiedDeviceInfo.deviceInfo, rpcHardwareInfo);
     if (!parsedVerifiedDeviceInfo) {
         return {nullptr, parsedVerifiedDeviceInfo.moveMessage()};
     }
 
-    auto [parsedProtectedData, ignore2, errMsg] = parse(protectedData.protectedData);
+    auto [parsedProtectedData, ignore2, errMsg] = cppbor::parse(protectedData.protectedData);
     if (!parsedProtectedData) {
         std::cerr << "Error parsing protected data: '" << errMsg << "'" << std::endl;
         return {nullptr, errMsg};
@@ -145,7 +125,7 @@
     if (!status.isOk()) {
         std::cerr << "Failed to get hardware info for '" << componentName
                   << "'. Description: " << status.getDescription() << "." << std::endl;
-        exit(-1);
+        return {nullptr, status.getDescription()};
     }
 
     const std::vector<uint8_t> eek = getProdEekChain(hwInfo.supportedEekCurve);
@@ -156,13 +136,14 @@
     if (!status.isOk()) {
         std::cerr << "Bundle extraction failed for '" << componentName
                   << "'. Description: " << status.getDescription() << "." << std::endl;
-        exit(-1);
+        return {nullptr, status.getDescription()};
     }
     return composeCertificateRequestV1(protectedData, verifiedDeviceInfo, challenge, keysToSignMac,
-                                       irpc);
+                                       hwInfo);
 }
 
-void selfTestGetCsrV1(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+std::optional<std::string> selfTestGetCsrV1(std::string_view componentName,
+                                            IRemotelyProvisionedComponent* irpc) {
     std::vector<uint8_t> keysToSignMac;
     std::vector<MacedPublicKey> emptyKeys;
     DeviceInfo verifiedDeviceInfo;
@@ -172,14 +153,14 @@
     if (!status.isOk()) {
         std::cerr << "Failed to get hardware info for '" << componentName
                   << "'. Description: " << status.getDescription() << "." << std::endl;
-        exit(-1);
+        return status.getDescription();
     }
 
     const std::vector<uint8_t> eekId = {0, 1, 2, 3, 4, 5, 6, 7};
     ErrMsgOr<EekChain> eekChain = generateEekChain(hwInfo.supportedEekCurve, /*length=*/3, eekId);
     if (!eekChain) {
         std::cerr << "Error generating test EEK certificate chain: " << eekChain.message();
-        exit(-1);
+        return eekChain.message();
     }
     const std::vector<uint8_t> challenge = generateChallenge();
     status = irpc->generateCertificateRequest(
@@ -188,18 +169,19 @@
     if (!status.isOk()) {
         std::cerr << "Error generating test cert chain for '" << componentName
                   << "'. Description: " << status.getDescription() << "." << std::endl;
-        exit(-1);
+        return status.getDescription();
     }
 
     auto result = verifyFactoryProtectedData(verifiedDeviceInfo, /*keysToSign=*/{}, keysToSignMac,
-                                             protectedData, *eekChain, eekId,
-                                             hwInfo.supportedEekCurve, irpc, challenge);
+                                             protectedData, *eekChain, eekId, hwInfo,
+                                             std::string(componentName), challenge);
 
     if (!result) {
         std::cerr << "Self test failed for IRemotelyProvisionedComponent '" << componentName
                   << "'. Error message: '" << result.message() << "'." << std::endl;
-        exit(-1);
+        return result.message();
     }
+    return std::nullopt;
 }
 
 CborResult<Array> composeCertificateRequestV3(const std::vector<uint8_t>& csr) {
@@ -223,25 +205,35 @@
     return {std::unique_ptr<Array>(parsedCsr.release()->asArray()), ""};
 }
 
-CborResult<cppbor::Array> getCsrV3(std::string_view componentName,
-                                   IRemotelyProvisionedComponent* irpc, bool selfTest) {
+CborResult<Array> getCsrV3(std::string_view componentName, IRemotelyProvisionedComponent* irpc,
+                           bool selfTest, bool allowDegenerate, bool requireUdsCerts) {
     std::vector<uint8_t> csr;
     std::vector<MacedPublicKey> emptyKeys;
     const std::vector<uint8_t> challenge = generateChallenge();
 
-    auto status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr);
+    RpcHardwareInfo hwInfo;
+    auto status = irpc->getHardwareInfo(&hwInfo);
+    if (!status.isOk()) {
+        std::cerr << "Failed to get hardware info for '" << componentName
+                  << "'. Description: " << status.getDescription() << "." << std::endl;
+        return {nullptr, status.getDescription()};
+    }
+
+    status = irpc->generateCertificateRequestV2(emptyKeys, challenge, &csr);
     if (!status.isOk()) {
         std::cerr << "Bundle extraction failed for '" << componentName
                   << "'. Description: " << status.getDescription() << "." << std::endl;
-        exit(-1);
+        return {nullptr, status.getDescription()};
     }
 
     if (selfTest) {
-        auto result = verifyFactoryCsr(/*keysToSign=*/cppbor::Array(), csr, irpc, challenge);
+        auto result = verifyFactoryCsr(/*keysToSign=*/cppbor::Array(), csr, hwInfo,
+                                       std::string(componentName), challenge, allowDegenerate,
+                                       requireUdsCerts);
         if (!result) {
             std::cerr << "Self test failed for IRemotelyProvisionedComponent '" << componentName
                       << "'. Error message: '" << result.message() << "'." << std::endl;
-            exit(-1);
+            return {nullptr, result.message()};
         }
     }
 
@@ -249,35 +241,37 @@
 }
 
 CborResult<Array> getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc,
-                         bool selfTest) {
+                         bool selfTest, bool allowDegenerate, bool requireUdsCerts) {
     RpcHardwareInfo hwInfo;
     auto status = irpc->getHardwareInfo(&hwInfo);
     if (!status.isOk()) {
         std::cerr << "Failed to get hardware info for '" << componentName
                   << "'. Description: " << status.getDescription() << "." << std::endl;
-        exit(-1);
+        return {nullptr, status.getDescription()};
     }
 
     if (hwInfo.versionNumber < kVersionWithoutSuperencryption) {
         if (selfTest) {
-            selfTestGetCsrV1(componentName, irpc);
+            auto errMsg = selfTestGetCsrV1(componentName, irpc);
+            if (errMsg) {
+                return {nullptr, *errMsg};
+            }
         }
         return getCsrV1(componentName, irpc);
     } else {
-        return getCsrV3(componentName, irpc, selfTest);
+        return getCsrV3(componentName, irpc, selfTest, allowDegenerate, requireUdsCerts);
     }
 }
 
-bool isRemoteProvisioningSupported(IRemotelyProvisionedComponent* irpc) {
-    RpcHardwareInfo hwInfo;
-    auto status = irpc->getHardwareInfo(&hwInfo);
-    if (status.isOk()) {
-        return true;
+std::unordered_set<std::string> parseCommaDelimited(const std::string& input) {
+    std::stringstream ss(input);
+    std::unordered_set<std::string> result;
+    while (ss.good()) {
+        std::string name;
+        std::getline(ss, name, ',');
+        if (!name.empty()) {
+            result.insert(name);
+        }
     }
-    if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
-        return false;
-    }
-    std::cerr << "Unexpected error when getting hardware info. Description: "
-              << status.getDescription() << "." << std::endl;
-    exit(-1);
-}
+    return result;
+}
\ No newline at end of file
diff --git a/provisioner/rkp_factory_extraction_lib.h b/provisioner/rkp_factory_extraction_lib.h
index 93c498a..3515f48 100644
--- a/provisioner/rkp_factory_extraction_lib.h
+++ b/provisioner/rkp_factory_extraction_lib.h
@@ -23,8 +23,13 @@
 #include <memory>
 #include <string>
 #include <string_view>
+#include <unordered_set>
 #include <vector>
 
+// Parse a comma-delimited string.
+// Ignores any empty strings.
+std::unordered_set<std::string> parseCommaDelimited(const std::string& input);
+
 // Challenge size must be between 32 and 64 bytes inclusive.
 constexpr size_t kChallengeSize = 64;
 
@@ -35,9 +40,6 @@
     std::string errMsg;
 };
 
-// Return `buffer` encoded as a base64 string.
-std::string toBase64(const std::vector<uint8_t>& buffer);
-
 // Generate a random challenge containing `kChallengeSize` bytes.
 std::vector<uint8_t> generateChallenge();
 
@@ -47,13 +49,4 @@
 CborResult<cppbor::Array>
 getCsr(std::string_view componentName,
        aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc,
-       bool selfTest);
-
-// Generates a test certificate chain and validates it, exiting the process on error.
-void selfTestGetCsr(
-    std::string_view componentName,
-    aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
-
-// Returns true if the given IRemotelyProvisionedComponent supports remote provisioning.
-bool isRemoteProvisioningSupported(
-    aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
+       bool selfTest, bool allowDegenerate, bool requireUdsCerts);
\ No newline at end of file
diff --git a/provisioner/rkp_factory_extraction_lib_test.cpp b/provisioner/rkp_factory_extraction_lib_test.cpp
index 3fe88da..e21ef93 100644
--- a/provisioner/rkp_factory_extraction_lib_test.cpp
+++ b/provisioner/rkp_factory_extraction_lib_test.cpp
@@ -25,6 +25,7 @@
 #include <android-base/properties.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <openssl/base64.h>
 
 #include <cstdint>
 #include <memory>
@@ -60,6 +61,101 @@
 
 }  // namespace cppbor
 
+inline const std::vector<uint8_t> kCsrWithoutUdsCerts{
+    0x85, 0x01, 0xa0, 0x82, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xb8, 0x36,
+    0xbb, 0x1e, 0x07, 0x85, 0x02, 0xde, 0xdb, 0x91, 0x38, 0x5d, 0xc7, 0xf8, 0x59, 0xa9, 0x4f, 0x50,
+    0xee, 0x2a, 0x3f, 0xa5, 0x5f, 0xaa, 0xa1, 0x8e, 0x46, 0x84, 0xb8, 0x3b, 0x4b, 0x6d, 0x22, 0x58,
+    0x20, 0xa1, 0xc1, 0xd8, 0xa5, 0x9d, 0x1b, 0xce, 0x8c, 0x65, 0x10, 0x8d, 0xcf, 0xa1, 0xf4, 0x91,
+    0x10, 0x09, 0xfb, 0xb0, 0xc5, 0xb4, 0x01, 0x75, 0x72, 0xb4, 0x44, 0xaa, 0x23, 0x13, 0xe1, 0xe9,
+    0xe5, 0x84, 0x43, 0xa1, 0x01, 0x26, 0xa0, 0x59, 0x01, 0x04, 0xa9, 0x01, 0x66, 0x69, 0x73, 0x73,
+    0x75, 0x65, 0x72, 0x02, 0x67, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x00, 0x47, 0x44,
+    0x50, 0x58, 0x20, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+    0x55, 0x55, 0x55, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x20, 0xb8, 0x96, 0x54, 0xe2, 0x2c, 0xa4,
+    0xd2, 0x4a, 0x9c, 0x0e, 0x45, 0x11, 0xc8, 0xf2, 0x63, 0xf0, 0x66, 0x0d, 0x2e, 0x20, 0x48, 0x96,
+    0x90, 0x14, 0xf4, 0x54, 0x63, 0xc4, 0xf4, 0x39, 0x30, 0x38, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x55,
+    0xa1, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74,
+    0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x20, 0x55, 0x55, 0x55, 0x55,
+    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x3a, 0x00, 0x47, 0x44,
+    0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x4d, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20,
+    0x01, 0x21, 0x58, 0x20, 0x91, 0xdc, 0x49, 0x60, 0x0d, 0x22, 0xf6, 0x28, 0x14, 0xaf, 0xab, 0xa5,
+    0x9d, 0x4f, 0x26, 0xac, 0xf9, 0x99, 0xe7, 0xe1, 0xc9, 0xb7, 0x5d, 0x36, 0x21, 0x9d, 0x00, 0x47,
+    0x63, 0x28, 0x79, 0xa7, 0x22, 0x58, 0x20, 0x13, 0x77, 0x51, 0x7f, 0x6a, 0xca, 0xa0, 0x50, 0x79,
+    0x52, 0xb4, 0x6b, 0xd9, 0xb1, 0x3a, 0x1c, 0x9f, 0x91, 0x97, 0x60, 0xc1, 0x4b, 0x43, 0x5e, 0x45,
+    0xd3, 0x0b, 0xa4, 0xbb, 0xc7, 0x27, 0x39, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40,
+    0x88, 0xbd, 0xf9, 0x82, 0x04, 0xfe, 0xa6, 0xfe, 0x82, 0x94, 0xa3, 0xe9, 0x10, 0x91, 0xb5, 0x2e,
+    0xa1, 0x62, 0x68, 0xa5, 0x3d, 0xab, 0xdb, 0xa5, 0x87, 0x2a, 0x97, 0x26, 0xb8, 0xd4, 0x60, 0x1a,
+    0xf1, 0x3a, 0x45, 0x72, 0x77, 0xd4, 0xeb, 0x2b, 0xa4, 0x48, 0x93, 0xba, 0xae, 0x79, 0x35, 0x57,
+    0x66, 0x54, 0x9d, 0x8e, 0xbd, 0xb0, 0x87, 0x5f, 0x8c, 0xf9, 0x04, 0xa3, 0xa7, 0x00, 0xf1, 0x21,
+    0x84, 0x43, 0xa1, 0x01, 0x26, 0xa0, 0x59, 0x02, 0x0f, 0x82, 0x58, 0x20, 0x01, 0x02, 0x03, 0x04,
+    0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
+    0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x59, 0x01, 0xe9, 0x84,
+    0x03, 0x67, 0x6b, 0x65, 0x79, 0x6d, 0x69, 0x6e, 0x74, 0xae, 0x65, 0x62, 0x72, 0x61, 0x6e, 0x64,
+    0x66, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x01, 0x65, 0x6d,
+    0x6f, 0x64, 0x65, 0x6c, 0x65, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x66, 0x64, 0x65, 0x76, 0x69, 0x63,
+    0x65, 0x66, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x67, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,
+    0x65, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x68, 0x76, 0x62, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x65,
+    0x67, 0x72, 0x65, 0x65, 0x6e, 0x6a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+    0x62, 0x31, 0x32, 0x6c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72,
+    0x66, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x6d, 0x76, 0x62, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x64,
+    0x69, 0x67, 0x65, 0x73, 0x74, 0x4f, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa,
+    0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x6c,
+    0x65, 0x76, 0x65, 0x6c, 0x63, 0x74, 0x65, 0x65, 0x70, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x70, 0x61,
+    0x74, 0x63, 0x68, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x1a, 0x01, 0x34, 0x8c, 0x62, 0x70, 0x62,
+    0x6f, 0x6f, 0x74, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x66,
+    0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x72, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x70, 0x61,
+    0x74, 0x63, 0x68, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x1a, 0x01, 0x34, 0x8c, 0x61, 0x72, 0x76,
+    0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6c, 0x65, 0x76, 0x65,
+    0x6c, 0x1a, 0x01, 0x34, 0x8c, 0x63, 0x82, 0xa6, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58,
+    0x20, 0x85, 0xcd, 0xd8, 0x8c, 0x35, 0x50, 0x11, 0x9c, 0x44, 0x24, 0xa7, 0xf1, 0xbf, 0x75, 0x6e,
+    0x7c, 0xab, 0x8c, 0x86, 0xfa, 0x23, 0x95, 0x2c, 0x11, 0xaf, 0xf9, 0x52, 0x80, 0x8f, 0x45, 0x43,
+    0x40, 0x22, 0x58, 0x20, 0xec, 0x4e, 0x0d, 0x5a, 0x81, 0xe8, 0x06, 0x12, 0x18, 0xa8, 0x10, 0x74,
+    0x6e, 0x56, 0x33, 0x11, 0x7d, 0x74, 0xff, 0x49, 0xf7, 0x38, 0x32, 0xda, 0xf4, 0x60, 0xaa, 0x19,
+    0x64, 0x29, 0x58, 0xbe, 0x23, 0x58, 0x21, 0x00, 0xa6, 0xd1, 0x85, 0xdb, 0x8b, 0x15, 0x84, 0xde,
+    0x34, 0xf2, 0xe3, 0xee, 0x73, 0x8b, 0x85, 0x57, 0xc1, 0xa3, 0x5d, 0x3f, 0x95, 0x14, 0xd3, 0x74,
+    0xfc, 0x73, 0x51, 0x7f, 0xe7, 0x1b, 0x30, 0xbb, 0xa6, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21,
+    0x58, 0x20, 0x96, 0x6c, 0x16, 0x6c, 0x4c, 0xa7, 0x73, 0x64, 0x9a, 0x34, 0x88, 0x75, 0xf4, 0xdc,
+    0xf3, 0x93, 0xb2, 0xf1, 0xd7, 0xfd, 0xe3, 0x11, 0xcf, 0x6b, 0xee, 0x26, 0xa4, 0xc5, 0xeb, 0xa5,
+    0x33, 0x24, 0x22, 0x58, 0x20, 0xe0, 0x33, 0xe8, 0x53, 0xb2, 0x65, 0x1e, 0x33, 0x2a, 0x61, 0x9a,
+    0x7a, 0xf4, 0x5f, 0x40, 0x0f, 0x80, 0x4a, 0x38, 0xff, 0x5d, 0x3c, 0xa3, 0x82, 0x36, 0x1e, 0x9d,
+    0x93, 0xd9, 0x48, 0xaa, 0x0a, 0x23, 0x58, 0x20, 0x5e, 0xe5, 0x8f, 0x9a, 0x8c, 0xd3, 0xf4, 0xc0,
+    0xf7, 0x08, 0x27, 0x5f, 0x8f, 0x77, 0x12, 0x36, 0x7b, 0x6d, 0xf7, 0x65, 0xd4, 0xcc, 0x63, 0xdc,
+    0x28, 0x35, 0x33, 0x27, 0x5d, 0x28, 0xc9, 0x9d, 0x58, 0x40, 0x6c, 0xfa, 0xc9, 0xc0, 0xdf, 0x0e,
+    0xe4, 0x17, 0x58, 0x06, 0xea, 0xf9, 0x88, 0x9e, 0x27, 0xa0, 0x89, 0x17, 0xa8, 0x1a, 0xe6, 0x0c,
+    0x5e, 0x85, 0xa1, 0x13, 0x20, 0x86, 0x14, 0x2e, 0xd6, 0xae, 0xfb, 0xc1, 0xb6, 0x59, 0x66, 0x83,
+    0xd2, 0xf4, 0xc8, 0x7a, 0x30, 0x0c, 0x6b, 0x53, 0x8b, 0x76, 0x06, 0xcb, 0x1b, 0x0f, 0xc3, 0x51,
+    0x71, 0x52, 0xd1, 0xe3, 0x2a, 0xbc, 0x53, 0x16, 0x46, 0x49, 0xa1, 0x6b, 0x66, 0x69, 0x6e, 0x67,
+    0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x78, 0x3b, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x31, 0x2f,
+    0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x31, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x31,
+    0x3a, 0x31, 0x31, 0x2f, 0x69, 0x64, 0x2f, 0x32, 0x30, 0x32, 0x31, 0x30, 0x38, 0x30, 0x35, 0x2e,
+    0x34, 0x32, 0x3a, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2d,
+    0x6b, 0x65, 0x79, 0x73};
+
+std::string toBase64(const std::vector<uint8_t>& buffer) {
+    size_t base64Length;
+    int rc = EVP_EncodedLength(&base64Length, buffer.size());
+    if (!rc) {
+        std::cerr << "Error getting base64 length. Size overflow?" << std::endl;
+        exit(-1);
+    }
+
+    std::string base64(base64Length, ' ');
+    rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size());
+    ++rc;  // Account for NUL, which BoringSSL does not for some reason.
+    if (rc != base64Length) {
+        std::cerr << "Error writing base64. Expected " << base64Length
+                  << " bytes to be written, but " << rc << " bytes were actually written."
+                  << std::endl;
+        exit(-1);
+    }
+
+    // BoringSSL automatically adds a NUL -- remove it from the string data
+    base64.pop_back();
+
+    return base64;
+}
+
 class MockIRemotelyProvisionedComponent : public IRemotelyProvisionedComponentDefault {
   public:
     MOCK_METHOD(ScopedAStatus, getHardwareInfo, (RpcHardwareInfo * _aidl_return), (override));
@@ -77,7 +173,7 @@
                 (const std::vector<MacedPublicKey>& in_keysToSign,
                  const std::vector<uint8_t>& in_challenge, std::vector<uint8_t>* _aidl_return),
                 (override));
-    MOCK_METHOD(ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override));
+    MOCK_METHOD(ScopedAStatus, getInterfaceVersion, (int32_t* _aidl_return), (override));
     MOCK_METHOD(ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override));
 };
 
@@ -87,7 +183,7 @@
         input[i] = i;
     }
 
-    // Test three lengths so we get all the different paddding options
+    // Test three lengths so we get all the different padding options
     EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4"
               "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV"
               "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj"
@@ -159,7 +255,7 @@
     std::vector<uint8_t> eekChain;
     std::vector<uint8_t> challenge;
 
-    // Set up mock, then call getSCsr
+    // Set up mock, then call getCsr
     auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
     EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
         hwInfo->versionNumber = 2;
@@ -180,8 +276,9 @@
                         SetArgPointee<6>(kFakeMac),             //
                         Return(ByMove(ScopedAStatus::ok()))));  //
 
-    auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get(),
-                                   /*selfTest=*/false);
+    auto [csr, csrErrMsg] =
+        getCsr("mock component name", mockRpc.get(),
+               /*selfTest=*/false, /*allowDegenerate=*/true, /*requireUdsCerts=*/false);
     ASSERT_THAT(csr, NotNull()) << csrErrMsg;
     ASSERT_THAT(csr->asArray(), Pointee(Property(&Array::size, Eq(4))));
 
@@ -230,7 +327,7 @@
 
 TEST(LibRkpFactoryExtractionTests, GetCsrWithV3Hal) {
     const std::vector<uint8_t> kCsr = Array()
-                                          .add(3 /* version */)
+                                          .add(1 /* version */)
                                           .add(Map() /* UdsCerts */)
                                           .add(Array() /* DiceCertChain */)
                                           .add(Array() /* SignedData */)
@@ -250,12 +347,13 @@
         .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(kCsr),
                         Return(ByMove(ScopedAStatus::ok()))));
 
-    auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get(),
-                                   /*selfTest=*/false);
+    auto [csr, csrErrMsg] =
+        getCsr("mock component name", mockRpc.get(),
+               /*selfTest=*/false, /*allowDegenerate=*/true, /*requireUdsCerts=*/false);
     ASSERT_THAT(csr, NotNull()) << csrErrMsg;
     ASSERT_THAT(csr, Pointee(Property(&Array::size, Eq(5))));
 
-    EXPECT_THAT(csr->get(0 /* version */), Pointee(Eq(Uint(3))));
+    EXPECT_THAT(csr->get(0 /* version */), Pointee(Eq(Uint(1))));
     EXPECT_THAT(csr->get(1)->asMap(), NotNull());
     EXPECT_THAT(csr->get(2)->asArray(), NotNull());
     EXPECT_THAT(csr->get(3)->asArray(), NotNull());
@@ -266,3 +364,63 @@
     const Tstr fingerprint(android::base::GetProperty("ro.build.fingerprint", ""));
     EXPECT_THAT(*unverifedDeviceInfo->get("fingerprint")->asTstr(), Eq(fingerprint));
 }
+
+TEST(LibRkpFactoryExtractionTests, requireUdsCerts) {
+    const std::vector<uint8_t> csrEncoded = kCsrWithoutUdsCerts;
+    std::vector<uint8_t> challenge;
+
+    // Set up mock, then call getCsr
+    auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
+    EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
+        hwInfo->versionNumber = 3;
+        return ScopedAStatus::ok();
+    });
+    EXPECT_CALL(*mockRpc,
+                generateCertificateRequestV2(IsEmpty(),   // keysToSign
+                                             _,           // challenge
+                                             NotNull()))  // _aidl_return
+        .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(csrEncoded),
+                        Return(ByMove(ScopedAStatus::ok()))));
+
+    auto [csr, csrErrMsg] =
+        getCsr("default", mockRpc.get(),
+               /*selfTest=*/true, /*allowDegenerate=*/false, /*requireUdsCerts=*/true);
+    ASSERT_EQ(csr, nullptr);
+    ASSERT_THAT(csrErrMsg, testing::HasSubstr("UdsCerts are required"));
+}
+
+TEST(LibRkpFactoryExtractionTests, dontRequireUdsCerts) {
+    const std::vector<uint8_t> csrEncoded = kCsrWithoutUdsCerts;
+    std::vector<uint8_t> challenge;
+
+    // Set up mock, then call getCsr
+    auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
+    EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
+        hwInfo->versionNumber = 3;
+        return ScopedAStatus::ok();
+    });
+    EXPECT_CALL(*mockRpc,
+                generateCertificateRequestV2(IsEmpty(),   // keysToSign
+                                             _,           // challenge
+                                             NotNull()))  // _aidl_return
+        .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(csrEncoded),
+                        Return(ByMove(ScopedAStatus::ok()))));
+
+    auto [csr, csrErrMsg] =
+        getCsr("default", mockRpc.get(),
+               /*selfTest=*/true, /*allowDegenerate=*/false, /*requireUdsCerts=*/false);
+    ASSERT_EQ(csr, nullptr);
+    ASSERT_THAT(csrErrMsg, testing::HasSubstr("challenges do not match"));
+}
+
+TEST(LibRkpFactoryExtractionTests, parseCommaDelimitedString) {
+    const auto& rpcNames = "default,avf,,default,Strongbox,strongbox,,";
+    const auto& rpcSet = parseCommaDelimited(rpcNames);
+
+    ASSERT_EQ(rpcSet.size(), 4);
+    ASSERT_TRUE(rpcSet.count("") == 0);
+    ASSERT_TRUE(rpcSet.count("default") == 1);
+    ASSERT_TRUE(rpcSet.count("avf") == 1);
+    ASSERT_TRUE(rpcSet.count("strongbox") == 1);
+    ASSERT_TRUE(rpcSet.count("Strongbox") == 1);
+}
diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp
index 1cb1144..599b52a 100644
--- a/provisioner/rkp_factory_extraction_tool.cpp
+++ b/provisioner/rkp_factory_extraction_tool.cpp
@@ -26,6 +26,7 @@
 
 #include <future>
 #include <string>
+#include <unordered_set>
 #include <vector>
 
 #include "DrmRkpAdapter.h"
@@ -33,18 +34,23 @@
 
 using aidl::android::hardware::drm::IDrmFactory;
 using aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent;
+using aidl::android::hardware::security::keymint::RpcHardwareInfo;
 using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild;
-
-using namespace cppbor;
-using namespace cppcose;
+using aidl::android::hardware::security::keymint::remote_prov::RKPVM_INSTANCE_NAME;
 
 DEFINE_string(output_format, "build+csr", "How to format the output. Defaults to 'build+csr'.");
 DEFINE_bool(self_test, true,
             "If true, this tool performs a self-test, validating the payload for correctness. "
             "This checks that the device on the factory line is producing valid output "
             "before attempting to upload the output to the device info service.");
+DEFINE_bool(allow_degenerate, true,
+            "If true, self_test validation will allow degenerate DICE chains in the CSR.");
 DEFINE_string(serialno_prop, "ro.serialno",
               "The property of getting serial number. Defaults to 'ro.serialno'.");
+DEFINE_string(require_uds_certs, "",
+              "The comma-delimited names of remotely provisioned "
+              "components whose UDS certificate chains are required to be present in the CSR. "
+              "Example: avf,default,strongbox");
 
 namespace {
 
@@ -57,15 +63,15 @@
     return  std::string(descriptor) + "/" + name;
 }
 
-void writeOutput(const std::string instance_name, const Array& csr) {
+void writeOutput(const std::string instance_name, const cppbor::Array& csr) {
     if (FLAGS_output_format == kBinaryCsrOutput) {
         auto bytes = csr.encode();
         std::copy(bytes.begin(), bytes.end(), std::ostream_iterator<char>(std::cout));
     } else if (FLAGS_output_format == kBuildPlusCsr) {
         auto [json, error] = jsonEncodeCsrWithBuild(instance_name, csr, FLAGS_serialno_prop);
         if (!error.empty()) {
-            std::cerr << "Error JSON encoding the output: " << error;
-            exit(1);
+            std::cerr << "Error JSON encoding the output: " << error << std::endl;
+            exit(-1);
         }
         std::cout << json << std::endl;
     } else {
@@ -73,20 +79,28 @@
         std::cerr << "Valid formats:" << std::endl;
         std::cerr << "  " << kBinaryCsrOutput << std::endl;
         std::cerr << "  " << kBuildPlusCsr << std::endl;
-        exit(1);
+        exit(-1);
     }
 }
 
-void getCsrForIRpc(const char* descriptor, const char* name, IRemotelyProvisionedComponent* irpc) {
+void getCsrForIRpc(const char* descriptor, const char* name, IRemotelyProvisionedComponent* irpc,
+                   bool requireUdsCerts) {
+    auto fullName = getFullServiceName(descriptor, name);
     // AVF RKP HAL is not always supported, so we need to check if it is supported before
     // generating the CSR.
-    if (std::string(name) == "avf" && !isRemoteProvisioningSupported(irpc)) {
-        return;
+    if (fullName == RKPVM_INSTANCE_NAME) {
+        RpcHardwareInfo hwInfo;
+        auto status = irpc->getHardwareInfo(&hwInfo);
+        if (!status.isOk()) {
+            return;
+        }
     }
-    auto [request, errMsg] = getCsr(name, irpc, FLAGS_self_test);
-    auto fullName = getFullServiceName(descriptor, name);
+
+    auto [request, errMsg] =
+        getCsr(name, irpc, FLAGS_self_test, FLAGS_allow_degenerate, requireUdsCerts);
     if (!request) {
-        std::cerr << "Unable to build CSR for '" << fullName << ": " << errMsg << std::endl;
+        std::cerr << "Unable to build CSR for '" << fullName << "': " << errMsg << ", exiting."
+                  << std::endl;
         exit(-1);
     }
 
@@ -95,23 +109,33 @@
 
 // Callback for AServiceManager_forEachDeclaredInstance that writes out a CSR
 // for every IRemotelyProvisionedComponent.
-void getCsrForInstance(const char* name, void* /*context*/) {
+void getCsrForInstance(const char* name, void* context) {
     auto fullName = getFullServiceName(IRemotelyProvisionedComponent::descriptor, name);
-    std::future<AIBinder*> wait_for_service_func =
+    std::future<AIBinder*> waitForServiceFunc =
         std::async(std::launch::async, AServiceManager_waitForService, fullName.c_str());
-    if (wait_for_service_func.wait_for(std::chrono::seconds(10)) == std::future_status::timeout) {
-        std::cerr << "Wait for service timed out after 10 seconds: " << fullName;
+    if (waitForServiceFunc.wait_for(std::chrono::seconds(10)) == std::future_status::timeout) {
+        std::cerr << "Wait for service timed out after 10 seconds: '" << fullName << "', exiting."
+                  << std::endl;
         exit(-1);
     }
-    AIBinder* rkpAiBinder = wait_for_service_func.get();
+    AIBinder* rkpAiBinder = waitForServiceFunc.get();
     ::ndk::SpAIBinder rkp_binder(rkpAiBinder);
-    auto rkp_service = IRemotelyProvisionedComponent::fromBinder(rkp_binder);
-    if (!rkp_service) {
-        std::cerr << "Unable to get binder object for '" << fullName << "', skipping.";
+    auto rkpService = IRemotelyProvisionedComponent::fromBinder(rkp_binder);
+    if (!rkpService) {
+        std::cerr << "Unable to get binder object for '" << fullName << "', exiting." << std::endl;
         exit(-1);
     }
 
-    getCsrForIRpc(IRemotelyProvisionedComponent::descriptor, name, rkp_service.get());
+    if (context == nullptr) {
+        std::cerr << "Unable to get context for '" << fullName << "', exiting." << std::endl;
+        exit(-1);
+    }
+
+    auto requireUdsCertsRpcNames = static_cast<std::unordered_set<std::string>*>(context);
+    auto requireUdsCerts = requireUdsCertsRpcNames->count(name) != 0;
+    requireUdsCertsRpcNames->erase(name);
+    getCsrForIRpc(IRemotelyProvisionedComponent::descriptor, name, rkpService.get(),
+                  requireUdsCerts);
 }
 
 }  // namespace
@@ -119,12 +143,21 @@
 int main(int argc, char** argv) {
     gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags=*/true);
 
-    AServiceManager_forEachDeclaredInstance(IRemotelyProvisionedComponent::descriptor,
-                                            /*context=*/nullptr, getCsrForInstance);
+    auto requireUdsCertsRpcNames = parseCommaDelimited(FLAGS_require_uds_certs);
 
-    // Append drm csr's
-    for (auto const& e : android::mediadrm::getDrmRemotelyProvisionedComponents()) {
-        getCsrForIRpc(IDrmFactory::descriptor, e.first.c_str(), e.second.get());
+    AServiceManager_forEachDeclaredInstance(IRemotelyProvisionedComponent::descriptor,
+                                            &requireUdsCertsRpcNames, getCsrForInstance);
+
+    // Append drm CSRs
+    for (auto const& [name, irpc] : android::mediadrm::getDrmRemotelyProvisionedComponents()) {
+        auto requireUdsCerts = requireUdsCertsRpcNames.count(name) != 0;
+        requireUdsCertsRpcNames.erase(name);
+        getCsrForIRpc(IDrmFactory::descriptor, name.c_str(), irpc.get(), requireUdsCerts);
+    }
+
+    for (auto const& rpcName : requireUdsCertsRpcNames) {
+        std::cerr << "WARNING: You requested to enforce the presence of UDS Certs for '" << rpcName
+                  << "', but no Remotely Provisioned Component had that name." << std::endl;
     }
 
     return 0;