kanidmd_lib/plugins/
keyobject.rs1use crate::plugins::Plugin;
2use crate::prelude::*;
3use std::sync::Arc;
4
5pub struct KeyObjectManagement {}
6
7impl Plugin for KeyObjectManagement {
8    fn id() -> &'static str {
9        "plugin_keyobject_management"
10    }
11
12    #[instrument(
13        level = "debug",
14        name = "keyobject_management::pre_create_transform",
15        skip_all
16    )]
17    fn pre_create_transform(
18        qs: &mut QueryServerWriteTransaction,
19        cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
20        _ce: &CreateEvent,
21    ) -> Result<(), OperationError> {
22        Self::apply_keyobject_inner(qs, cand)
23    }
24
25    #[instrument(level = "debug", name = "keyobject_management::pre_modify", skip_all)]
26    fn pre_modify(
27        qs: &mut QueryServerWriteTransaction,
28        _pre_cand: &[Arc<EntrySealedCommitted>],
29        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
30        _me: &ModifyEvent,
31    ) -> Result<(), OperationError> {
32        Self::apply_keyobject_inner(qs, cand)
33    }
34
35    #[instrument(
36        level = "debug",
37        name = "keyobject_management::pre_batch_modify",
38        skip_all
39    )]
40    fn pre_batch_modify(
41        qs: &mut QueryServerWriteTransaction,
42        _pre_cand: &[Arc<EntrySealedCommitted>],
43        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
44        _me: &BatchModifyEvent,
45    ) -> Result<(), OperationError> {
46        Self::apply_keyobject_inner(qs, cand)
47    }
48
49    #[instrument(level = "debug", name = "keyobject_management::verify", skip_all)]
62    fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
63        let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
64
65        let key_providers = match qs
66            .internal_search(filt_in)
67            .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
68        {
69            Ok(all_cand) => all_cand,
70            Err(e) => return vec![e],
71        };
72
73        let key_providers: hashbrown::HashSet<_> = key_providers
75            .into_iter()
76            .map(|entry| entry.get_uuid())
77            .collect();
78
79        let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
80
81        let key_objects = match qs
82            .internal_search(filt_in)
83            .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
84        {
85            Ok(all_cand) => all_cand,
86            Err(e) => return vec![e],
87        };
88
89        key_objects
90            .into_iter()
91            .filter_map(|key_object_entry| {
92                let object_uuid = key_object_entry.get_uuid();
93
94                let Some(provider_uuid) =
96                    key_object_entry.get_ava_single_refer(Attribute::KeyProvider)
97                else {
98                    error!(?object_uuid, "Invalid key object, no key provider uuid.");
99                    return Some(ConsistencyError::KeyProviderUuidMissing {
100                        key_object: object_uuid,
101                    });
102                };
103
104                if !key_providers.contains(&provider_uuid) {
105                    error!(
106                        ?object_uuid,
107                        ?provider_uuid,
108                        "Invalid key object, key provider referenced is not found."
109                    );
110                    return Some(ConsistencyError::KeyProviderNotFound {
111                        key_object: object_uuid,
112                        provider: provider_uuid,
113                    });
114                }
115
116                let has_jwt_es256 = key_object_entry
118                    .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into());
119
120                let has_jwt_rs256 = key_object_entry
121                    .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtRs256.into());
122
123                let has_hkdf_s256 = key_object_entry
124                    .attribute_equality(Attribute::Class, &EntryClass::KeyObjectHkdfS256.into());
125
126                let has_key = has_jwt_es256 || has_jwt_rs256 || has_hkdf_s256;
127
128                if !has_key {
129                    error!(?object_uuid, "Invalid key object, contains no keys.");
130                    return Some(ConsistencyError::KeyProviderNoKeys {
131                        key_object: object_uuid,
132                    });
133                }
134
135                None
136            })
137            .map(Err)
138            .collect::<Vec<_>>()
139    }
140}
141
142impl KeyObjectManagement {
143    fn apply_keyobject_inner<T: Clone>(
144        qs: &mut QueryServerWriteTransaction,
145        cand: &mut [Entry<EntryInvalid, T>],
146    ) -> Result<(), OperationError> {
147        let valid_from = qs.get_curtime();
149        let txn_cid = qs.get_cid().clone();
150        let key_providers = qs.get_key_providers_mut();
151        cand.iter_mut()
156            .filter(|entry| {
157                entry.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
158            })
159            .try_for_each(|entry| {
160                entry.remove_ava(Attribute::Class, &EntryClass::KeyObjectInternal.into());
163
164                let key_object_uuid = entry
166                    .get_uuid()
167                    .ok_or(OperationError::KP0008KeyObjectMissingUuid)?;
168
169                trace!(?key_object_uuid, "Setting up key object");
170
171                let mut key_object = key_providers.get_or_create_in_default(key_object_uuid)?;
175
176                let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsEs256);
179                if let Some(import_keys) = maybe_import
180                    .as_ref()
181                    .and_then(|vs| vs.as_private_binary_set())
182                {
183                    key_object.jws_es256_import(import_keys, valid_from, &txn_cid)?;
184                }
185
186                let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsRs256);
187                if let Some(import_keys) = maybe_import
188                    .as_ref()
189                    .and_then(|vs| vs.as_private_binary_set())
190                {
191                    key_object.jws_rs256_import(import_keys, valid_from, &txn_cid)?;
192                }
193
194                let maybe_revoked = entry.pop_ava(Attribute::KeyActionRevoke);
197                if let Some(revoke_keys) =
198                    maybe_revoked.as_ref().and_then(|vs| vs.as_hexstring_set())
199                {
200                    key_object.revoke_keys(revoke_keys, &txn_cid)?;
201                }
202
203                if let Some(rotation_time) = entry
208                    .pop_ava(Attribute::KeyActionRotate)
209                    .and_then(|vs| vs.to_datetime_single())
210                    .map(|odt| {
211                        let secs = odt.unix_timestamp() as u64;
212                        if secs > valid_from.as_secs() {
213                            Duration::from_secs(secs)
214                        } else {
215                            valid_from
216                        }
217                    })
218                {
219                    debug!(?rotation_time, "initiate key rotation");
220                    key_object.rotate_keys(rotation_time, &txn_cid)?;
221                }
222
223                if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectHkdfS256.into())
224                {
225                    key_object.hkdf_s256_assert(Duration::ZERO, &txn_cid)?;
226                }
227
228                if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into())
229                {
230                    key_object.jws_es256_assert(Duration::ZERO, &txn_cid)?;
234                }
235
236                if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtRs256.into())
237                {
238                    key_object.jws_rs256_assert(Duration::ZERO, &txn_cid)?;
239                }
240
241                if entry
242                    .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJweA128GCM.into())
243                {
244                    key_object.jwe_a128gcm_assert(Duration::ZERO, &txn_cid)?;
245                }
246
247                key_object
250                    .as_valuesets()?
251                    .into_iter()
252                    .try_for_each(|(attribute, valueset)| {
253                        entry.merge_ava_set(&attribute, valueset)
254                    })?;
255
256                Ok(())
257            })
258    }
259}
260
261