1use crate::plugins::Plugin;
2use crate::prelude::*;
3use std::sync::Arc;
45pub struct KeyObjectManagement {}
67impl Plugin for KeyObjectManagement {
8fn id() -> &'static str {
9"plugin_keyobject_management"
10}
1112#[instrument(
13 level = "debug",
14 name = "keyobject_management::pre_create_transform",
15 skip_all
16 )]
17fn pre_create_transform(
18 qs: &mut QueryServerWriteTransaction,
19 cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
20 _ce: &CreateEvent,
21 ) -> Result<(), OperationError> {
22Self::apply_keyobject_inner(qs, cand)
23 }
2425#[instrument(level = "debug", name = "keyobject_management::pre_modify", skip_all)]
26fn pre_modify(
27 qs: &mut QueryServerWriteTransaction,
28 _pre_cand: &[Arc<EntrySealedCommitted>],
29 cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
30 _me: &ModifyEvent,
31 ) -> Result<(), OperationError> {
32Self::apply_keyobject_inner(qs, cand)
33 }
3435#[instrument(
36 level = "debug",
37 name = "keyobject_management::pre_batch_modify",
38 skip_all
39 )]
40fn 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> {
46Self::apply_keyobject_inner(qs, cand)
47 }
4849/*
50 #[instrument(level = "debug", name = "keyobject_management::pre_delete", skip_all)]
51 fn pre_delete(
52 _qs: &mut QueryServerWriteTransaction,
53 // Should these be EntrySealed
54 _cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
55 _de: &DeleteEvent,
56 ) -> Result<(), OperationError> {
57 Ok(())
58 }
59 */
6061#[instrument(level = "debug", name = "keyobject_management::verify", skip_all)]
62fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
63let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
6465let key_providers = match qs
66 .internal_search(filt_in)
67 .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
68 {
69Ok(all_cand) => all_cand,
70Err(e) => return vec![e],
71 };
7273// Put the providers into a map by uuid.
74let key_providers: hashbrown::HashSet<_> = key_providers
75 .into_iter()
76 .map(|entry| entry.get_uuid())
77 .collect();
7879let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
8081let key_objects = match qs
82 .internal_search(filt_in)
83 .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
84 {
85Ok(all_cand) => all_cand,
86Err(e) => return vec![e],
87 };
8889 key_objects
90 .into_iter()
91 .filter_map(|key_object_entry| {
92let object_uuid = key_object_entry.get_uuid();
9394// Each key objects must relate to a provider.
95let Some(provider_uuid) =
96 key_object_entry.get_ava_single_refer(Attribute::KeyProvider)
97else {
98error!(?object_uuid, "Invalid key object, no key provider uuid.");
99return Some(ConsistencyError::KeyProviderUuidMissing {
100 key_object: object_uuid,
101 });
102 };
103104if !key_providers.contains(&provider_uuid) {
105error!(
106?object_uuid,
107?provider_uuid,
108"Invalid key object, key provider referenced is not found."
109);
110return Some(ConsistencyError::KeyProviderNotFound {
111 key_object: object_uuid,
112 provider: provider_uuid,
113 });
114 }
115116// Every key object needs at least *one* key it stores.
117let has_jwt_es256 = key_object_entry
118 .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into());
119120let has_jwt_rs256 = key_object_entry
121 .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtRs256.into());
122123let has_hkdf_s256 = key_object_entry
124 .attribute_equality(Attribute::Class, &EntryClass::KeyObjectHkdfS256.into());
125126let has_key = has_jwt_es256 || has_jwt_rs256 || has_hkdf_s256;
127128if !has_key {
129error!(?object_uuid, "Invalid key object, contains no keys.");
130return Some(ConsistencyError::KeyProviderNoKeys {
131 key_object: object_uuid,
132 });
133 }
134135None
136})
137 .map(Err)
138 .collect::<Vec<_>>()
139 }
140}
141142impl KeyObjectManagement {
143fn apply_keyobject_inner<T: Clone>(
144 qs: &mut QueryServerWriteTransaction,
145 cand: &mut [Entry<EntryInvalid, T>],
146 ) -> Result<(), OperationError> {
147// New keys will be valid from right meow!
148let valid_from = qs.get_curtime();
149let txn_cid = qs.get_cid().clone();
150let key_providers = qs.get_key_providers_mut();
151// ====================================================================
152 // Transform any found KeyObjects and manage any related key operations
153 // for them
154155cand.iter_mut()
156 .filter(|entry| {
157 entry.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
158 })
159 .try_for_each(|entry| {
160// The entry should not have set any type of KeyObject at this point.
161 // Should we force delete those attrs here just in case?
162entry.remove_ava(Attribute::Class, &EntryClass::KeyObjectInternal.into());
163164// Must be set by now.
165let key_object_uuid = entry
166 .get_uuid()
167 .ok_or(OperationError::KP0008KeyObjectMissingUuid)?;
168169trace!(?key_object_uuid, "Setting up key object");
170171// Get the default provider, and create a new ephemeral key object
172 // inside it. If the object existed already, we clone it so that we can stage
173 // our changes.
174let mut key_object = key_providers.get_or_create_in_default(key_object_uuid)?;
175176// Import any keys that we were asked to import. This is before revocation so that
177 // any keyId here might also be able to be revoked.
178let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsEs256);
179if 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 }
185186let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsRs256);
187if 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 }
193194// If revoke. This weird looking let dance is to ensure that the inner hexstring set
195 // lives long enough.
196let maybe_revoked = entry.pop_ava(Attribute::KeyActionRevoke);
197if 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 }
202203// Rotation is after revocation, but before assertion. This way if the user
204 // asked for rotation and revocation, we don't double rotate when we get to
205 // the assert phase. We also only get a rotation time if the time is in the
206 // future, to avoid rotating keys in the past.
207if let Some(rotation_time) = entry
208 .pop_ava(Attribute::KeyActionRotate)
209 .and_then(|vs| vs.to_datetime_single())
210 .map(|odt| {
211let secs = odt.unix_timestamp() as u64;
212if secs > valid_from.as_secs() {
213 Duration::from_secs(secs)
214 } else {
215 valid_from
216 }
217 })
218 {
219debug!(?rotation_time, "initiate key rotation");
220 key_object.rotate_keys(rotation_time, &txn_cid)?;
221 }
222223if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectHkdfS256.into())
224 {
225 key_object.hkdf_s256_assert(Duration::ZERO, &txn_cid)?;
226 }
227228if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into())
229 {
230// Assert that this object has a valid es256 key present. Post revoke, it may NOT
231 // be present. This differs to rotate, in that the assert verifies we have at least
232 // *one* key that is valid in all conditions.
233key_object.jws_es256_assert(Duration::ZERO, &txn_cid)?;
234 }
235236if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtRs256.into())
237 {
238 key_object.jws_rs256_assert(Duration::ZERO, &txn_cid)?;
239 }
240241if entry
242 .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJweA128GCM.into())
243 {
244 key_object.jwe_a128gcm_assert(Duration::ZERO, &txn_cid)?;
245 }
246247// Turn that object into it's entry template to create. I think we need to make this
248 // some kind of merge_vs?
249key_object
250 .as_valuesets()?
251.into_iter()
252 .try_for_each(|(attribute, valueset)| {
253 entry.merge_ava_set(&attribute, valueset)
254 })?;
255256Ok(())
257 })
258 }
259}
260261// Unlike other plugins, tests for this plugin will be located in server/lib/src/server/keys.
262//
263// The reason is because we can preconfigure different providers to test these paths in future.