Merge "Handling `UNIMPLEMENTED` error code from deleteKey API in Keystore VTS Tests." into main
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index be2f812..ef5111f 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -50,6 +50,8 @@
         "libandroid_security_flags_rust",
         "libanyhow",
         "libbinder_rs",
+        "libbssl_crypto",
+        "libder",
         "libkeystore2_aaid-rust",
         "libkeystore2_apc_compat-rust",
         "libkeystore2_crypto_rust",
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index c815622..66b123e 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -1245,7 +1245,7 @@
     /// 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(),
@@ -3049,4 +3049,11 @@
         let app_uids_vec: Vec<i64> = app_uids_affected_by_sid.into_iter().collect();
         Ok(app_uids_vec)
     }
+
+    /// 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/tests.rs b/keystore2/src/database/tests.rs
index 381a45a..4ada694 100644
--- a/keystore2/src/database/tests.rs
+++ b/keystore2/src/database/tests.rs
@@ -2832,6 +2832,7 @@
             let batch_size = crate::utils::estimate_safe_amount_to_return(
                 domain,
                 namespace,
+                None,
                 &keys,
                 crate::utils::RESPONSE_SIZE_LIMIT,
             );
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index 4c895ae..7b44ad7 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -19,7 +19,7 @@
 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, LEGACY_IMPORTER, SUPER_KEY, ENCODED_MODULE_INFO};
 use crate::ks_err;
 use crate::permission::{KeyPerm, KeystorePerm};
 use crate::super_key::SuperKeyManager;
@@ -28,7 +28,7 @@
     check_keystore_permission, uid_to_android_user, watchdog as wd,
 };
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-    ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel,
+    ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
 };
 use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::{
     BnKeystoreMaintenance, IKeystoreMaintenance,
@@ -41,12 +41,37 @@
 };
 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 bssl_crypto::digest;
+use der::{DerOrd, Encode, asn1::OctetString, asn1::SetOfVec, Sequence};
 use keystore2_crypto::Password;
+use std::cmp::Ordering;
 
 /// Reexport Domain for the benefit of DeleteListener
 pub use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
 
+/// Version number of KeyMint V4.
+pub const KEYMINT_V4: i32 = 400;
+
+/// Module information structure for DER-encoding.
+#[derive(Sequence, Debug)]
+struct ModuleInfo {
+    name: OctetString,
+    version: i32,
+}
+
+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 {
@@ -139,19 +164,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"))?;
 
+        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<()>,
     {
@@ -160,7 +201,7 @@
             (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 {}.",
@@ -197,7 +238,8 @@
         {
             log::error!("SUPER_KEY.set_up_boot_level_cache failed:\n{:?}\n:(", e);
         }
-        Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded())
+
+        Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded(), None)
     }
 
     fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
@@ -253,7 +295,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(
@@ -309,6 +351,34 @@
         }
         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)?;
@@ -320,6 +390,44 @@
 
         Ok(())
     }
+
+    #[allow(dead_code)]
+    fn set_module_info(module_info: Vec<ModuleInfo>) -> Result<()> {
+        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),
+        )
+    }
+
+    #[allow(dead_code)]
+    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 {
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 7149d12..fd1f9b5 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,
@@ -104,11 +105,13 @@
         // StorageStats is an original pulled atom (i.e. not a pushed atom converted to a
         // 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 {
@@ -120,8 +123,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
@@ -133,8 +134,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 {
diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs
index 9ae8ccf..c11c1f4 100644
--- a/keystore2/src/operation.rs
+++ b/keystore2/src/operation.rs
@@ -308,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
     }
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index c6dc11e..35290df 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -541,39 +541,40 @@
 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.
@@ -602,8 +603,13 @@
         start_past_alias,
     );
 
-    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())
 }
 
diff --git a/keystore2/src/utils/tests.rs b/keystore2/src/utils/tests.rs
index 618ea47..e514b2a 100644
--- a/keystore2/src/utils/tests.rs
+++ b/keystore2/src/utils/tests.rs
@@ -53,9 +53,9 @@
     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);
