kanidmd_lib/plugins/
keyobject.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
use crate::plugins::Plugin;
use crate::prelude::*;
use std::sync::Arc;

pub struct KeyObjectManagement {}

impl Plugin for KeyObjectManagement {
    fn id() -> &'static str {
        "plugin_keyobject_management"
    }

    #[instrument(
        level = "debug",
        name = "keyobject_management::pre_create_transform",
        skip_all
    )]
    fn pre_create_transform(
        qs: &mut QueryServerWriteTransaction,
        cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
        _ce: &CreateEvent,
    ) -> Result<(), OperationError> {
        Self::apply_keyobject_inner(qs, cand)
    }

    #[instrument(level = "debug", name = "keyobject_management::pre_modify", skip_all)]
    fn pre_modify(
        qs: &mut QueryServerWriteTransaction,
        _pre_cand: &[Arc<EntrySealedCommitted>],
        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
        _me: &ModifyEvent,
    ) -> Result<(), OperationError> {
        Self::apply_keyobject_inner(qs, cand)
    }

    #[instrument(
        level = "debug",
        name = "keyobject_management::pre_batch_modify",
        skip_all
    )]
    fn pre_batch_modify(
        qs: &mut QueryServerWriteTransaction,
        _pre_cand: &[Arc<EntrySealedCommitted>],
        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
        _me: &BatchModifyEvent,
    ) -> Result<(), OperationError> {
        Self::apply_keyobject_inner(qs, cand)
    }

    /*
    #[instrument(level = "debug", name = "keyobject_management::pre_delete", skip_all)]
    fn pre_delete(
        _qs: &mut QueryServerWriteTransaction,
        // Should these be EntrySealed
        _cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
        _de: &DeleteEvent,
    ) -> Result<(), OperationError> {
        Ok(())
    }
    */

    #[instrument(level = "debug", name = "keyobject_management::verify", skip_all)]
    fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
        let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));

        let key_providers = match qs
            .internal_search(filt_in)
            .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
        {
            Ok(all_cand) => all_cand,
            Err(e) => return vec![e],
        };

        // Put the providers into a map by uuid.
        let key_providers: hashbrown::HashSet<_> = key_providers
            .into_iter()
            .map(|entry| entry.get_uuid())
            .collect();

        let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));

        let key_objects = match qs
            .internal_search(filt_in)
            .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
        {
            Ok(all_cand) => all_cand,
            Err(e) => return vec![e],
        };

        key_objects
            .into_iter()
            .filter_map(|key_object_entry| {
                let object_uuid = key_object_entry.get_uuid();

                // Each key objects must relate to a provider.
                let Some(provider_uuid) =
                    key_object_entry.get_ava_single_refer(Attribute::KeyProvider)
                else {
                    error!(?object_uuid, "Invalid key object, no key provider uuid.");
                    return Some(ConsistencyError::KeyProviderUuidMissing {
                        key_object: object_uuid,
                    });
                };

                if !key_providers.contains(&provider_uuid) {
                    error!(
                        ?object_uuid,
                        ?provider_uuid,
                        "Invalid key object, key provider referenced is not found."
                    );
                    return Some(ConsistencyError::KeyProviderNotFound {
                        key_object: object_uuid,
                        provider: provider_uuid,
                    });
                }

                // Every key object needs at least *one* key it stores.
                if !key_object_entry
                    .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into())
                {
                    error!(?object_uuid, "Invalid key object, contains no keys.");
                    return Some(ConsistencyError::KeyProviderNoKeys {
                        key_object: object_uuid,
                    });
                }

                None
            })
            .map(Err)
            .collect::<Vec<_>>()
    }
}

impl KeyObjectManagement {
    fn apply_keyobject_inner<T: Clone>(
        qs: &mut QueryServerWriteTransaction,
        cand: &mut [Entry<EntryInvalid, T>],
    ) -> Result<(), OperationError> {
        // Valid from right meow!
        let valid_from = qs.get_curtime();
        let txn_cid = qs.get_cid().clone();

        let key_providers = qs.get_key_providers_mut();

        cand.iter_mut()
            .filter(|entry| {
                entry.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
            })
            .try_for_each(|entry| {
                // The entry should not have set any type of KeyObject at this point.
                // Should we force delete those attrs here just incase?
                entry.remove_ava(Attribute::Class, &EntryClass::KeyObjectInternal.into());

                // Must be set by now.
                let key_object_uuid = entry
                    .get_uuid()
                    .ok_or(OperationError::KP0008KeyObjectMissingUuid)?;

                trace!(?key_object_uuid, "Setting up key object");

                // Get the default provider, and create a new ephemeral key object
                // inside it. If the object existed already, we clone it so that we can stage
                // our changes.
                let mut key_object = key_providers.get_or_create_in_default(key_object_uuid)?;

                // Import any keys that we were asked to import. This is before revocation so that
                // any keyId here might also be able to be revoked.
                let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsEs256);
                if let Some(import_keys) = maybe_import
                    .as_ref()
                    .and_then(|vs| vs.as_private_binary_set())
                {
                    key_object.jws_es256_import(import_keys, valid_from, &txn_cid)?;
                }

                // If revoke. This weird looking let dance is to ensure that the inner hexstring set
                // lives long enough.
                let maybe_revoked = entry.pop_ava(Attribute::KeyActionRevoke);
                if let Some(revoke_keys) =
                    maybe_revoked.as_ref().and_then(|vs| vs.as_hexstring_set())
                {
                    key_object.revoke_keys(revoke_keys, &txn_cid)?;
                }

                // Rotation is after revocation, but before assertion. This way if the user
                // asked for rotation and revocation, we don't double rotate when we get to
                // the assert phase. We also only get a rotation time if the time is in the
                // future, to avoid rotating keys in the past.
                if let Some(rotation_time) = entry
                    .pop_ava(Attribute::KeyActionRotate)
                    .and_then(|vs| vs.to_datetime_single())
                    .and_then(|odt| {
                        let secs = odt.unix_timestamp() as u64;
                        if secs > valid_from.as_secs() {
                            Some(Duration::from_secs(secs))
                        } else {
                            None
                        }
                    })
                {
                    key_object.rotate_keys(rotation_time, &txn_cid)?;
                }

                if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into())
                {
                    // Assert that this object has a valid es256 key present. Post revoke, it may NOT
                    // be present. This differs to rotate, in that the assert verifes we have at least
                    // *one* key that is valid in all conditions.
                    key_object.jws_es256_assert(Duration::ZERO, &txn_cid)?;
                }

                if entry
                    .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJweA128GCM.into())
                {
                    key_object.jwe_a128gcm_assert(Duration::ZERO, &txn_cid)?;
                }

                // Turn that object into it's entry template to create. I think we need to make this
                // some kind of merge_vs?
                key_object
                    .as_valuesets()?
                    .into_iter()
                    .try_for_each(|(attribute, valueset)| {
                        entry.merge_ava_set(&attribute, valueset)
                    })?;

                Ok(())
            })
    }
}

// Unlike other plugins, tests for this plugin will be located in server/lib/src/server/keys.
//
// The reason is because we can preconfigure different providers to test these paths in future.