kanidmd_lib/plugins/
eckeygen.rs

1use openssl::ec::{EcGroup, EcKey};
2use openssl::nid::Nid;
3
4use crate::prelude::*;
5use std::sync::Arc;
6
7use super::Plugin;
8
9lazy_static! {
10    // it contains all the partialvalues used to match against an Entry's class,
11    // we need ALL partialvalues to match in order to target the entry
12    static ref DEFAULT_KEY_GROUP: EcGroup = {
13        let nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
14        #[allow(clippy::unwrap_used)]
15        EcGroup::from_curve_name(nid).unwrap()
16    };
17}
18
19pub struct EcdhKeyGen {}
20
21impl EcdhKeyGen {
22    // we optionally provide a target_cand to update only the entry with the given uuid
23    fn generate_key<STATE: Clone>(
24        cands: &mut [Entry<EntryInvalid, STATE>],
25    ) -> Result<(), OperationError> {
26        for cand in cands.iter_mut() {
27            if cand.attribute_equality(Attribute::Class, &EntryClass::Person.to_partialvalue())
28                && !cand.attribute_pres(Attribute::IdVerificationEcKey)
29            {
30                debug!(
31                    "Generating {} for {}",
32                    Attribute::IdVerificationEcKey,
33                    cand.get_display_id()
34                );
35
36                let new_private_key = EcKey::generate(&DEFAULT_KEY_GROUP).map_err(|e| {
37                    error!(err = ?e, "Unable to generate id verification ECDH private key");
38                    OperationError::CryptographyError
39                })?;
40                cand.add_ava_if_not_exist(
41                    Attribute::IdVerificationEcKey,
42                    crate::value::Value::EcKeyPrivate(new_private_key),
43                )
44            }
45        }
46        Ok(())
47    }
48}
49
50impl Plugin for EcdhKeyGen {
51    fn id() -> &'static str {
52        "plugin_ecdhkey_gen"
53    }
54
55    #[instrument(level = "debug", name = "ecdhkeygen::pre_create_transform", skip_all)]
56    fn pre_create_transform(
57        _qs: &mut QueryServerWriteTransaction,
58        cand: &mut Vec<EntryInvalidNew>,
59        _ce: &CreateEvent,
60    ) -> Result<(), OperationError> {
61        Self::generate_key(cand)
62    }
63
64    #[instrument(level = "debug", name = "ecdhkeygen::pre_modify", skip_all)]
65    fn pre_modify(
66        _qs: &mut QueryServerWriteTransaction,
67        _pre_cand: &[Arc<EntrySealedCommitted>],
68        cand: &mut Vec<EntryInvalidCommitted>,
69        _me: &ModifyEvent,
70    ) -> Result<(), OperationError> {
71        Self::generate_key(cand)
72    }
73
74    #[instrument(level = "debug", name = "ecdhkeygen::pre_batch_modify", skip_all)]
75    fn pre_batch_modify(
76        _qs: &mut QueryServerWriteTransaction,
77        _pre_cand: &[Arc<EntrySealedCommitted>],
78        cand: &mut Vec<EntryInvalidCommitted>,
79        _me: &BatchModifyEvent,
80    ) -> Result<(), OperationError> {
81        Self::generate_key(cand)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use openssl::ec::EcKey;
88    use uuid::Uuid;
89
90    use super::DEFAULT_KEY_GROUP;
91    use crate::prelude::*;
92    use crate::value::Value;
93    use crate::valueset;
94
95    #[test]
96    fn test_new_user_generate_key() {
97        let uuid = Uuid::new_v4();
98        let ea = entry_init!(
99            (Attribute::Class, EntryClass::Account.to_value()),
100            (Attribute::Class, EntryClass::Person.to_value()),
101            (Attribute::Class, EntryClass::Object.to_value()),
102            (Attribute::Name, Value::new_iname("test_name")),
103            (Attribute::Uuid, Value::Uuid(uuid)),
104            (Attribute::Description, Value::new_utf8s("testperson")),
105            (Attribute::DisplayName, Value::new_utf8s("Test Person"))
106        );
107        let preload: Vec<Entry<EntryInit, EntryNew>> = Vec::with_capacity(0);
108
109        let create = vec![ea];
110        run_create_test!(
111            Ok(()),
112            preload,
113            create,
114            None,
115            |qs: &mut QueryServerWriteTransaction| {
116                let e = qs.internal_search_uuid(uuid).expect("failed to get entry");
117
118                let key = e
119                    .get_ava_single_eckey_private(Attribute::IdVerificationEcKey)
120                    .expect("unable to retrieve the ecdh key");
121
122                assert!(key.check_key().is_ok())
123            }
124        );
125    }
126
127    /*
128    // Invalid, can't be set due to no impl from clone_value
129
130    #[test]
131    fn test_modify_present_ecdkey() {
132        let ea = entry_init!(
133            (Attribute::Class, EntryClass::Account.to_value()),
134            (Attribute::Class, EntryClass::Person.to_value()),
135            (Attribute::Class, EntryClass::Object.to_value()),
136            (Attribute::Name, Value::new_iname("test_name")),
137            (Attribute::Description, Value::new_utf8s("testperson")),
138            (Attribute::DisplayName, Value::new_utf8s("Test person!"))
139        );
140        let preload = vec![ea];
141        let new_private_key = EcKey::generate(&DEFAULT_KEY_GROUP).unwrap();
142        run_modify_test!(
143            Err(OperationError::SystemProtectedAttribute),
144            preload,
145            filter!(f_eq(Attribute::Name, PartialValue::new_iname("test_name"))),
146            modlist!([m_pres(
147                Attribute::IdVerificationEcKey.into(),
148                &Value::EcKeyPrivate(new_private_key)
149            )]),
150            None,
151            |_| {},
152            |_| {}
153        );
154    }
155    */
156
157    #[test]
158    fn test_modify_purge_eckey() {
159        let private_key = EcKey::generate(&DEFAULT_KEY_GROUP).unwrap();
160        let private_key_value = Value::EcKeyPrivate(private_key.clone());
161
162        let uuid = Uuid::new_v4();
163
164        let ea = entry_init!(
165            (Attribute::Class, EntryClass::Account.to_value()),
166            (Attribute::Class, EntryClass::Person.to_value()),
167            (Attribute::Class, EntryClass::Object.to_value()),
168            (Attribute::Name, Value::new_iname("test_name")),
169            (Attribute::Uuid, Value::Uuid(uuid)),
170            (Attribute::IdVerificationEcKey, private_key_value.clone()),
171            (Attribute::Description, Value::new_utf8s("testperson")),
172            (Attribute::DisplayName, Value::new_utf8s("Test person!"))
173        );
174        let key_partialvalue = valueset::from_value_iter(std::iter::once(private_key_value))
175            .unwrap()
176            .to_partialvalue_iter()
177            .next()
178            .unwrap();
179        let preload = vec![ea];
180        run_modify_test!(
181            Ok(()),
182            preload,
183            filter!(f_eq(Attribute::Name, PartialValue::new_iname("test_name"))),
184            modlist!([m_purge(Attribute::IdVerificationEcKey)]),
185            None,
186            |_| {},
187            |qs: &mut QueryServerWriteTransaction| {
188                let e = qs.internal_search_uuid(uuid).expect("failed to get entry");
189
190                assert!(
191                    !e.attribute_equality(Attribute::IdVerificationEcKey, &key_partialvalue)
192                        && e.attribute_pres(Attribute::IdVerificationEcKey)
193                )
194            }
195        );
196    }
197    #[test]
198    fn test_modify_remove_eckey() {
199        let private_key = EcKey::generate(&DEFAULT_KEY_GROUP).unwrap();
200        let private_key_value = Value::EcKeyPrivate(private_key.clone());
201
202        let uuid = Uuid::new_v4();
203
204        let ea = entry_init!(
205            (Attribute::Class, EntryClass::Account.to_value()),
206            (Attribute::Class, EntryClass::Person.to_value()),
207            (Attribute::Class, EntryClass::Object.to_value()),
208            (Attribute::Name, Value::new_iname("test_name")),
209            (Attribute::Uuid, Value::Uuid(uuid)),
210            (Attribute::IdVerificationEcKey, private_key_value.clone()),
211            (Attribute::Description, Value::new_utf8s("testperson")),
212            (Attribute::DisplayName, Value::new_utf8s("Test person!"))
213        );
214        let key_partialvalue = valueset::from_value_iter(std::iter::once(private_key_value))
215            .unwrap()
216            .to_partialvalue_iter()
217            .next()
218            .unwrap();
219        let preload = vec![ea];
220        run_modify_test!(
221            Ok(()),
222            preload,
223            filter!(f_eq(Attribute::Name, PartialValue::new_iname("test_name"))),
224            modlist!([m_remove(Attribute::IdVerificationEcKey, &key_partialvalue)]),
225            None,
226            |_| {},
227            |qs: &mut QueryServerWriteTransaction| {
228                let e = qs.internal_search_uuid(uuid).expect("failed to get entry");
229
230                assert!(
231                    !e.attribute_equality(Attribute::IdVerificationEcKey, &key_partialvalue)
232                        && e.attribute_pres(Attribute::IdVerificationEcKey)
233                )
234            }
235        );
236    }
237}