From 02ac615d62b00f9b4ceb280426f3b7f67a8a326f Mon Sep 17 00:00:00 2001
From: John Baublitz <jbaublitz@redhat.com>
Date: Wed, 13 Nov 2024 11:26:05 -0500
Subject: [PATCH] Modify D-Bus interface to support multiple token slots

---
 src/dbus_api/api/manager_3_8/api.rs           |  24 +-
 src/dbus_api/api/manager_3_8/methods.rs       |  81 ++--
 src/dbus_api/consts.rs                        |   2 +
 src/dbus_api/pool/mod.rs                      |  19 +-
 src/dbus_api/pool/pool_3_0/methods.rs         |  42 +-
 src/dbus_api/pool/pool_3_8/api.rs             | 103 +++++
 src/dbus_api/pool/pool_3_8/methods.rs         | 399 ++++++++++++++++++
 src/dbus_api/pool/pool_3_8/mod.rs             |  12 +
 src/dbus_api/pool/pool_3_8/props.rs           |  27 ++
 src/dbus_api/pool/prop_conv.rs                |  98 ++++-
 src/dbus_api/pool/shared.rs                   |  16 +-
 src/dbus_api/tree.rs                          | 226 ++++++----
 src/dbus_api/types.rs                         |  27 +-
 src/engine/engine.rs                          |   4 +-
 src/engine/sim_engine/pool.rs                 |   8 +-
 .../strat_engine/backstore/backstore/v2.rs    |  50 ++-
 src/engine/strat_engine/crypt/handle/v2.rs    |  28 +-
 src/engine/strat_engine/crypt/shared.rs       |   4 +-
 src/engine/strat_engine/pool/dispatch.rs      |   4 +-
 src/engine/strat_engine/pool/v1.rs            |   9 +-
 src/engine/strat_engine/pool/v2.rs            |  18 +-
 src/engine/types/actions.rs                   |  12 +-
 src/engine/types/keys.rs                      |  17 +
 23 files changed, 983 insertions(+), 247 deletions(-)
 create mode 100644 src/dbus_api/pool/pool_3_8/api.rs
 create mode 100644 src/dbus_api/pool/pool_3_8/methods.rs
 create mode 100644 src/dbus_api/pool/pool_3_8/mod.rs
 create mode 100644 src/dbus_api/pool/pool_3_8/props.rs

diff --git a/src/dbus_api/api/manager_3_8/api.rs b/src/dbus_api/api/manager_3_8/api.rs
index 5b8e468599..0e1a5d2464 100644
--- a/src/dbus_api/api/manager_3_8/api.rs
+++ b/src/dbus_api/api/manager_3_8/api.rs
@@ -20,7 +20,7 @@ pub fn start_pool_method(f: &Factory<MTSync<TData>, TData>) -> Method<MTSync<TDa
     f.method("StartPool", (), start_pool)
         .in_arg(("id", "s"))
         .in_arg(("id_type", "s"))
-        .in_arg(("unlock_method", "(bs)"))
+        .in_arg(("unlock_method", "(b(bu))"))
         .in_arg(("key_fd", "(bh)"))
         // In order from left to right:
         // b: true if the pool was newly started
@@ -38,21 +38,23 @@ pub fn create_pool_method(f: &Factory<MTSync<TData>, TData>) -> Method<MTSync<TD
     f.method("CreatePool", (), create_pool)
         .in_arg(("name", "s"))
         .in_arg(("devices", "as"))
-        // Optional key description of key in the kernel keyring
-        // b: true if the pool should be encrypted and able to be
-        // unlocked with a passphrase associated with this key description.
+        // Optional key descriptions of key in the kernel keyring
+        // a: array of zero or more elements
+        // b: true if a token slot is specified
+        // i: token slot
         // s: key description
         //
-        // Rust representation: (bool, String)
-        .in_arg(("key_desc", "(bs)"))
-        // Optional Clevis information for binding on initialization.
-        // b: true if the pool should be encrypted and able to be unlocked
-        // using Clevis.
+        // Rust representation: Vec<((bool, u32), String)>
+        .in_arg(("key_desc", "a((bu)s)"))
+        // Optional Clevis infos for binding on initialization.
+        // a: array of zero or more elements
+        // b: true if a token slot is specified
+        // i: token slot
         // s: pin name
         // s: JSON config for Clevis use
         //
-        // Rust representation: (bool, (String, String))
-        .in_arg(("clevis_info", "(b(ss))"))
+        // Rust representation: Vec<((bool, u32), String, String)>
+        .in_arg(("clevis_info", "a((bu)ss)"))
         // Optional journal size for integrity metadata reservation.
         // b: true if the size should be specified.
         //    false if the default should be used.
diff --git a/src/dbus_api/api/manager_3_8/methods.rs b/src/dbus_api/api/manager_3_8/methods.rs
index a8c50e245b..2845048f48 100644
--- a/src/dbus_api/api/manager_3_8/methods.rs
+++ b/src/dbus_api/api/manager_3_8/methods.rs
@@ -10,12 +10,12 @@ use dbus::{
 };
 use dbus_tree::{MTSync, MethodInfo, MethodResult};
 use futures::executor::block_on;
+use serde_json::from_str;
 
 use devicemapper::Bytes;
 
 use crate::{
     dbus_api::{
-        api::shared::EncryptionParams,
         blockdev::create_dbus_blockdev,
         filesystem::create_dbus_filesystem,
         pool::create_dbus_pool,
@@ -24,11 +24,16 @@ use crate::{
     },
     engine::{
         CreateAction, InputEncryptionInfo, IntegritySpec, IntegrityTagSpec, KeyDescription, Name,
-        PoolIdentifier, PoolUuid, StartAction, TokenUnlockMethod, UnlockMethod,
+        PoolIdentifier, PoolUuid, StartAction, TokenUnlockMethod,
     },
     stratis::StratisError,
 };
 
+type EncryptionInfos<'a> = (
+    Vec<((bool, u32), &'a str)>,
+    Vec<((bool, u32), (&'a str, &'a str))>,
+);
+
 pub fn start_pool(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
     let base_path = m.path.get_name();
     let message: &Message = m.msg;
@@ -64,29 +69,15 @@ pub fn start_pool(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
             }
         }
     };
