Add rebind_alias.

Bug: 159370859
Test: atest keystore2_test
Change-Id: Ia4ad48fd576fc12b4bfe78bc09ed33c6cf0008a4
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 447b95f..394b7be 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -20,7 +20,7 @@
 use keystore_aidl_generated as aidl;
 #[cfg(not(test))]
 use rand::prelude::random;
-use rusqlite::{params, Connection, NO_PARAMS};
+use rusqlite::{params, Connection, TransactionBehavior, NO_PARAMS};
 #[cfg(test)]
 use tests::random;
 
@@ -101,6 +101,52 @@
             }
         }
     }
+
+    pub fn rebind_alias(
+        &mut self,
+        newid: u32,
+        alias: &str,
+        domain: aidl::Domain,
+        namespace: i64,
+    ) -> Result<()> {
+        match domain {
+            aidl::Domain::App | aidl::Domain::SELinux => {}
+            _ => {
+                return Err(KsError::sys())
+                    .context(format!("Domain {:?} must be either App or SELinux.", domain));
+            }
+        }
+        let tx = self
+            .conn
+            .transaction_with_behavior(TransactionBehavior::Immediate)
+            .context("Failed to initialize transaction.")?;
+        tx.execute(
+            "UPDATE persistent.keyentry
+                 SET alias = NULL, domain = NULL, namespace = NULL
+                 WHERE alias = ? AND domain = ? AND namespace = ?;",
+            params![alias, domain as i64, namespace],
+        )
+        .context("Failed to rebind existing entry.")?;
+        let result = tx
+            .execute(
+                "UPDATE persistent.keyentry
+                 SET alias = ?
+                 WHERE id = ? AND domain = ? AND namespace = ?;",
+                params![alias, newid, domain as i64, namespace],
+            )
+            .context("Failed to set alias.")?;
+        if result != 1 {
+            // Note that this explicit rollback is not required, as
+            // the transaction should rollback if we do not commit it.
+            // We leave it here for readability.
+            tx.rollback().context("Failed to rollback a failed transaction.")?;
+            return Err(KsError::sys()).context(format!(
+                "Expected to update a single entry but instead updated {}.",
+                result
+            ));
+        }
+        tx.commit().context("Failed to commit transaction.")
+    }
 }
 
 #[cfg(test)]
@@ -208,6 +254,64 @@
         Ok(())
     }
 
+    #[test]
+    fn test_rebind_alias() -> Result<()> {
+        use aidl::Domain;
+
+        fn extractor(ke: &KeyEntryRow) -> (Option<Domain>, Option<i64>, Option<&str>) {
+            (ke.domain, ke.namespace, ke.alias.as_deref())
+        }
+
+        let mut db = KeystoreDB::new()?;
+        db.create_key_entry(Domain::App, 42)?;
+        db.create_key_entry(Domain::App, 42)?;
+        let entries = get_keyentry(&db)?;
+        assert_eq!(entries.len(), 2);
+        assert_eq!(extractor(&entries[0]), (Some(Domain::App), Some(42), None));
+        assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), None));
+
+        // Test that the first call to rebind_alias sets the alias.
+        db.rebind_alias(entries[0].id, "foo", Domain::App, 42)?;
+        let entries = get_keyentry(&db)?;
+        assert_eq!(entries.len(), 2);
+        assert_eq!(extractor(&entries[0]), (Some(Domain::App), Some(42), Some("foo")));
+        assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), None));
+
+        // Test that the second call to rebind_alias also empties the old one.
+        db.rebind_alias(entries[1].id, "foo", Domain::App, 42)?;
+        let entries = get_keyentry(&db)?;
+        assert_eq!(entries.len(), 2);
+        assert_eq!(extractor(&entries[0]), (None, None, None));
+        assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), Some("foo")));
+
+        // Test that we must pass in a valid Domain.
+        check_result_is_error_containing_string(
+            db.rebind_alias(0, "foo", Domain::Grant, 42),
+            "Domain Grant must be either App or SELinux.",
+        );
+        check_result_is_error_containing_string(
+            db.rebind_alias(0, "foo", Domain::Blob, 42),
+            "Domain Blob must be either App or SELinux.",
+        );
+        check_result_is_error_containing_string(
+            db.rebind_alias(0, "foo", Domain::KeyId, 42),
+            "Domain KeyId must be either App or SELinux.",
+        );
+
+        // Test that we correctly handle setting an alias for something that does not exist.
+        check_result_is_error_containing_string(
+            db.rebind_alias(0, "foo", Domain::SELinux, 42),
+            "Expected to update a single entry but instead updated 0",
+        );
+        // Test that we correctly abort the transaction in this case.
+        let entries = get_keyentry(&db)?;
+        assert_eq!(entries.len(), 2);
+        assert_eq!(extractor(&entries[0]), (None, None, None));
+        assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), Some("foo")));
+
+        Ok(())
+    }
+
     // Helpers
 
     // Checks that the given result is an error containing the given string.