+    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(())
 }
 
diff --git a/keystore2/tests/user_auth.rs b/keystore2/tests/user_auth.rs
index 187256b..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, BarrierReachedWithData};
+use crate::keystore2_client_test_utils::{
+    BarrierReached, BarrierReachedWithData, get_vsr_api_level
+};
 use android_security_authorization::aidl::android::security::authorization::{
     IKeystoreAuthorization::IKeystoreAuthorization
 };
@@ -59,12 +61,13 @@
 ];
 /// Gatekeeper password.
 static GK_PASSWORD: &[u8] = b"correcthorsebatterystaple";
-/// Fake SID value corresponding to Gatekeeper.
-static GK_FAKE_SID: i64 = 123456;
-/// Fake SID value corresponding to a biometric authenticator.
-static BIO_FAKE_SID1: i64 = 345678;
-/// Fake SID value corresponding to a biometric authenticator.
-static BIO_FAKE_SID2: i64 = 456789;
+/// 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;
@@ -171,6 +174,8 @@
 
 #[test]
 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()
@@ -199,8 +204,8 @@
             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_secure_id(bio_fake_sid1)
+            .user_secure_id(bio_fake_sid2)
             .user_auth_type(HardwareAuthenticatorType::ANY)
             .auth_timeout(3)
             .algorithm(Algorithm::EC)
@@ -214,7 +219,7 @@
                 &KeyDescriptor {
                     domain: Domain::APP,
                     nspace: -1,
-                    alias: Some("auth-bound-timeout".to_string()),
+                    alias: Some("auth-bound-timeout-1".to_string()),
                     blob: None,
                 },
                 None,
@@ -223,7 +228,7 @@
                 b"entropy",
             )
             .context("key generation failed")?;
-        info!("A: created auth-timeout key {key:?}");
+        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);
@@ -279,7 +284,7 @@
 
     // Lock and unlock to ensure super keys are already created.
     auth_service
-        .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
         .unwrap();
     auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
 
@@ -304,6 +309,9 @@
 
 #[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")
@@ -323,8 +331,8 @@
         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_secure_id(bio_fake_sid1)
+            .user_secure_id(bio_fake_sid2)
             .user_auth_type(HardwareAuthenticatorType::ANY)
             .auth_timeout(3)
             .algorithm(Algorithm::EC)
@@ -338,7 +346,7 @@
                 &KeyDescriptor {
                     domain: Domain::APP,
                     nspace: -1,
-                    alias: Some("auth-bound-timeout".to_string()),
+                    alias: Some("auth-bound-timeout-2".to_string()),
                     blob: None,
                 },
                 None,
@@ -347,7 +355,7 @@
                 b"entropy",
             )
             .context("key generation failed")?;
-        info!("A: created auth-timeout key {key:?}");
+        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);
@@ -361,7 +369,12 @@
         reader.recv();
 
         let result = sec_level.createOperation(&key, &params, UNFORCED);
-        expect_km_error!(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
+        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.
@@ -395,10 +408,10 @@
 
     // Lock and unlock to ensure super keys are already created.
     auth_service
-        .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
+        .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();
+    auth_service.addAuthToken(&fake_lskf_token(gk_fake_sid)).unwrap();
 
     info!("trigger child process action A and wait for completion");
     child_handle.send(&BarrierReached {});
@@ -406,7 +419,7 @@
 
     // 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();
+    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 {});
@@ -421,6 +434,8 @@
 
 #[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()
@@ -449,7 +464,7 @@
             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_sid1)
             .user_auth_type(HardwareAuthenticatorType::ANY)
             .algorithm(Algorithm::EC)
             .purpose(KeyPurpose::SIGN)