-    let unlock_method = {
-        let unlock_method_tup: (bool, &str) = get_next_arg(&mut iter, 2)?;
-        match tuple_to_option(unlock_method_tup) {
-            Some(unlock_method_str) => {
-                match UnlockMethod::try_from(unlock_method_str).map_err(|_| {
-                    StratisError::Msg(format!("{unlock_method_str} is an invalid unlock method"))
-                }) {
-                    Ok(um) => Some(um),
-                    Err(e) => {
-                        let (rc, rs) = engine_to_dbus_err_tuple(&e);
-                        return Ok(vec![return_message.append3(default_return, rc, rs)]);
-                    }
-                }
-            }
-            None => None,
-        }
-    };
+    let unlock_method_tup: (bool, (bool, u32)) = get_next_arg(&mut iter, 2)?;
+    let unlock_method =
+        TokenUnlockMethod::from_options(tuple_to_option(unlock_method_tup).map(tuple_to_option));
     let fd_opt: (bool, OwnedFd) = get_next_arg(&mut iter, 3)?;
     let fd = tuple_to_option(fd_opt);
 
     let ret = match handle_action!(block_on(dbus_context.engine.start_pool(
         id.clone(),
-        TokenUnlockMethod::from(unlock_method),
+        unlock_method,
         fd.map(|f| f.into_fd()),
     ))) {
         Ok(StartAction::Started(_)) => {
@@ -153,10 +144,8 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
 
     let name: &str = get_next_arg(&mut iter, 0)?;
     let devs: Array<'_, &str, _> = get_next_arg(&mut iter, 1)?;
-    let (key_desc_tuple, clevis_tuple): EncryptionParams = (
-        Some(get_next_arg(&mut iter, 2)?),
-        Some(get_next_arg(&mut iter, 3)?),
-    );
+    let (key_desc_array, clevis_array): EncryptionInfos<'_> =
+        (get_next_arg(&mut iter, 2)?, get_next_arg(&mut iter, 3)?);
     let journal_size_tuple: (bool, u64) = get_next_arg(&mut iter, 4)?;
     let tag_spec_tuple: (bool, String) = get_next_arg(&mut iter, 5)?;
     let allocate_superblock_tuple: (bool, bool) = get_next_arg(&mut iter, 6)?;
@@ -166,26 +155,44 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
     let default_return: (bool, (dbus::Path<'static>, Vec<dbus::Path<'static>>)) =
         (false, (dbus::Path::default(), Vec::new()));
 
-    let key_desc = match key_desc_tuple.and_then(tuple_to_option) {
-        Some(kds) => match KeyDescription::try_from(kds) {
-            Ok(kd) => Some(kd),
+    let key_descs =
+        match key_desc_array
+            .into_iter()
+            .try_fold(Vec::new(), |mut vec, (ts_opt, kd_str)| {
+                let token_slot = tuple_to_option(ts_opt);
+                let kd = KeyDescription::try_from(kd_str.to_string())?;
+                vec.push((token_slot, kd));
+                Ok(vec)
+            }) {
+            Ok(kds) => kds,
             Err(e) => {
                 let (rc, rs) = engine_to_dbus_err_tuple(&e);
                 return Ok(vec![return_message.append3(default_return, rc, rs)]);
             }
-        },
-        None => None,
-    };
+        };
 
-    let clevis_info = match clevis_tuple.and_then(tuple_to_option) {
-        Some((pin, json_string)) => match serde_json::from_str(json_string.as_str()) {
-            Ok(j) => Some((pin, j)),
+    let clevis_infos =
+        match clevis_array
+            .into_iter()
+            .try_fold(Vec::new(), |mut vec, (ts_opt, (pin, json_str))| {
+                let token_slot = tuple_to_option(ts_opt);
+                let json = from_str(json_str)?;
+                vec.push((token_slot, (pin.to_owned(), json)));
+                Ok(vec)
+            }) {
+            Ok(cis) => cis,
             Err(e) => {
-                let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::Serde(e));
+                let (rc, rs) = engine_to_dbus_err_tuple(&e);
                 return Ok(vec![return_message.append3(default_return, rc, rs)]);
             }
-        },
-        None => None,
+        };
+
+    let ei = match InputEncryptionInfo::new(key_descs, clevis_infos) {
+        Ok(opt) => opt,
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return Ok(vec![return_message.append3(default_return, rc, rs)]);
+        }
     };
 
     let journal_size = tuple_to_option(journal_size_tuple).map(Bytes::from);
@@ -208,7 +215,7 @@ pub fn create_pool(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
     let create_result = handle_action!(block_on(dbus_context.engine.create_pool(
         name,
         &devs.map(Path::new).collect::<Vec<&Path>>(),
-        InputEncryptionInfo::new_legacy(key_desc, clevis_info).as_ref(),
+        ei.as_ref(),
         IntegritySpec {
             journal_size,
             tag_spec,
diff --git a/src/dbus_api/consts.rs b/src/dbus_api/consts.rs
index ada3d96bac..41b6391246 100644
--- a/src/dbus_api/consts.rs
+++ b/src/dbus_api/consts.rs
@@ -44,9 +44,11 @@ pub const POOL_HAS_CACHE_PROP: &str = "HasCache";
 pub const POOL_ENCRYPTED_PROP: &str = "Encrypted";
 pub const POOL_AVAIL_ACTIONS_PROP: &str = "AvailableActions";
 pub const POOL_KEY_DESC_PROP: &str = "KeyDescription";
+pub const POOL_KEY_DESCS_PROP: &str = "KeyDescriptions";
 pub const POOL_TOTAL_SIZE_PROP: &str = "TotalPhysicalSize";
 pub const POOL_TOTAL_USED_PROP: &str = "TotalPhysicalUsed";
 pub const POOL_CLEVIS_INFO_PROP: &str = "ClevisInfo";
+pub const POOL_CLEVIS_INFOS_PROP: &str = "ClevisInfos";
 pub const POOL_ALLOC_SIZE_PROP: &str = "AllocatedSize";
 pub const POOL_FS_LIMIT_PROP: &str = "FsLimit";
 pub const POOL_OVERPROV_PROP: &str = "Overprovisioning";
diff --git a/src/dbus_api/pool/mod.rs b/src/dbus_api/pool/mod.rs
index 57a63cf335..7c2cf69f92 100644
--- a/src/dbus_api/pool/mod.rs
+++ b/src/dbus_api/pool/mod.rs
@@ -19,6 +19,7 @@ mod pool_3_3;
 mod pool_3_5;
 mod pool_3_6;
 mod pool_3_7;
+mod pool_3_8;
 pub mod prop_conv;
 mod shared;
 
@@ -281,14 +282,14 @@ pub fn create_dbus_pool<'a>(
                 .add_m(pool_3_7::destroy_filesystems_method(&f))
                 .add_m(pool_3_0::snapshot_filesystem_method(&f))
                 .add_m(pool_3_0::add_blockdevs_method(&f))
-                .add_m(pool_3_0::bind_clevis_method(&f))
-                .add_m(pool_3_0::unbind_clevis_method(&f))
+                .add_m(pool_3_8::bind_clevis_method(&f))
+                .add_m(pool_3_8::unbind_clevis_method(&f))
                 .add_m(pool_3_5::init_cache_method(&f))
                 .add_m(pool_3_0::add_cachedevs_method(&f))
-                .add_m(pool_3_0::bind_keyring_method(&f))
-                .add_m(pool_3_0::unbind_keyring_method(&f))
-                .add_m(pool_3_0::rebind_keyring_method(&f))
-                .add_m(pool_3_0::rebind_clevis_method(&f))
+                .add_m(pool_3_8::bind_keyring_method(&f))
+                .add_m(pool_3_8::unbind_keyring_method(&f))
+                .add_m(pool_3_8::rebind_keyring_method(&f))
+                .add_m(pool_3_8::rebind_clevis_method(&f))
                 .add_m(pool_3_0::rename_method(&f))
                 .add_m(pool_3_3::grow_physical_device_method(&f))
                 .add_m(pool_3_7::get_metadata_method(&f))
@@ -297,8 +298,8 @@ pub fn create_dbus_pool<'a>(
                 .add_p(pool_3_0::uuid_property(&f))
                 .add_p(pool_3_0::encrypted_property(&f))
                 .add_p(pool_3_0::avail_actions_property(&f))
-                .add_p(pool_3_0::key_desc_property(&f))
-                .add_p(pool_3_0::clevis_info_property(&f))
+                .add_p(pool_3_8::key_descs_property(&f))
+                .add_p(pool_3_8::clevis_infos_property(&f))
                 .add_p(pool_3_0::has_cache_property(&f))
                 .add_p(pool_3_0::alloc_size_property(&f))
                 .add_p(pool_3_0::used_size_property(&f))
@@ -444,7 +445,7 @@ pub fn get_pool_properties(
             consts::POOL_UUID_PROP => uuid_to_string!(pool_uuid),
             consts::POOL_ENCRYPTED_PROP => shared::pool_enc_prop(pool),
             consts::POOL_AVAIL_ACTIONS_PROP => shared::pool_avail_actions_prop(pool),
-            consts::POOL_KEY_DESC_PROP => shared::pool_key_desc_prop(pool),
+            consts::POOL_KEY_DESC_PROP => shared::pool_key_descs_prop(pool),
             consts::POOL_CLEVIS_INFO_PROP => shared::pool_clevis_info_prop(pool),
             consts::POOL_HAS_CACHE_PROP => shared::pool_has_cache_prop(pool),
             consts::POOL_ALLOC_SIZE_PROP => shared::pool_allocated_size(pool),
diff --git a/src/dbus_api/pool/pool_3_0/methods.rs b/src/dbus_api/pool/pool_3_0/methods.rs
index 30b7a19df0..32997cd4bd 100644
--- a/src/dbus_api/pool/pool_3_0/methods.rs
+++ b/src/dbus_api/pool/pool_3_0/methods.rs
@@ -384,8 +384,11 @@ pub fn bind_clevis(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
             return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Ok(CreateAction::Created(_)) => {
-            dbus_context
-                .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy());
+            dbus_context.push_pool_clevis_info_change(
+                pool_path.get_name(),
+                pool.encryption_info()
+                    .map(|either| either.map_left(|ei| (true, ei))),
+            );
             return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Err(e) => {
@@ -422,8 +425,11 @@ pub fn unbind_clevis(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
             return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Ok(DeleteAction::Deleted(_)) => {
-            dbus_context
-                .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy());
+            dbus_context.push_pool_clevis_info_change(
+                pool_path.get_name(),
+                pool.encryption_info()
+                    .map(|either| either.map_left(|ei| (true, ei))),
+            );
             return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Err(e) => {
@@ -474,8 +480,11 @@ pub fn bind_keyring(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
             return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Ok(CreateAction::Created(_)) => {
-            dbus_context
-                .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy());
+            dbus_context.push_pool_key_desc_change(
+                pool_path.get_name(),
+                pool.encryption_info()
+                    .map(|either| either.map_left(|ei| (true, ei))),
+            );
             return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Err(e) => {
@@ -516,8 +525,11 @@ pub fn unbind_keyring(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult
             return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Ok(DeleteAction::Deleted(_)) => {
-            dbus_context
-                .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy());
+            dbus_context.push_pool_key_desc_change(
+                pool_path.get_name(),
+                pool.encryption_info()
+                    .map(|either| either.map_left(|ei| (true, ei))),
+            );
             return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Err(e) => {
@@ -568,8 +580,11 @@ pub fn rebind_keyring(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult
             return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Ok(RenameAction::Renamed(_)) => {
-            dbus_context
-                .push_pool_key_desc_change(pool_path.get_name(), pool.encryption_info_legacy());
+            dbus_context.push_pool_key_desc_change(
+                pool_path.get_name(),
+                pool.encryption_info()
+                    .map(|either| either.map_left(|ei| (true, ei))),
+            );
             return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Ok(RenameAction::NoSource) => {
@@ -610,8 +625,11 @@ pub fn rebind_clevis(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
 
     let msg = match handle_action!(pool.rebind_clevis(None), dbus_context, pool_path.get_name()) {
         Ok(_) => {
-            dbus_context
-                .push_pool_clevis_info_change(pool_path.get_name(), pool.encryption_info_legacy());
+            dbus_context.push_pool_clevis_info_change(
+                pool_path.get_name(),
+                pool.encryption_info()
+                    .map(|either| either.map_left(|ei| (true, ei))),
+            );
             return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
         }
         Err(e) => {
diff --git a/src/dbus_api/pool/pool_3_8/api.rs b/src/dbus_api/pool/pool_3_8/api.rs
new file mode 100644
index 0000000000..ab581d317b
--- /dev/null
+++ b/src/dbus_api/pool/pool_3_8/api.rs
@@ -0,0 +1,103 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use dbus_tree::{Access, EmitsChangedSignal, Factory, MTSync, Method, Property};
+
+use crate::dbus_api::{
+    pool::{
+        consts,
+        pool_3_8::{
+            methods::{
+                bind_clevis, bind_keyring, rebind_clevis, rebind_keyring, unbind_clevis,
+                unbind_keyring,
+            },
+            props::{get_pool_clevis_infos, get_pool_key_descs},
+        },
+    },
+    types::TData,
+};
+
+pub fn bind_clevis_method(f: &Factory<MTSync<TData>, TData>) -> Method<MTSync<TData>, TData> {
+    f.method("BindClevis", (), bind_clevis)
+        .in_arg(("pin", "s"))
+        .in_arg(("json", "s"))
+        .in_arg(("token_slot", "(b(bu))"))
+        // b: Indicates if new clevis bindings were added
+        //
+        // Rust representation: bool
+        .out_arg(("results", "b"))
+        .out_arg(("return_code", "q"))
+        .out_arg(("return_string", "s"))
+}
+
+pub fn unbind_clevis_method(f: &Factory<MTSync<TData>, TData>) -> Method<MTSync<TData>, TData> {
+    f.method("UnbindClevis", (), unbind_clevis)
+        .in_arg(("token_slot", "(bu)"))
+        // b: Indicates if clevis bindings were removed
+        //
+        // Rust representation: bool
+        .out_arg(("results", "b"))
+        .out_arg(("return_code", "q"))
+        .out_arg(("return_string", "s"))
+}
+
+pub fn bind_keyring_method(f: &Factory<MTSync<TData>, TData>) -> Method<MTSync<TData>, TData> {
+    f.method("BindKeyring", (), bind_keyring)
+        .in_arg(("key_desc", "s"))
+        .in_arg(("token_slot", "(b(bu))"))
+        // b: Indicates if new keyring bindings were added
+        //
+        // Rust representation: bool
+        .out_arg(("results", "b"))
+        .out_arg(("return_code", "q"))
+        .out_arg(("return_string", "s"))
+}
+
+pub fn unbind_keyring_method(f: &Factory<MTSync<TData>, TData>) -> Method<MTSync<TData>, TData> {
+    f.method("UnbindKeyring", (), unbind_keyring)
+        .in_arg(("token_slot", "(bu)"))
+        // b: Indicates if keyring bindings were removed
+        //
+        // Rust representation: bool
+        .out_arg(("results", "b"))
+        .out_arg(("return_code", "q"))
+        .out_arg(("return_string", "s"))
+}
+
+pub fn rebind_keyring_method(f: &Factory<MTSync<TData>, TData>) -> Method<MTSync<TData>, TData> {
+    f.method("RebindKeyring", (), rebind_keyring)
+        .in_arg(("key_desc", "s"))
+        .in_arg(("token_slot", "(bu)"))
+        // b: Indicates if keyring bindings were changed
+        //
+        // Rust representation: bool
+        .out_arg(("results", "b"))
+        .out_arg(("return_code", "q"))
+        .out_arg(("return_string", "s"))
+}
+
+pub fn rebind_clevis_method(f: &Factory<MTSync<TData>, TData>) -> Method<MTSync<TData>, TData> {
+    f.method("RebindClevis", (), rebind_clevis)
+        .in_arg(("token_slot", "(bu)"))
+        // b: Indicates if Clevis bindings were changed
+        //
+        // Rust representation: bool
+        .out_arg(("results", "b"))
+        .out_arg(("return_code", "q"))
+        .out_arg(("return_string", "s"))
+}
+
+pub fn key_descs_property(f: &Factory<MTSync<TData>, TData>) -> Property<MTSync<TData>, TData> {
+    f.property::<Vec<(u32, String)>, _>(consts::POOL_KEY_DESCS_PROP, ())
+        .access(Access::Read)
+        .emits_changed(EmitsChangedSignal::True)
+        .on_get(get_pool_key_descs)
+}
+
+pub fn clevis_infos_property(f: &Factory<MTSync<TData>, TData>) -> Property<MTSync<TData>, TData> {
+    f.property::<Vec<(u32, (String, String))>, _>(consts::POOL_CLEVIS_INFOS_PROP, ())
+        .access(Access::Read)
+        .emits_changed(EmitsChangedSignal::True)
+        .on_get(get_pool_clevis_infos)
+}
diff --git a/src/dbus_api/pool/pool_3_8/methods.rs b/src/dbus_api/pool/pool_3_8/methods.rs
new file mode 100644
index 0000000000..f1ac66b536
--- /dev/null
+++ b/src/dbus_api/pool/pool_3_8/methods.rs
@@ -0,0 +1,399 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use dbus::Message;
+use dbus_tree::{MTSync, MethodInfo, MethodResult};
+use serde_json::Value;
+
+use crate::{
+    dbus_api::{
+        types::{DbusErrorEnum, TData, OK_STRING},
+        util::{engine_to_dbus_err_tuple, get_next_arg, tuple_to_option},
+    },
+    engine::{CreateAction, DeleteAction, KeyDescription, OptionalTokenSlotInput, RenameAction},
+    stratis::StratisError,
+};
+
+pub fn bind_clevis(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
+    let message: &Message = m.msg;
+    let mut iter = message.iter_init();
+    let pin: String = get_next_arg(&mut iter, 0)?;
+    let json_string: String = get_next_arg(&mut iter, 1)?;
+    let token_slot_tuple: (bool, u32) = get_next_arg(&mut iter, 2)?;
+
+    let dbus_context = m.tree.get_data();
+    let object_path = m.path.get_name();
+    let return_message = message.method_return();
+    let default_return = false;
+
+    let pool_path = m
+        .tree
+        .get(object_path)
+        .expect("implicit argument must be in tree");
+    let pool_uuid = typed_uuid!(
+        get_data!(pool_path; default_return; return_message).uuid;
+        Pool;
+        default_return;
+        return_message
+    );
+
+    let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message);
+    let lowest_token_slot = pool
+        .encryption_info()
+        .and_then(|either| either.left())
+        .as_ref()
+        .and_then(|ei| ei.single_clevis_info())
+        .map(|(token_slot, _)| token_slot);
+
+    let json: Value = match serde_json::from_str(&json_string) {
+        Ok(j) => j,
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&StratisError::Serde(e));
+            return Ok(vec![return_message.append3(default_return, rc, rs)]);
+        }
+    };
+
+    let token_slot = match tuple_to_option(token_slot_tuple) {
+        Some(t) => OptionalTokenSlotInput::Some(t),
+        None => OptionalTokenSlotInput::None,
+    };
+
+    let msg = match handle_action!(
+        pool.bind_clevis(token_slot, pin.as_str(), &json),
+        dbus_context,
+        pool_path.get_name()
+    ) {
+        Ok(CreateAction::Identity) => {
+            return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Ok(CreateAction::Created(_)) => {
+            dbus_context.push_pool_clevis_info_change(
+                pool_path.get_name(),
+                pool.encryption_info()
+                    .map(|either| either.map_left(|ei| (lowest_token_slot.is_none(), ei))),
+            );
+            return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return_message.append3(default_return, rc, rs)
+        }
+    };
+    Ok(vec![msg])
+}
+
+pub fn unbind_clevis(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
+    let message: &Message = m.msg;
+
+    let mut iter = message.iter_init();
+    let token_slot_tuple: (bool, u32) = get_next_arg(&mut iter, 0)?;
+
+    let dbus_context = m.tree.get_data();
+    let object_path = m.path.get_name();
+    let return_message = message.method_return();
+    let default_return = false;
+
+    let token_slot = tuple_to_option(token_slot_tuple);
+
+    let pool_path = m
+        .tree
+        .get(object_path)
+        .expect("implicit argument must be in tree");
+    let pool_uuid = typed_uuid!(
+        get_data!(pool_path; default_return; return_message).uuid;
+        Pool;
+        default_return;
+        return_message
+    );
+
+    let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message);
+    let lowest_token_slot = pool
+        .encryption_info()
+        .and_then(|either| either.left())
+        .as_ref()
+        .and_then(|ei| ei.single_clevis_info())
+        .map(|(token_slot, _)| token_slot);
+
+    let msg = match handle_action!(
+        pool.unbind_clevis(token_slot),
+        dbus_context,
+        pool_path.get_name()
+    ) {
+        Ok(DeleteAction::Identity) => {
+            return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Ok(DeleteAction::Deleted(_)) => {
+            dbus_context.push_pool_clevis_info_change(
+                pool_path.get_name(),
+                pool.encryption_info().map(|either| {
+                    either.map_left(|ei| {
+                        (token_slot.is_some() && token_slot == lowest_token_slot, ei)
+                    })
+                }),
+            );
+            return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return_message.append3(default_return, rc, rs)
+        }
+    };
+    Ok(vec![msg])
+}
+
+pub fn bind_keyring(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
+    let message: &Message = m.msg;
+    let mut iter = message.iter_init();
+    let key_desc_str: String = get_next_arg(&mut iter, 0)?;
+    let token_slot_tuple: (bool, u32) = get_next_arg(&mut iter, 1)?;
+
+    let dbus_context = m.tree.get_data();
+    let object_path = m.path.get_name();
+    let return_message = message.method_return();
+    let default_return = false;
+
+    let key_desc = match KeyDescription::try_from(key_desc_str) {
+        Ok(kd) => kd,
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return Ok(vec![return_message.append3(default_return, rc, rs)]);
+        }
+    };
+
+    let token_slot = match tuple_to_option(token_slot_tuple) {
+        Some(t) => OptionalTokenSlotInput::Some(t),
+        None => OptionalTokenSlotInput::None,
+    };
+
+    let pool_path = m
+        .tree
+        .get(object_path)
+        .expect("implicit argument must be in tree");
+    let pool_uuid = typed_uuid!(
+        get_data!(pool_path; default_return; return_message).uuid;
+        Pool;
+        default_return;
+        return_message
+    );
+
+    let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message);
+    let lowest_token_slot = pool
+        .encryption_info()
+        .and_then(|either| either.left())
+        .as_ref()
+        .and_then(|ei| ei.single_key_description())
+        .map(|(token_slot, _)| token_slot);
+
+    let msg = match handle_action!(
+        pool.bind_keyring(token_slot, &key_desc),
+        dbus_context,
+        pool_path.get_name()
+    ) {
+        Ok(CreateAction::Identity) => {
+            return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Ok(CreateAction::Created(_)) => {
+            dbus_context.push_pool_key_desc_change(
+                pool_path.get_name(),
+                pool.encryption_info()
+                    .map(|ei| ei.map_left(|e| (lowest_token_slot.is_none(), e))),
+            );
+            return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return_message.append3(default_return, rc, rs)
+        }
+    };
+    Ok(vec![msg])
+}
+
+pub fn unbind_keyring(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
+    let message: &Message = m.msg;
+
+    let mut iter = message.iter_init();
+    let token_slot_tuple: (bool, u32) = get_next_arg(&mut iter, 0)?;
+
+    let dbus_context = m.tree.get_data();
+    let object_path = m.path.get_name();
+    let return_message = message.method_return();
+    let default_return = false;
+
+    let token_slot = tuple_to_option(token_slot_tuple);
+
+    let pool_path = m
+        .tree
+        .get(object_path)
+        .expect("implicit argument must be in tree");
+    let pool_uuid = typed_uuid!(
+        get_data!(pool_path; default_return; return_message).uuid;
+        Pool;
+        default_return;
+        return_message
+    );
+
+    let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message);
+    let lowest_token_slot = pool
+        .encryption_info()
+        .and_then(|either| either.left())
+        .as_ref()
+        .and_then(|ei| ei.single_key_description())
+        .map(|(token_slot, _)| token_slot);
+
+    let msg = match handle_action!(
+        pool.unbind_keyring(token_slot),
+        dbus_context,
+        pool_path.get_name()
+    ) {
+        Ok(DeleteAction::Identity) => {
+            return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Ok(DeleteAction::Deleted(_)) => {
+            dbus_context.push_pool_key_desc_change(
+                pool_path.get_name(),
+                pool.encryption_info().map(|either| {
+                    either.map_left(|ei| {
+                        (token_slot.is_some() && token_slot == lowest_token_slot, ei)
+                    })
+                }),
+            );
+            return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return_message.append3(default_return, rc, rs)
+        }
+    };
+    Ok(vec![msg])
+}
+
+pub fn rebind_keyring(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
+    let message: &Message = m.msg;
+    let mut iter = message.iter_init();
+    let key_desc_str: String = get_next_arg(&mut iter, 0)?;
+    let token_slot_tuple: (bool, u32) = get_next_arg(&mut iter, 1)?;
+
+    let dbus_context = m.tree.get_data();
+    let object_path = m.path.get_name();
+    let return_message = message.method_return();
+    let default_return = false;
+
+    let key_desc = match KeyDescription::try_from(key_desc_str) {
+        Ok(kd) => kd,
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return Ok(vec![return_message.append3(default_return, rc, rs)]);
+        }
+    };
+
+    let token_slot = tuple_to_option(token_slot_tuple);
+
+    let pool_path = m
+        .tree
+        .get(object_path)
+        .expect("implicit argument must be in tree");
+    let pool_uuid = typed_uuid!(
+        get_data!(pool_path; default_return; return_message).uuid;
+        Pool;
+        default_return;
+        return_message
+    );
+
+    let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message);
+    let lowest_token_slot = pool
+        .encryption_info()
+        .and_then(|either| either.left())
+        .as_ref()
+        .and_then(|ei| ei.single_key_description())
+        .map(|(token_slot, _)| token_slot);
+
+    let msg = match handle_action!(
+        pool.rebind_keyring(token_slot, &key_desc),
+        dbus_context,
+        pool_path.get_name()
+    ) {
+        Ok(RenameAction::Identity) => {
+            return_message.append3(false, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Ok(RenameAction::Renamed(_)) => {
+            dbus_context.push_pool_key_desc_change(
+                pool_path.get_name(),
+                pool.encryption_info().map(|either| {
+                    either.map_left(|ei| {
+                        (token_slot.is_some() && token_slot == lowest_token_slot, ei)
+                    })
+                }),
+            );
+            return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Ok(RenameAction::NoSource) => {
+            let error_message = format!(
+                "pool with UUID {pool_uuid} is not currently bound to a keyring passphrase"
+            );
+            let (rc, rs) = (DbusErrorEnum::ERROR as u16, error_message);
+            return_message.append3(default_return, rc, rs)
+        }
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return_message.append3(default_return, rc, rs)
+        }
+    };
+    Ok(vec![msg])
+}
+
+pub fn rebind_clevis(m: &MethodInfo<'_, MTSync<TData>, TData>) -> MethodResult {
+    let message: &Message = m.msg;
+
+    let mut iter = message.iter_init();
+    let token_slot_tuple: (bool, u32) = get_next_arg(&mut iter, 0)?;
+
+    let dbus_context = m.tree.get_data();
+    let object_path = m.path.get_name();
+    let return_message = message.method_return();
+    let default_return = false;
+
+    let token_slot = tuple_to_option(token_slot_tuple);
+
+    let pool_path = m
+        .tree
+        .get(object_path)
+        .expect("implicit argument must be in tree");
+    let pool_uuid = typed_uuid!(
+        get_data!(pool_path; default_return; return_message).uuid;
+        Pool;
+        default_return;
+        return_message
+    );
+
+    let mut pool = get_mut_pool!(dbus_context.engine; pool_uuid; default_return; return_message);
+    let lowest_token_slot = pool
+        .encryption_info()
+        .and_then(|either| either.left())
+        .as_ref()
+        .and_then(|ei| ei.single_clevis_info())
+        .map(|(token_slot, _)| token_slot);
+
+    let msg = match handle_action!(
+        pool.rebind_clevis(token_slot),
+        dbus_context,
+        pool_path.get_name()
+    ) {
+        Ok(_) => {
+            dbus_context.push_pool_clevis_info_change(
+                pool_path.get_name(),
+                pool.encryption_info().map(|either| {
+                    either.map_left(|ei| {
+                        (token_slot.is_some() && token_slot == lowest_token_slot, ei)
+                    })
+                }),
+            );
+            return_message.append3(true, DbusErrorEnum::OK as u16, OK_STRING.to_string())
+        }
+        Err(e) => {
+            let (rc, rs) = engine_to_dbus_err_tuple(&e);
+            return_message.append3(default_return, rc, rs)
+        }
+    };
+    Ok(vec![msg])
+}
diff --git a/src/dbus_api/pool/pool_3_8/mod.rs b/src/dbus_api/pool/pool_3_8/mod.rs
new file mode 100644
index 0000000000..4ec70ddccb
--- /dev/null
+++ b/src/dbus_api/pool/pool_3_8/mod.rs
@@ -0,0 +1,12 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+mod api;
+mod methods;
+mod props;
+
+pub use api::{
+    bind_clevis_method, bind_keyring_method, clevis_infos_property, key_descs_property,
+    rebind_clevis_method, rebind_keyring_method, unbind_clevis_method, unbind_keyring_method,
+};
diff --git a/src/dbus_api/pool/pool_3_8/props.rs b/src/dbus_api/pool/pool_3_8/props.rs
new file mode 100644
index 0000000000..c218f37f61
--- /dev/null
+++ b/src/dbus_api/pool/pool_3_8/props.rs
@@ -0,0 +1,27 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use dbus::arg::IterAppend;
+use dbus_tree::{MTSync, MethodErr, PropInfo};
+
+use crate::dbus_api::{
+    pool::shared::{self, get_pool_property},
+    types::TData,
+};
+
+pub fn get_pool_key_descs(
+    i: &mut IterAppend<'_>,
+    p: &PropInfo<'_, MTSync<TData>, TData>,
+) -> Result<(), MethodErr> {
+    get_pool_property(i, p, |(_, _, pool)| Ok(shared::pool_key_descs_prop(pool)))
+}
+
+pub fn get_pool_clevis_infos(
+    i: &mut IterAppend<'_>,
+    p: &PropInfo<'_, MTSync<TData>, TData>,
+) -> Result<(), MethodErr> {
+    get_pool_property(i, p, |(_, _, pool)| {
+        Ok(shared::pool_clevis_infos_prop(pool))
+    })
+}
diff --git a/src/dbus_api/pool/prop_conv.rs b/src/dbus_api/pool/prop_conv.rs
index 512872cf9a..6143f77026 100644
--- a/src/dbus_api/pool/prop_conv.rs
+++ b/src/dbus_api/pool/prop_conv.rs
@@ -2,19 +2,26 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+use dbus::arg::{RefArg, Variant};
+use either::Either;
+
 use devicemapper::Bytes;
 
 use crate::{
     dbus_api::util::option_to_tuple,
-    engine::{ActionAvailability, PoolEncryptionInfo},
+    engine::{ActionAvailability, EncryptionInfo, PoolEncryptionInfo},
     stratis::StratisResult,
 };
 
 /// Convert an encryption information data structure to a
 /// D-Bus type.
-fn enc_to_prop<F, T>(ei: Option<PoolEncryptionInfo>, f: F, default: T) -> (bool, (bool, T))
+fn enc_to_prop<F, T>(
+    ei: Option<Either<EncryptionInfo, PoolEncryptionInfo>>,
+    f: F,
+    default: T,
+) -> (bool, (bool, T))
 where
-    F: Fn(PoolEncryptionInfo) -> StratisResult<Option<T>>,
+    F: Fn(Either<EncryptionInfo, PoolEncryptionInfo>) -> StratisResult<Option<T>>,
     T: Clone,
 {
     option_to_tuple(
@@ -28,29 +35,102 @@ where
 
 /// Fetch the key description and handle converting it into a
 /// D-Bus type.
-pub fn key_desc_to_prop(ei: Option<PoolEncryptionInfo>) -> (bool, (bool, String)) {
+pub fn key_desc_to_prop(
+    ei: Option<Either<EncryptionInfo, PoolEncryptionInfo>>,
+) -> (bool, (bool, String)) {
     enc_to_prop(
         ei,
         |ei| {
-            ei.key_description()
-                .map(|kd_opt| kd_opt.map(|kd| kd.as_application_str().to_string()))
+            ei.either(
+                |ei| {
+                    Ok(ei
+                        .single_key_description()
+                        .map(|(_, kd)| kd.as_application_str().to_owned()))
+                },
+                |pei| {
+                    pei.key_description()
+                        .map(|opt| opt.map(|k| k.as_application_str().to_owned()))
+                },
+            )
         },
         String::new(),
     )
 }
 
+/// Fetch the key descriptions and handle converting it into a
+/// D-Bus type.
+pub fn key_descs_to_prop(
+    eei: Option<Either<EncryptionInfo, PoolEncryptionInfo>>,
+) -> Variant<Box<dyn RefArg>> {
+    match eei {
+        Some(Either::Left(ei)) => Variant(Box::new(
+            ei.all_key_descriptions()
+                .map(|(i, kd)| (*i, kd.as_application_str().to_string()))
+                .collect::<Vec<_>>(),
+        )),
+        Some(Either::Right(pei)) => Variant(Box::new(option_to_tuple(
+            pei.key_description()
+                .map(|kd_opt| {
+                    option_to_tuple(
+                        kd_opt.map(|kd| kd.as_application_str().to_string()),
+                        String::new(),
+                    )
+                })
+                .ok(),
+            (false, String::new()),
+        ))),
+        None => Variant(Box::new("Unencrypted".to_string())),
+    }
+}
+
 /// Fetch the Clevis information and handle converting it into a
 /// D-Bus type.
-pub fn clevis_info_to_prop(ei: Option<PoolEncryptionInfo>) -> (bool, (bool, (String, String))) {
+pub fn clevis_info_to_prop(
+    ei: Option<Either<EncryptionInfo, PoolEncryptionInfo>>,
+) -> (bool, (bool, (String, String))) {
     enc_to_prop(
         ei,
         |ei| {
-            ei.clevis_info()
-                .map(|ci_opt| ci_opt.map(|(pin, cfg)| (pin.to_owned(), cfg.to_string())))
+            ei.either(
+                |ei| {
+                    Ok(ei
+                        .single_clevis_info()
+                        .map(|(_, (pin, ci))| (pin.to_owned(), ci.to_string())))
+                },
+                |pei| {
+                    pei.clevis_info()
+                        .map(|opt| opt.map(|(pin, ci)| (pin.to_owned(), ci.to_string())))
+                },
+            )
         },
         (String::new(), String::new()),
     )
 }
+/// Fetch the Clevis infos and handle converting it into a
+/// D-Bus type.
+pub fn clevis_infos_to_prop(
+    eei: Option<Either<EncryptionInfo, PoolEncryptionInfo>>,
+) -> Variant<Box<dyn RefArg>> {
+    match eei {
+        Some(Either::Left(ei)) => Variant(Box::new(
+            ei.all_clevis_infos()
+                .map(|(i, (pin, config))| (*i, (pin.to_owned(), config.to_string())))
+                .collect::<Vec<_>>(),
+        )),
+        Some(Either::Right(pei)) => Variant(Box::new(option_to_tuple(
+            pei.clevis_info()
+                .map(|ci_opt| {
+                    option_to_tuple(
+                        ci_opt.map(|(pin, config)| (pin.to_owned(), config.to_string())),
+                        (String::new(), String::new()),
+                    )
+                })
+                .ok(),
+            (false, (String::new(), String::new())),
+        ))),
+        None => Variant(Box::new("Unencrypted".to_string())),
+    }
+}
 
 /// Generate D-Bus representation of pool state property.
 #[inline]
diff --git a/src/dbus_api/pool/shared.rs b/src/dbus_api/pool/shared.rs
index 41100fca0d..b77fa4c639 100644
--- a/src/dbus_api/pool/shared.rs
+++ b/src/dbus_api/pool/shared.rs
@@ -5,7 +5,7 @@
 use std::{fmt::Display, path::Path};
 
 use dbus::{
-    arg::{Array, IterAppend},
+    arg::{Array, IterAppend, RefArg, Variant},
     Message,
 };
 use dbus_tree::{MTSync, MethodErr, MethodInfo, MethodResult, PropInfo, Tree};
@@ -283,12 +283,22 @@ pub fn pool_avail_actions_prop(pool: &dyn Pool) -> String {
 
 /// Generate D-Bus representation of a pool key description property.
 pub fn pool_key_desc_prop(pool: &dyn Pool) -> (bool, (bool, String)) {
-    prop_conv::key_desc_to_prop(pool.encryption_info_legacy())
+    prop_conv::key_desc_to_prop(pool.encryption_info())
+}
+
+/// Generate D-Bus representation of a pool key descriptions property.
+pub fn pool_key_descs_prop(pool: &dyn Pool) -> Variant<Box<dyn RefArg>> {
+    prop_conv::key_descs_to_prop(pool.encryption_info())
 }
 
 /// Generate D-Bus representation of a pool Clevis info property.
 pub fn pool_clevis_info_prop(pool: &dyn Pool) -> (bool, (bool, (String, String))) {
-    prop_conv::clevis_info_to_prop(pool.encryption_info_legacy())
+    prop_conv::clevis_info_to_prop(pool.encryption_info())
+}
+
+/// Generate D-Bus representation of a pool Clevis infos property.
+pub fn pool_clevis_infos_prop(pool: &dyn Pool) -> Variant<Box<dyn RefArg>> {
+    prop_conv::clevis_infos_to_prop(pool.encryption_info())
 }
 
 /// Generate D-Bus representation of a boolean indicating whether the pool
diff --git a/src/dbus_api/tree.rs b/src/dbus_api/tree.rs
index 4ffab6c470..c094493c88 100644
--- a/src/dbus_api/tree.rs
+++ b/src/dbus_api/tree.rs
@@ -18,6 +18,7 @@ use dbus::{
     Path,
 };
 use dbus_tree::{MTSync, ObjectPath};
+use either::Either;
 use tokio::sync::{broadcast::Receiver, mpsc::UnboundedReceiver};
 
 use devicemapper::{Bytes, Sectors};
@@ -44,12 +45,14 @@ use crate::{
         util::{poll_exit_and_future, thread_safe_to_dbus_sendable},
     },
     engine::{
-        ActionAvailability, DevUuid, FilesystemUuid, LockedPoolsInfo, PoolEncryptionInfo, PoolUuid,
-        StoppedPoolsInfo, StratisUuid,
+        ActionAvailability, DevUuid, EncryptionInfo, FilesystemUuid, LockedPoolsInfo,
+        PoolEncryptionInfo, PoolUuid, StoppedPoolsInfo, StratisUuid,
     },
     stratis::{StratisError, StratisResult},
 };
 
+use super::pool::prop_conv::{clevis_infos_to_prop, key_descs_to_prop};
+
 type PropertySignal = HashMap<String, (HashMap<String, Variant<Box<dyn RefArg>>>, Vec<String>)>;
 
 /// Handler for a D-Bus tree.
@@ -412,56 +415,76 @@ impl DbusTreeHandler {
     }
 
     /// Handle a change of the key description for a pool in the engine.
-    fn handle_pool_key_desc_change(&self, item: Path<'static>, ei: Option<PoolEncryptionInfo>) {
-        let kd_prop = key_desc_to_prop(ei);
+    fn handle_pool_key_desc_change(
+        &self,
+        item: Path<'static>,
+        ei: Option<Either<(bool, EncryptionInfo), PoolEncryptionInfo>>,
+    ) {
+        let kd_prop = key_desc_to_prop(ei.clone().map(|either| either.map_left(|(_, ei)| ei)));
+        if ei
+            .as_ref()
+            .map(|either| {
+                either.as_ref().left().map(|(b, _)| *b).unwrap_or(false) || either.is_right()
+            })
+            .expect("Should be encrypted to send signal")
+            && self
+                .property_changed_invalidated_signal(
+                    &item,
+                    prop_hashmap! {
+                        consts::POOL_INTERFACE_NAME_3_0 => {
+                            Vec::new(),
+                            consts::POOL_KEY_DESC_PROP.to_string() =>
+                            box_variant!(kd_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_1 => {
+                            Vec::new(),
+                            consts::POOL_KEY_DESC_PROP.to_string() =>
+                            box_variant!(kd_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_2 => {
+                            Vec::new(),
+                            consts::POOL_KEY_DESC_PROP.to_string() =>
+                            box_variant!(kd_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_3 => {
+                            Vec::new(),
+                            consts::POOL_KEY_DESC_PROP.to_string() =>
+                            box_variant!(kd_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_4 => {
+                            Vec::new(),
+                            consts::POOL_KEY_DESC_PROP.to_string() =>
+                            box_variant!(kd_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_5 => {
+                            Vec::new(),
+                            consts::POOL_KEY_DESC_PROP.to_string() =>
+                            box_variant!(kd_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_6 => {
+                            Vec::new(),
+                            consts::POOL_KEY_DESC_PROP.to_string() =>
+                            box_variant!(kd_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_7 => {
+                            Vec::new(),
+                            consts::POOL_KEY_DESC_PROP.to_string() =>
+                            box_variant!(kd_prop.clone())
+                        }
+                    },
+                )
+                .is_err()
+        {
+            warn!("Signal on pool key description change was not sent to the D-Bus client");
+        }
         if self
             .property_changed_invalidated_signal(
                 &item,
                 prop_hashmap! {
-                    consts::POOL_INTERFACE_NAME_3_0 => {
-                        Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_1 => {
-                        Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_2 => {
-                        Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_3 => {
-                        Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_4 => {
-                        Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_5 => {
-                        Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_6 => {
-                        Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_7 => {
-                        Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop.clone())
-                    },
                     consts::POOL_INTERFACE_NAME_3_8 => {
                         Vec::new(),
-                        consts::POOL_KEY_DESC_PROP.to_string() =>
-                        box_variant!(kd_prop)
+                        consts::POOL_KEY_DESCS_PROP.to_string() =>
+                        key_descs_to_prop(ei.clone().map(|either| either.map_left(|(_, ei)| ei)))
                     }
                 },
             )
@@ -471,57 +494,78 @@ impl DbusTreeHandler {
         }
     }
 
-    /// Handle a change of the key description for a pool in the engine.
-    fn handle_pool_clevis_info_change(&self, item: Path<'static>, ei: Option<PoolEncryptionInfo>) {
-        let ci_prop = clevis_info_to_prop(ei);
+    /// Handle a change of the Clevis info for a pool in the engine.
+    fn handle_pool_clevis_info_change(
+        &self,
+        item: Path<'static>,
+        ei: Option<Either<(bool, EncryptionInfo), PoolEncryptionInfo>>,
+    ) {
+        let ci_prop = clevis_info_to_prop(ei.clone().map(|either| either.map_left(|(_, ei)| ei)));
+
+        if ei
+            .as_ref()
+            .map(|either| {
+                either.as_ref().left().map(|(b, _)| *b).unwrap_or(false) || either.is_right()
+            })
+            .expect("Should be encrypted to send signal")
+            && self
+                .property_changed_invalidated_signal(
+                    &item,
+                    prop_hashmap! {
+                        consts::POOL_INTERFACE_NAME_3_0 => {
+                            Vec::new(),
+                            consts::POOL_CLEVIS_INFO_PROP.to_string() =>
+                            box_variant!(ci_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_1 => {
+                            Vec::new(),
+                            consts::POOL_CLEVIS_INFO_PROP.to_string() =>
+                            box_variant!(ci_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_2 => {
+                            Vec::new(),
+                            consts::POOL_CLEVIS_INFO_PROP.to_string() =>
+                            box_variant!(ci_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_3 => {
+                            Vec::new(),
+                            consts::POOL_CLEVIS_INFO_PROP.to_string() =>
+                            box_variant!(ci_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_4 => {
+                            Vec::new(),
+                            consts::POOL_CLEVIS_INFO_PROP.to_string() =>
+                            box_variant!(ci_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_5 => {
+                            Vec::new(),
+                            consts::POOL_CLEVIS_INFO_PROP.to_string() =>
+                            box_variant!(ci_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_6 => {
+                            Vec::new(),
+                            consts::POOL_CLEVIS_INFO_PROP.to_string() =>
+                            box_variant!(ci_prop.clone())
+                        },
+                        consts::POOL_INTERFACE_NAME_3_7 => {
+                            Vec::new(),
+                            consts::POOL_CLEVIS_INFO_PROP.to_string() =>
+                            box_variant!(ci_prop.clone())
+                        }
+                    },
+                )
+                .is_err()
+        {
+            warn!("Signal on pool Clevis information change was not sent to the D-Bus client");
+        }
         if self
             .property_changed_invalidated_signal(
                 &item,
                 prop_hashmap! {
-                    consts::POOL_INTERFACE_NAME_3_0 => {
-                        Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_1 => {
-                        Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_2 => {
-                        Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_3 => {
-                        Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_4 => {
-                        Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_5 => {
-                        Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_6 => {
-                        Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop.clone())
-                    },
-                    consts::POOL_INTERFACE_NAME_3_7 => {
-                        Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop.clone())
-                    },
                     consts::POOL_INTERFACE_NAME_3_8 => {
                         Vec::new(),
-                        consts::POOL_CLEVIS_INFO_PROP.to_string() =>
-                        box_variant!(ci_prop)
+                        consts::POOL_CLEVIS_INFOS_PROP.to_string() =>
+                        clevis_infos_to_prop(ei.clone().map(|either| either.map_left(|(_, ei)| ei)))
                     }
                 },
             )
diff --git a/src/dbus_api/types.rs b/src/dbus_api/types.rs
index 328dbaf830..bfa2d66afc 100644
--- a/src/dbus_api/types.rs
+++ b/src/dbus_api/types.rs
@@ -18,6 +18,7 @@ use dbus::{
     Path,
 };
 use dbus_tree::{DataType, MTSync, ObjectPath, Tree};
+use either::Either;
 use tokio::sync::{
     mpsc::UnboundedSender as TokioSender, OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock,
 };
@@ -27,10 +28,10 @@ use devicemapper::{Bytes, Sectors};
 use crate::{
     dbus_api::{connection::DbusConnectionHandler, tree::DbusTreeHandler, udev::DbusUdevHandler},
     engine::{
-        total_allocated, total_used, ActionAvailability, DevUuid, Diff, Engine, ExclusiveGuard,
-        FilesystemUuid, Lockable, LockedPoolsInfo, PoolDiff, PoolEncryptionInfo, PoolUuid,
-        SharedGuard, StoppedPoolsInfo, StratBlockDevDiff, StratFilesystemDiff, StratPoolDiff,
-        StratisUuid, ThinPoolDiff,
+        total_allocated, total_used, ActionAvailability, DevUuid, Diff, EncryptionInfo, Engine,
+        ExclusiveGuard, FilesystemUuid, Lockable, LockedPoolsInfo, PoolDiff, PoolEncryptionInfo,
+        PoolUuid, SharedGuard, StoppedPoolsInfo, StratBlockDevDiff, StratFilesystemDiff,
+        StratPoolDiff, StratisUuid, ThinPoolDiff,
     },
 };
 
@@ -97,8 +98,14 @@ pub enum DbusAction {
     FsNameChange(Path<'static>, String),
     PoolNameChange(Path<'static>, String),
     PoolAvailActions(Path<'static>, ActionAvailability),
-    PoolKeyDescChange(Path<'static>, Option<PoolEncryptionInfo>),
-    PoolClevisInfoChange(Path<'static>, Option<PoolEncryptionInfo>),
+    PoolKeyDescChange(
+        Path<'static>,
+        Option<Either<(bool, EncryptionInfo), PoolEncryptionInfo>>,
+    ),
+    PoolClevisInfoChange(
+        Path<'static>,
+        Option<Either<(bool, EncryptionInfo), PoolEncryptionInfo>>,
+    ),
     PoolCacheChange(Path<'static>, bool),
     PoolFsLimitChange(Path<'static>, u64),
     PoolOverprovModeChange(Path<'static>, bool),
@@ -297,7 +304,11 @@ impl DbusContext {
     }
 
     /// Send changed signal for KeyDesc property.
-    pub fn push_pool_key_desc_change(&self, item: &Path<'static>, ei: Option<PoolEncryptionInfo>) {
+    pub fn push_pool_key_desc_change(
+        &self,
+        item: &Path<'static>,
+        ei: Option<Either<(bool, EncryptionInfo), PoolEncryptionInfo>>,
+    ) {
         if let Err(e) = self
             .sender
             .send(DbusAction::PoolKeyDescChange(item.clone(), ei))
@@ -313,7 +324,7 @@ impl DbusContext {
     pub fn push_pool_clevis_info_change(
         &self,
         item: &Path<'static>,
-        ei: Option<PoolEncryptionInfo>,
+        ei: Option<Either<(bool, EncryptionInfo), PoolEncryptionInfo>>,
     ) {
         if let Err(e) = self
             .sender
diff --git a/src/engine/engine.rs b/src/engine/engine.rs
index 6d0f543825..03d6c815ff 100644
--- a/src/engine/engine.rs
+++ b/src/engine/engine.rs
@@ -193,7 +193,7 @@ pub trait Pool: Debug + Send + Sync {
         token_slot: OptionalTokenSlotInput,
         pin: &str,
         clevis_info: &Value,
-    ) -> StratisResult<CreateAction<Clevis>>;
+    ) -> StratisResult<CreateAction<(Clevis, u32)>>;
 
     /// V1: Binds all devices in the pool to a given key description.
     /// * token_slot is always None
@@ -207,7 +207,7 @@ pub trait Pool: Debug + Send + Sync {
         &mut self,
         token_slot: OptionalTokenSlotInput,
         key_desc: &KeyDescription,
-    ) -> StratisResult<CreateAction<Key>>;
+    ) -> StratisResult<CreateAction<(Key, u32)>>;
 
     /// V1: Rebinds all devices in the pool to a given key description.
     /// * token_slot is always None
diff --git a/src/engine/sim_engine/pool.rs b/src/engine/sim_engine/pool.rs
index f652dab21f..587f79d370 100644
--- a/src/engine/sim_engine/pool.rs
+++ b/src/engine/sim_engine/pool.rs
@@ -310,7 +310,7 @@ impl Pool for SimPool {
         token_slot: OptionalTokenSlotInput,
         pin: &str,
         clevis_info: &Value,
-    ) -> StratisResult<CreateAction<Clevis>> {
+    ) -> StratisResult<CreateAction<(Clevis, u32)>> {
         let encryption_info = match self.encryption_info.as_mut() {
             Some(ei) => ei,
             None => {
@@ -360,14 +360,14 @@ impl Pool for SimPool {
             token_slot_to_add,
             UnlockMechanism::ClevisInfo((pin.to_owned(), clevis_info.to_owned())),
         )?;
-        Ok(CreateAction::Created(Clevis))
+        Ok(CreateAction::Created((Clevis, token_slot_to_add)))
     }
 
     fn bind_keyring(
         &mut self,
         token_slot: OptionalTokenSlotInput,
         key_description: &KeyDescription,
-    ) -> StratisResult<CreateAction<Key>> {
+    ) -> StratisResult<CreateAction<(Key, u32)>> {
         let encryption_info = match self.encryption_info.as_mut() {
             Some(ei) => ei,
             None => {
@@ -423,7 +423,7 @@ impl Pool for SimPool {
             token_slot_to_add,
             UnlockMechanism::KeyDesc(key_description.to_owned()),
         )?;
-        Ok(CreateAction::Created(Key))
+        Ok(CreateAction::Created((Key, token_slot_to_add)))
     }
 
     fn unbind_keyring(&mut self, token_slot: Option<u32>) -> StratisResult<DeleteAction<Key>> {
diff --git a/src/engine/strat_engine/backstore/backstore/v2.rs b/src/engine/strat_engine/backstore/backstore/v2.rs
index 761a56b675..9510af2092 100644
--- a/src/engine/strat_engine/backstore/backstore/v2.rs
+++ b/src/engine/strat_engine/backstore/backstore/v2.rs
@@ -936,7 +936,7 @@ impl Backstore {
         token_slot: OptionalTokenSlotInput,
         pin: &str,
         clevis_info: &Value,
-    ) -> StratisResult<bool> {
+    ) -> StratisResult<Option<u32>> {
         let handle = self
             .enc
             .as_mut()
@@ -952,7 +952,7 @@ impl Backstore {
                 let ci = handle.encryption_info().single_clevis_info();
                 if let Some((_, (existing_pin, existing_info))) = ci {
                     if existing_pin.as_str() == pin {
-                        Ok(false)
+                        Ok(None)
                     } else {
                         Err(StratisError::Msg(format!(
                             "Crypt device has already been bound with pin {existing_pin} and config {existing_info}; \
@@ -960,8 +960,7 @@ impl Backstore {
                         )))
                     }
                 } else {
-                    handle.bind_clevis(None, pin, clevis_info)?;
-                    Ok(true)
+                    handle.bind_clevis(None, pin, clevis_info).map(Some)
                 }
             }
             OptionalTokenSlotInput::Some(k) => {
@@ -970,7 +969,7 @@ impl Backstore {
                 let ci = handle.encryption_info().get_info(k);
                 if let Some(UnlockMechanism::ClevisInfo((existing_pin, existing_info))) = ci {
                     if existing_pin == pin {
-                        Ok(false)
+                        Ok(None)
                     } else {
                         Err(StratisError::Msg(format!(
                             "Crypt device has already been bound with pin {existing_pin} and config {existing_info}; \
@@ -978,16 +977,14 @@ impl Backstore {
                         )))
                     }
                 } else {
-                    handle.bind_clevis(Some(k), pin, clevis_info)?;
-                    Ok(true)
+                    handle.bind_clevis(Some(k), pin, clevis_info).map(Some)
                 }
             }
             OptionalTokenSlotInput::None => {
                 // Because idemptotence is checked based on pin, we can't reliably check whether
                 // the binding has already been applied when no token slot is specified. As a
                 // result, we default to adding the new config unless a token slot is specified.
-                handle.bind_clevis(None, pin, clevis_info)?;
-                Ok(true)
+                handle.bind_clevis(None, pin, clevis_info).map(Some)
             }
         }
     }
@@ -1087,7 +1084,7 @@ impl Backstore {
         &mut self,
         token_slot: OptionalTokenSlotInput,
         key_desc: &KeyDescription,
-    ) -> StratisResult<bool> {
+    ) -> StratisResult<Option<u32>> {
         let handle = self
             .enc
             .as_mut()
@@ -1103,7 +1100,7 @@ impl Backstore {
                 let info = handle.encryption_info().single_key_description();
                 if let Some((_, kd)) = info {
                     if kd == key_desc {
-                        Ok(false)
+                        Ok(None)
                     } else {
                         Err(StratisError::Msg(format!(
                             "Crypt device has already been bound with key description {}; \
@@ -1113,8 +1110,7 @@ impl Backstore {
                         )))
                     }
                 } else {
-                    handle.bind_keyring(None, key_desc)?;
-                    Ok(true)
+                    handle.bind_keyring(None, key_desc).map(Some)
                 }
             }
             OptionalTokenSlotInput::Some(k) => {
@@ -1123,7 +1119,7 @@ impl Backstore {
                 let info = handle.encryption_info().get_info(k);
                 if let Some(UnlockMechanism::KeyDesc(ref kd)) = info {
                     if kd == key_desc {
-                        Ok(false)
+                        Ok(None)
                     } else {
                         Err(StratisError::Msg(format!(
                             "Crypt device has already been bound with key description {}; \
@@ -1133,8 +1129,7 @@ impl Backstore {
                         )))
                     }
                 } else {
-                    handle.bind_keyring(Some(k), key_desc)?;
-                    Ok(true)
+                    handle.bind_keyring(Some(k), key_desc).map(Some)
                 }
             }
             OptionalTokenSlotInput::None => {
@@ -1145,10 +1140,9 @@ impl Backstore {
                     .all_key_descriptions()
                     .find(|(_, kd)| *kd == key_desc);
                 if existing_config.is_some() {
-                    Ok(false)
+                    Ok(None)
                 } else {
-                    handle.bind_keyring(None, key_desc)?;
-                    Ok(true)
+                    handle.bind_keyring(None, key_desc).map(Some)
                 }
             }
         }
@@ -1587,7 +1581,7 @@ mod tests {
                 "tang",
                 &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required")})
             ),
-            Ok(false)
+            Ok(None)
         );
 
         assert_matches!(
@@ -1596,7 +1590,7 @@ mod tests {
                 "tang",
                 &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true})
             ),
-            Ok(false)
+            Ok(None)
         );
 
         invariant(&backstore);
@@ -1680,7 +1674,7 @@ mod tests {
                 OptionalTokenSlotInput::Some(ci_slot),
                 "tang",
                 &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}),
-            ).unwrap() {
+            ).unwrap().is_some() {
                 panic!(
                     "Clevis bind idempotence test failed"
                 );
@@ -1691,6 +1685,7 @@ mod tests {
             if backstore
                 .bind_keyring(OptionalTokenSlotInput::None, key_desc)
                 .unwrap()
+                .is_some()
             {
                 panic!("Keyring bind idempotence test failed")
             }
@@ -1698,6 +1693,7 @@ mod tests {
             if backstore
                 .bind_keyring(OptionalTokenSlotInput::Some(kd_slot), key_desc)
                 .unwrap()
+                .is_some()
             {
                 panic!("Keyring bind idempotence test failed")
             }
@@ -1722,11 +1718,11 @@ mod tests {
 
             invariant(&backstore);
 
-            if !backstore.bind_clevis(
+            if backstore.bind_clevis(
                 OptionalTokenSlotInput::Some(10),
                 "tang",
                 &json!({"url": env::var("TANG_URL").expect("TANG_URL env var required"), "stratis:tang:trust_url": true}),
-            ).unwrap() {
+            ).unwrap().is_none() {
                 panic!(
                     "Clevis bind test failed"
                 );
@@ -1752,16 +1748,18 @@ mod tests {
 
             invariant(&backstore);
 
-            if !backstore
+            if backstore
                 .bind_keyring(OptionalTokenSlotInput::Some(11), key_desc)
                 .unwrap()
+                .is_none()
             {
                 panic!("Keyring bind test failed");
             }
 
-            if !backstore
+            if backstore
                 .bind_keyring(OptionalTokenSlotInput::Some(12), key_desc)
                 .unwrap()
+                .is_none()
             {
                 panic!("Keyring bind test failed");
             }
diff --git a/src/engine/strat_engine/crypt/handle/v2.rs b/src/engine/strat_engine/crypt/handle/v2.rs
index a4ec1351ae..22ac9488e2 100644
--- a/src/engine/strat_engine/crypt/handle/v2.rs
+++ b/src/engine/strat_engine/crypt/handle/v2.rs
@@ -536,14 +536,11 @@ impl CryptHandle {
         token_slot: Option<u32>,
         pin: &str,
         json: &Value,
-    ) -> StratisResult<()> {
+    ) -> StratisResult<u32> {
         let mut json_owned = json.clone();
         let yes = interpret_clevis_config(pin, &mut json_owned)?;
 
-        let t = match token_slot {
-            Some(t) => t,
-            None => self.metadata.encryption_info.free_token_slot(),
-        };
+        let old_encryption_info = self.encryption_info();
 
         let (_, key) = get_passphrase(
             &mut acquire_crypt_device(self.luks2_device_path())?,
@@ -553,17 +550,25 @@ impl CryptHandle {
         clevis_luks_bind(
             self.luks2_device_path(),
             &Either::Right(key),
-            Some(t),
+            token_slot,
             pin,
             &json_owned,
             yes,
         )?;
+
+        let encryption_info =
+            encryption_info_from_metadata(&mut acquire_crypt_device(self.luks2_device_path())?)?;
+
+        let new_slot = encryption_info
+            .diff(old_encryption_info)
+            .expect("just added a token slot");
+
         self.metadata.encryption_info.add_info(
-            t,
+            new_slot,
             UnlockMechanism::ClevisInfo((pin.to_owned(), json.to_owned())),
         )?;
 
-        Ok(())
+        Ok(new_slot)
     }
 
     /// Unbind the given device using clevis.
@@ -629,7 +634,7 @@ impl CryptHandle {
         &mut self,
         token_slot: Option<u32>,
         key_desc: &KeyDescription,
-    ) -> StratisResult<()> {
+    ) -> StratisResult<u32> {
         let mut device = self.acquire_crypt_device()?;
         let (_, key) = get_passphrase(&mut device, self.encryption_info())?;
 
@@ -638,12 +643,13 @@ impl CryptHandle {
             None => self.metadata.encryption_info.free_token_slot(),
         };
 
-        add_keyring_keyslot(&mut device, Some(t), key_desc, Some(Either::Left(key)))?;
+        let token_slot =
+            add_keyring_keyslot(&mut device, Some(t), key_desc, Some(Either::Left(key)))?;
 
         self.metadata
             .encryption_info
             .add_info(t, UnlockMechanism::KeyDesc(key_desc.to_owned()))?;
-        Ok(())
+        Ok(token_slot)
     }
 
     /// Remove keyring binding from the underlying LUKS2 volume.
diff --git a/src/engine/strat_engine/crypt/shared.rs b/src/engine/strat_engine/crypt/shared.rs
index a9264d6530..c604cd5441 100644
--- a/src/engine/strat_engine/crypt/shared.rs
+++ b/src/engine/strat_engine/crypt/shared.rs
@@ -102,7 +102,7 @@ pub fn add_keyring_keyslot(
     token_slot: Option<u32>,
     key_description: &KeyDescription,
     pass: Option<Either<SizedKeyMemory, &KeyDescription>>,
-) -> StratisResult<()> {
+) -> StratisResult<u32> {
     let key = key_desc_to_passphrase(key_description)?;
     let keyslot = match pass {
         Some(Either::Left(ref pass)) => {
@@ -155,7 +155,7 @@ pub fn add_keyring_keyslot(
         "Failed to assign the LUKS2 keyring token to the Stratis keyslot"
     );
 
-    Ok(())
+    Ok(new_token_slot)
 }
 
 /// Create a device handle and load the LUKS2 header into memory from
diff --git a/src/engine/strat_engine/pool/dispatch.rs b/src/engine/strat_engine/pool/dispatch.rs
index 89e5d659ad..861114683c 100644
--- a/src/engine/strat_engine/pool/dispatch.rs
+++ b/src/engine/strat_engine/pool/dispatch.rs
@@ -48,7 +48,7 @@ impl Pool for AnyPool {
         token_slot: OptionalTokenSlotInput,
         pin: &str,
         clevis_info: &Value,
-    ) -> StratisResult<CreateAction<Clevis>> {
+    ) -> StratisResult<CreateAction<(Clevis, u32)>> {
         match self {
             AnyPool::V1(p) => p.bind_clevis(token_slot, pin, clevis_info),
             AnyPool::V2(p) => p.bind_clevis(token_slot, pin, clevis_info),
@@ -59,7 +59,7 @@ impl Pool for AnyPool {
         &mut self,
         token_slot: OptionalTokenSlotInput,
         key_description: &KeyDescription,
-    ) -> StratisResult<CreateAction<Key>> {
+    ) -> StratisResult<CreateAction<(Key, u32)>> {
         match self {
             AnyPool::V1(p) => p.bind_keyring(token_slot, key_description),
             AnyPool::V2(p) => p.bind_keyring(token_slot, key_description),
diff --git a/src/engine/strat_engine/pool/v1.rs b/src/engine/strat_engine/pool/v1.rs
index 3d66415e82..ea1f8067f0 100644
--- a/src/engine/strat_engine/pool/v1.rs
+++ b/src/engine/strat_engine/pool/v1.rs
@@ -38,6 +38,7 @@ use crate::{
                 blockdev::{v1::StratBlockDev, InternalBlockDev},
                 ProcessedPathInfos,
             },
+            crypt::{CLEVIS_LUKS_TOKEN_ID, LUKS2_TOKEN_ID},
             liminal::DeviceSet,
             metadata::BDA,
             serde_structs::{FlexDevsSave, PoolSave, Recordable},
@@ -708,14 +709,14 @@ impl Pool for StratPool {
         token_slot: OptionalTokenSlotInput,
         pin: &str,
         clevis_info: &Value,
-    ) -> StratisResult<CreateAction<Clevis>> {
+    ) -> StratisResult<CreateAction<(Clevis, u32)>> {
         if token_slot != OptionalTokenSlotInput::Legacy {
             return Err(StratisError::Msg("Specifying the token slot for binding is not supported in V1 pools; please migrate to V2 pools to use this feature".to_string()));
         }
 
         let changed = self.backstore.bind_clevis(pin, clevis_info)?;
         if changed {
-            Ok(CreateAction::Created(Clevis))
+            Ok(CreateAction::Created((Clevis, CLEVIS_LUKS_TOKEN_ID)))
         } else {
             Ok(CreateAction::Identity)
         }
@@ -727,14 +728,14 @@ impl Pool for StratPool {
         &mut self,
         token_slot: OptionalTokenSlotInput,
         key_description: &KeyDescription,
-    ) -> StratisResult<CreateAction<Key>> {
+    ) -> StratisResult<CreateAction<(Key, u32)>> {
         if token_slot != OptionalTokenSlotInput::Legacy {
             return Err(StratisError::Msg("Specifying the token slot for binding is not supported in V1 pools; please migrate to V2 pools to use this feature".to_string()));
         }
 
         let changed = self.backstore.bind_keyring(key_description)?;
         if changed {
-            Ok(CreateAction::Created(Key))
+            Ok(CreateAction::Created((Key, LUKS2_TOKEN_ID)))
         } else {
             Ok(CreateAction::Identity)
         }
diff --git a/src/engine/strat_engine/pool/v2.rs b/src/engine/strat_engine/pool/v2.rs
index c0677a20c8..847ba98b49 100644
--- a/src/engine/strat_engine/pool/v2.rs
+++ b/src/engine/strat_engine/pool/v2.rs
@@ -649,12 +649,11 @@ impl Pool for StratPool {
         token_slot: OptionalTokenSlotInput,
         pin: &str,
         clevis_info: &Value,
-    ) -> StratisResult<CreateAction<Clevis>> {
+    ) -> StratisResult<CreateAction<(Clevis, u32)>> {
         let changed = self.backstore.bind_clevis(token_slot, pin, clevis_info)?;
-        if changed {
-            Ok(CreateAction::Created(Clevis))
-        } else {
-            Ok(CreateAction::Identity)
+        match changed {
+            Some(t) => Ok(CreateAction::Created((Clevis, t))),
+            None => Ok(CreateAction::Identity),
         }
     }
 
@@ -663,12 +662,11 @@ impl Pool for StratPool {
         &mut self,
         token_slot: OptionalTokenSlotInput,
         key_description: &KeyDescription,
-    ) -> StratisResult<CreateAction<Key>> {
+    ) -> StratisResult<CreateAction<(Key, u32)>> {
         let changed = self.backstore.bind_keyring(token_slot, key_description)?;
-        if changed {
-            Ok(CreateAction::Created(Key))
-        } else {
-            Ok(CreateAction::Identity)
+        match changed {
+            Some(t) => Ok(CreateAction::Created((Key, t))),
+            None => Ok(CreateAction::Identity),
         }
     }
 
diff --git a/src/engine/types/actions.rs b/src/engine/types/actions.rs
index dbd45cfcf2..5737d5949d 100644
--- a/src/engine/types/actions.rs
+++ b/src/engine/types/actions.rs
@@ -75,13 +75,13 @@ impl Display for CreateAction<PoolUuid> {
     }
 }
 
-impl Display for CreateAction<Clevis> {
+impl Display for CreateAction<(Clevis, u32)> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            CreateAction::Created(Clevis) => {
+            CreateAction::Created((_, t)) => {
                 write!(
                     f,
-                    "Pool successfully bound to an unlocking mechanism using clevis"
+                    "Pool successfully bound an unlocking mechanism to token slot {t} using clevis"
                 )
             }
             CreateAction::Identity => {
@@ -94,13 +94,13 @@ impl Display for CreateAction<Clevis> {
     }
 }
 
-impl Display for CreateAction<Key> {
+impl Display for CreateAction<(Key, u32)> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            CreateAction::Created(Key) => {
+            CreateAction::Created((_, t)) => {
                 write!(
                     f,
-                    "Pool successfully bound to a passphrase in the kernel keyring"
+                    "Pool successfully bound to a passphrase to token slot {t} in the kernel keyring"
                 )
             }
             CreateAction::Identity => {
diff --git a/src/engine/types/keys.rs b/src/engine/types/keys.rs
index 4421e2bbe9..b25402eb30 100644
--- a/src/engine/types/keys.rs
+++ b/src/engine/types/keys.rs
@@ -390,6 +390,23 @@ impl EncryptionInfo {
     pub fn is_empty(&self) -> bool {
         self.encryption_infos.is_empty()
     }
+
+    /// Returns the values that are in &self but not in &other.
+    pub fn diff(&self, other: &Self) -> Option<u32> {
+        self.encryption_infos
+            .keys()
+            .cloned()
+            .collect::<HashSet<_>>()
+            .difference(
+                &other
+                    .encryption_infos
+                    .keys()
+                    .cloned()
+                    .collect::<HashSet<_>>(),
+            )
+            .next()
+            .cloned()
+    }
 }
 
 impl IntoIterator for EncryptionInfo {