Merge "Replace libchrome string_util to libbase strings" into main
diff --git a/keystore2/aconfig/flags.aconfig b/keystore2/aconfig/flags.aconfig
index 05dae46..2e9659f 100644
--- a/keystore2/aconfig/flags.aconfig
+++ b/keystore2/aconfig/flags.aconfig
@@ -26,9 +26,25 @@
 }
 
 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"
   bug: "283077822"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "use_blob_state_column"
+  namespace: "hardware_backed_security"
+  description: "Use state database column to track superseded blobentry rows"
+  bug: "319563050"
+  is_fixed_read_only: true
+}
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/database.rs b/keystore2/src/database.rs
index 8165c54..8451a33 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -500,6 +500,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`
@@ -870,8 +904,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 +948,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 +1047,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 +1073,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,7 +1233,7 @@
         )
     }
 
-    /// 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> {
@@ -1195,9 +1315,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("KeystoreDB::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
@@ -1244,22 +1383,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("KeystoreDB::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 +1679,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."))?;
@@ -2263,6 +2427,15 @@
             .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`).  Mark the affected rows with `state=Orphaned` so a subsequent garbage
+        // collection can do this.
+        tx.execute(
+            "UPDATE persistent.blobentry SET state = ? WHERE keyentryid = ?",
+            params![BlobState::Orphaned, key_id],
+        )
+        .context("Trying to mark blobentrys as superseded")?;
         Ok(updated != 0)
     }
 
@@ -2547,6 +2720,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,
@@ -2564,7 +2739,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 { "" }
         );
 
diff --git a/keystore2/src/database/tests.rs b/keystore2/src/database/tests.rs
index 5f882cd..381a45a 100644
--- a/keystore2/src/database/tests.rs
+++ b/keystore2/src/database/tests.rs
@@ -2429,6 +2429,20 @@
     .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()?;
@@ -2439,6 +2453,9 @@
     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));
 
@@ -2447,6 +2464,9 @@
     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));
 
@@ -2459,6 +2479,9 @@
     .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));
 
@@ -2468,6 +2491,9 @@
     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));
 
@@ -2477,6 +2503,9 @@
     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));
 
@@ -2484,6 +2513,9 @@
     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));
 
@@ -2491,6 +2523,55 @@
 }
 
 #[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;
@@ -2652,6 +2733,16 @@
 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")
@@ -2670,6 +2761,10 @@
         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());
@@ -2753,3 +2848,27 @@
         }
     })
 }
+
+#[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 bc68f15..a047cf3 100644
--- a/keystore2/src/database/versioning.rs
+++ b/keystore2/src/database/versioning.rs
@@ -61,7 +61,7 @@
     Ok(version)
 }
 
-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/maintenance.rs b/keystore2/src/maintenance.rs
index 61277f1..4c895ae 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -24,7 +24,7 @@
 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::{
@@ -36,6 +36,9 @@
 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};
@@ -264,9 +267,84 @@
         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 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 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(())
+    }
 }
 
-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<()> {
diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs
index 01f0a01..7149d12 100644
--- a/keystore2/src/metrics_store.rs
+++ b/keystore2/src/metrics_store.rs
@@ -69,6 +69,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
@@ -82,7 +102,7 @@
     /// 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 {
             return pull_storage_stats();
         }
@@ -677,3 +697,309 @@
     ///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",
+);
+
+// Metrics values for HardwareAuthenticatorType are broken -- the AIDL type is a bitmask
+// not an enum, so offseting the enum values by 1 doesn't work.
+impl_summary_enum!(MetricsHardwareAuthenticatorType, 6,
+    AUTH_TYPE_UNSPECIFIED => "UNSPEC",
+    NONE => "NONE",
+    PASSWORD => "PASSWD",
+    FINGERPRINT => "FPRINT",
+    ANY => "ANY",
+);
+
+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())
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[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)");
+    }
+}
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 bd20afb..89c0e97 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,
@@ -131,11 +131,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(
@@ -622,7 +632,14 @@
                     log_security_safe_params(&params)
                 ))
                 .map(|(mut result, _)| {
-                    result.certificateChain.push(attestation_certs);
+                    // 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
                 })
             }
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index 81ebdab..2b69d1e 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -38,6 +38,7 @@
 };
 use android_system_keystore2::aidl::android::system::keystore2::{
     Authorization::Authorization, Domain::Domain, KeyDescriptor::KeyDescriptor,
+    ResponseCode::ResponseCode,
 };
 use anyhow::{Context, Result};
 use binder::{FromIBinder, StatusCode, Strong, ThreadState};
@@ -125,14 +126,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
@@ -141,10 +148,19 @@
 /// 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")?;
 
@@ -160,8 +176,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")),
     }
 }
 
diff --git a/keystore2/tests/keystore2_client_aes_key_tests.rs b/keystore2/tests/keystore2_client_aes_key_tests.rs
index 3c5fda5..7128911 100644
--- a/keystore2/tests/keystore2_client_aes_key_tests.rs
+++ b/keystore2/tests/keystore2_client_aes_key_tests.rs
@@ -203,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();
@@ -247,7 +247,12 @@
         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
diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs
index f723d02..033036b 100644
--- a/keystore2/tests/keystore2_client_attest_key_tests.rs
+++ b/keystore2/tests/keystore2_client_attest_key_tests.rs
@@ -615,6 +615,8 @@
         // 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();
 
diff --git a/keystore2/tests/keystore2_client_authorizations_tests.rs b/keystore2/tests/keystore2_client_authorizations_tests.rs
index 6732f5c..2a6adba 100644
--- a/keystore2/tests/keystore2_client_authorizations_tests.rs
+++ b/keystore2/tests/keystore2_client_authorizations_tests.rs
@@ -633,8 +633,8 @@
     }
 }
 
-/// 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() {
     let sl = SecLevel::tee();
@@ -646,7 +646,8 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .app_data(b"app-data".to_vec());
+        .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::create_key_and_operation(
@@ -655,16 +656,17 @@
         &authorizations::AuthSetBuilder::new()
             .purpose(KeyPurpose::SIGN)
             .digest(Digest::SHA_2_256)
-            .app_data(b"app-data".to_vec()),
+            .app_data(b"app-data".to_vec())
+            .app_id(b"app-id".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`.
+/// 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
+/// with error code `INVALID_KEY_BLOB`.
 #[test]
 fn keystore2_gen_key_auth_app_data_test_fail() {
     let sl = SecLevel::tee();
@@ -676,7 +678,8 @@
         .purpose(KeyPurpose::VERIFY)
         .digest(Digest::SHA_2_256)
         .ec_curve(EcCurve::P_256)
-        .app_data(b"app-data".to_vec());
+        .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(
@@ -685,7 +688,8 @@
         &authorizations::AuthSetBuilder::new()
             .purpose(KeyPurpose::SIGN)
             .digest(Digest::SHA_2_256)
-            .app_data(b"invalid-app-data".to_vec()),
+            .app_data(b"invalid-app-data".to_vec())
+            .app_id(b"invalid-app-id".to_vec()),
         alias,
     ));
     assert!(result.is_err());
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()))?;