@@ -462,7 +477,7 @@
                 &KeyDescriptor {
                     domain: Domain::APP,
                     nspace: -1,
-                    alias: Some("auth-per-op".to_string()),
+                    alias: Some("auth-per-op-1".to_string()),
                     blob: None,
                 },
                 None,
@@ -471,7 +486,7 @@
                 b"entropy",
             )
             .context("key generation failed")?;
-        info!("A: created auth-per-op key {key:?}");
+        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);
@@ -530,7 +545,7 @@
 
     // Lock and unlock to ensure super keys are already created.
     auth_service
-        .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_DISABLED)
         .unwrap();
     auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
 
@@ -554,8 +569,10 @@
 }
 
 #[test]
-fn test_auth_bound_per_op_failure() {
-    type Barrier = BarrierReachedWithData<i64>;
+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")
@@ -566,17 +583,24 @@
                          writer: &mut ChannelWriter<Barrier>|
           -> Result<(), run_as::Error> {
         // Now we're in a new process, wait to be notified before starting.
-        reader.recv();
+        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_FAKE_SID)
-            .user_secure_id(BIO_FAKE_SID1)
+            .user_secure_id(gk_sid)
+            .user_secure_id(bio_fake_sid1)
             .user_auth_type(HardwareAuthenticatorType::ANY)
             .algorithm(Algorithm::EC)
             .purpose(KeyPurpose::SIGN)
@@ -589,7 +613,7 @@
                 &KeyDescriptor {
                     domain: Domain::APP,
                     nspace: -1,
-                    alias: Some("auth-per-op".to_string()),
+                    alias: Some("auth-per-op-2".to_string()),
                     blob: None,
                 },
                 None,
@@ -598,7 +622,7 @@
                 b"entropy",
             )
             .context("key generation failed")?;
-        info!("A: created auth-per-op key {key:?}");
+        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);
@@ -613,7 +637,7 @@
         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(0)); // A done.
+        writer.send(&Barrier::new(None)); // A done.
 
         // Action B: fail again when an irrelevant HAT is available.
         reader.recv();
@@ -629,7 +653,7 @@
         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(0)); // B done.
+        writer.send(&Barrier::new(None)); // B done.
 
         // Action C: start an operation and pass out the challenge
         reader.recv();
@@ -638,7 +662,7 @@
             .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(result.operationChallenge.unwrap().challenge)); // C done.
+        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
@@ -647,7 +671,7 @@
         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(0)); // D done.
+        writer.send(&Barrier::new(None)); // D done.
 
         Ok(())
     };
@@ -664,37 +688,43 @@
     // 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)
+        .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(&Barrier::new(0));
+    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_FAKE_SID)).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(0));
+    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(0));
-    let challenge = child_handle.recv_or_die().0;
+    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_FAKE_SID, challenge)).unwrap();
+    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(0));
+    child_handle.send(&Barrier::new(None));
     child_handle.recv_or_die();
 
     assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
@@ -702,6 +732,9 @@
 
 #[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")
@@ -804,10 +837,10 @@
 
     // Lock and unlock to ensure super keys are already created.
     auth_service
-        .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
+        .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();
+    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 {});
@@ -815,7 +848,7 @@
 
     // Move to locked and don't allow weak unlock, so super keys are wiped.
     auth_service
-        .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
+        .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");
@@ -824,7 +857,7 @@
 
     // Unlock with password => loads super key from database.
     auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
-    auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).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 {});
@@ -832,7 +865,7 @@
 
     // Move to locked and allow weak unlock, then do a weak unlock.
     auth_service
-        .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_ENABLED)
+        .onDeviceLocked(user_id, &[bio_fake_sid1, bio_fake_sid2], WEAK_UNLOCK_ENABLED)
         .unwrap();
     auth_service.onDeviceUnlocked(user_id, None).unwrap();
 
@@ -866,7 +899,7 @@
         challenge: 0,
         userId: gk_sid,
         authenticatorId: bio_sid,
-        authenticatorType: HardwareAuthenticatorType::PASSWORD,
+        authenticatorType: HardwareAuthenticatorType::FINGERPRINT,
         timestamp: Timestamp { milliSeconds: 123 },
         mac: vec![1, 2, 3],
     }
diff --git a/provisioner/rkp_factory_extraction_lib_test.cpp b/provisioner/rkp_factory_extraction_lib_test.cpp
index 9bfb25e..e21ef93 100644
--- a/provisioner/rkp_factory_extraction_lib_test.cpp
+++ b/provisioner/rkp_factory_extraction_lib_test.cpp
@@ -61,6 +61,77 @@
 
 }  // 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());
@@ -184,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;
@@ -295,12 +366,7 @@
 }
 
 TEST(LibRkpFactoryExtractionTests, requireUdsCerts) {
-    const std::vector<uint8_t> kCsr = Array()
-                                          .add(1 /* version */)
-                                          .add(Map() /* UdsCerts */)
-                                          .add(Array() /* DiceCertChain */)
-                                          .add(Array() /* SignedData */)
-                                          .encode();
+    const std::vector<uint8_t> csrEncoded = kCsrWithoutUdsCerts;
     std::vector<uint8_t> challenge;
 
     // Set up mock, then call getCsr
@@ -313,23 +379,18 @@
                 generateCertificateRequestV2(IsEmpty(),   // keysToSign
                                              _,           // challenge
                                              NotNull()))  // _aidl_return
-        .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(kCsr),
+        .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(csrEncoded),
                         Return(ByMove(ScopedAStatus::ok()))));
 
     auto [csr, csrErrMsg] =
-        getCsr("mock component name", mockRpc.get(),
+        getCsr("default", mockRpc.get(),
                /*selfTest=*/true, /*allowDegenerate=*/false, /*requireUdsCerts=*/true);
     ASSERT_EQ(csr, nullptr);
-    ASSERT_THAT(csrErrMsg, testing::HasSubstr("UdsCerts must not be empty"));
+    ASSERT_THAT(csrErrMsg, testing::HasSubstr("UdsCerts are required"));
 }
 
 TEST(LibRkpFactoryExtractionTests, dontRequireUdsCerts) {
-    const std::vector<uint8_t> kCsr = Array()
-                                          .add(1 /* version */)
-                                          .add(Map() /* UdsCerts */)
-                                          .add(Array() /* DiceCertChain */)
-                                          .add(Array() /* SignedData */)
-                                          .encode();
+    const std::vector<uint8_t> csrEncoded = kCsrWithoutUdsCerts;
     std::vector<uint8_t> challenge;
 
     // Set up mock, then call getCsr
@@ -342,14 +403,14 @@
                 generateCertificateRequestV2(IsEmpty(),   // keysToSign
                                              _,           // challenge
                                              NotNull()))  // _aidl_return
-        .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(kCsr),
+        .WillOnce(DoAll(SaveArg<1>(&challenge), SetArgPointee<2>(csrEncoded),
                         Return(ByMove(ScopedAStatus::ok()))));
 
     auto [csr, csrErrMsg] =
-        getCsr("mock component name", mockRpc.get(),
+        getCsr("default", mockRpc.get(),
                /*selfTest=*/true, /*allowDegenerate=*/false, /*requireUdsCerts=*/false);
     ASSERT_EQ(csr, nullptr);
-    ASSERT_THAT(csrErrMsg, testing::Not(testing::HasSubstr("UdsCerts must not be empty")));
+    ASSERT_THAT(csrErrMsg, testing::HasSubstr("challenges do not match"));
 }
 
 TEST(LibRkpFactoryExtractionTests, parseCommaDelimitedString) {
@@ -362,4 +423,4 @@
     ASSERT_TRUE(rpcSet.count("avf") == 1);
     ASSERT_TRUE(rpcSet.count("strongbox") == 1);
     ASSERT_TRUE(rpcSet.count("Strongbox") == 1);
-}
\ No newline at end of file
+}