kanidmd_lib/idm/
identityverification.rs

1use crate::credential::totp::{Totp, TotpAlgo, TotpDigits};
2use crate::idm::server::IdmServerProxyReadTransaction;
3use crate::prelude::*;
4use crate::server::identity::Identity;
5use crate::server::QueryServerTransaction;
6use kanidm_proto::internal::IdentifyUserResponse;
7use openssl::ec::EcKey;
8use openssl::pkey::{PKey, Private, Public};
9use openssl::pkey_ctx::PkeyCtx;
10use std::sync::Arc;
11use uuid::Uuid;
12
13// This is longer than a normal TOTP step as we expect users to be talking
14// to each other, so it could take a few minutes.
15static TOTP_STEP: u64 = 300;
16
17#[derive(Debug)]
18pub struct IdentifyUserStartEvent {
19    pub target: Uuid,
20    pub ident: Identity,
21}
22
23impl IdentifyUserStartEvent {
24    pub fn new(target: Uuid, ident: Identity) -> Self {
25        IdentifyUserStartEvent { target, ident }
26    }
27}
28pub struct IdentifyUserDisplayCodeEvent {
29    pub target: Uuid,
30    pub ident: Identity,
31}
32
33impl IdentifyUserDisplayCodeEvent {
34    pub fn new(target: Uuid, ident: Identity) -> Self {
35        IdentifyUserDisplayCodeEvent { target, ident }
36    }
37}
38
39pub struct IdentifyUserSubmitCodeEvent {
40    pub code: u32,
41    pub target: Uuid,
42    pub ident: Identity,
43}
44
45impl IdentifyUserSubmitCodeEvent {
46    pub fn new(target: Uuid, ident: Identity, code: u32) -> Self {
47        IdentifyUserSubmitCodeEvent {
48            target,
49            ident,
50            code,
51        }
52    }
53}
54
55impl IdmServerProxyReadTransaction<'_> {
56    pub fn handle_identify_user_start(
57        &mut self,
58        IdentifyUserStartEvent { target, ident }: &IdentifyUserStartEvent,
59        current_time: Duration,
60    ) -> Result<IdentifyUserResponse, OperationError> {
61        let (ident_entry, target_entry) = match self.get_involved_entries(ident, *target) {
62            Ok(tuple) => tuple,
63            Err(early_response) => return Ok(early_response),
64        };
65
66        let response = if ident_entry.get_uuid() < target_entry.get_uuid() {
67            IdentifyUserResponse::WaitForCode
68        } else {
69            let totp_secret = self.get_self_totp(&ident_entry, &target_entry)?;
70
71            let totp_value = totp_secret
72                .do_totp_duration_from_epoch(&current_time)
73                .map_err(|_| OperationError::CryptographyError)?;
74
75            IdentifyUserResponse::ProvideCode {
76                step: TOTP_STEP as u32,
77                totp: totp_value,
78            }
79        };
80        Ok(response)
81    }
82
83    pub fn handle_identify_user_display_code(
84        &mut self,
85        IdentifyUserDisplayCodeEvent { target, ident }: &IdentifyUserDisplayCodeEvent,
86        current_time: Duration,
87    ) -> Result<IdentifyUserResponse, OperationError> {
88        let (ident_entry, target_entry) = match self.get_involved_entries(ident, *target) {
89            Ok(tuple) => tuple,
90            Err(early_response) => return Ok(early_response),
91        };
92
93        let totp_secret = self.get_self_totp(&ident_entry, &target_entry)?;
94
95        let totp_value = totp_secret
96            .do_totp_duration_from_epoch(&current_time)
97            .map_err(|_| OperationError::CryptographyError)?;
98
99        Ok(IdentifyUserResponse::ProvideCode {
100            step: TOTP_STEP as u32,
101            totp: totp_value,
102        })
103    }
104
105    pub fn handle_identify_user_submit_code(
106        &mut self,
107        IdentifyUserSubmitCodeEvent {
108            target,
109            ident,
110            code,
111        }: &IdentifyUserSubmitCodeEvent,
112        current_time: Duration,
113    ) -> Result<IdentifyUserResponse, OperationError> {
114        let (ident_entry, target_entry) = match self.get_involved_entries(ident, *target) {
115            Ok(tuple) => tuple,
116            Err(early_response) => return Ok(early_response),
117        };
118
119        let totp_secret = self.get_user_totp(&ident_entry, &target_entry)?;
120
121        if !totp_secret.verify(*code, current_time) {
122            return Ok(IdentifyUserResponse::CodeFailure);
123        }
124
125        // if we are the first it means now it's time to go for ProvideCode, otherwise we just confirm that the code is correct
126        // (we know this for a fact as we have already checked that the code is correct)
127        let response = if ident_entry.get_uuid() < target_entry.get_uuid() {
128            let totp_secret = self.get_self_totp(&ident_entry, &target_entry)?;
129            let totp_value = totp_secret
130                .do_totp_duration_from_epoch(&current_time)
131                .map_err(|_| OperationError::CryptographyError)?;
132            IdentifyUserResponse::ProvideCode {
133                step: TOTP_STEP as u32,
134                totp: totp_value,
135            }
136        } else {
137            IdentifyUserResponse::Success
138        };
139        Ok(response)
140    }
141
142    // End of public functions
143
144    fn get_involved_entries(
145        &mut self,
146        ident: &Identity,
147        target: Uuid,
148    ) -> Result<(Arc<EntrySealedCommitted>, Arc<EntrySealedCommitted>), IdentifyUserResponse> {
149        let Some(ident_entry) = ident.get_user_entry() else {
150            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
151        };
152
153        let Ok(target_entry) = self.get_partner_entry(target) else {
154            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
155        };
156
157        if ident_entry.get_uuid() == target_entry.get_uuid() {
158            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
159        }
160
161        if target_entry
162            .get_ava_single_eckey_public(Attribute::IdVerificationEcKey)
163            .is_none()
164        {
165            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
166        }
167
168        if ident_entry
169            .get_ava_single_eckey_private(Attribute::IdVerificationEcKey)
170            .is_none()
171        {
172            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
173        }
174
175        Ok((ident_entry, target_entry))
176    }
177
178    fn get_partner_entry(
179        &mut self,
180        target: Uuid,
181    ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
182        self.qs_read
183            .internal_search_uuid(target)
184            .inspect_err(|err| error!(?err, ?target, "Failed to retrieve entry",))
185    }
186
187    fn get_user_own_key(
188        &mut self,
189        ident_entry: &EntrySealedCommitted,
190    ) -> Result<EcKey<Private>, OperationError> {
191        ident_entry
192            .get_ava_single_eckey_private(Attribute::IdVerificationEcKey)
193            .cloned()
194            .ok_or(OperationError::AU0001InvalidState)
195    }
196
197    fn get_user_public_key(
198        &mut self,
199        target_entry: &EntrySealedCommitted,
200    ) -> Result<EcKey<Public>, OperationError> {
201        target_entry
202            .get_ava_single_eckey_public(Attribute::IdVerificationEcKey)
203            .cloned()
204            .ok_or(OperationError::AU0001InvalidState)
205    }
206
207    fn get_self_totp(
208        &mut self,
209        ident_entry: &EntrySealedCommitted,
210        target_entry: &EntrySealedCommitted,
211    ) -> Result<Totp, OperationError> {
212        let self_private = self.get_user_own_key(ident_entry)?;
213        let other_user_public_key = self.get_user_public_key(target_entry)?;
214        let mut shared_key = self.derive_shared_key(self_private, other_user_public_key)?;
215        shared_key.extend_from_slice(ident_entry.get_uuid().as_bytes());
216        let totp = Totp::new(shared_key, TOTP_STEP, TotpAlgo::Sha256, TotpDigits::Six);
217        Ok(totp)
218    }
219
220    fn get_user_totp(
221        &mut self,
222        ident_entry: &EntrySealedCommitted,
223        target_entry: &EntrySealedCommitted,
224    ) -> Result<Totp, OperationError> {
225        let self_private = self.get_user_own_key(ident_entry)?;
226        let other_user_public_key = self.get_user_public_key(target_entry)?;
227        let mut shared_key = self.derive_shared_key(self_private, other_user_public_key)?;
228        shared_key.extend_from_slice(target_entry.get_uuid().as_bytes());
229        let totp = Totp::new(shared_key, TOTP_STEP, TotpAlgo::Sha256, TotpDigits::Six);
230        Ok(totp)
231    }
232
233    fn derive_shared_key(
234        &self,
235        private: EcKey<Private>,
236        public: EcKey<Public>,
237    ) -> Result<Vec<u8>, OperationError> {
238        let cryptography_error = |_| OperationError::CryptographyError;
239        let pkey_private = PKey::from_ec_key(private).map_err(cryptography_error)?;
240        let pkey_public = PKey::from_ec_key(public).map_err(cryptography_error)?;
241
242        let mut private_key_ctx: PkeyCtx<Private> =
243            PkeyCtx::new(&pkey_private).map_err(cryptography_error)?;
244        private_key_ctx.derive_init().map_err(cryptography_error)?;
245        private_key_ctx
246            .derive_set_peer(&pkey_public)
247            .map_err(cryptography_error)?;
248        let keylen = private_key_ctx.derive(None).map_err(cryptography_error)?;
249        let mut tmp_vec = vec![0; keylen];
250        let buffer = tmp_vec.as_mut_slice();
251        private_key_ctx
252            .derive(Some(buffer))
253            .map_err(cryptography_error)?;
254        Ok(buffer.to_vec())
255    }
256}
257
258#[cfg(test)]
259mod test {
260    use kanidm_proto::internal::IdentifyUserResponse;
261
262    use crate::idm::identityverification::{
263        IdentifyUserDisplayCodeEvent, IdentifyUserStartEvent, IdentifyUserSubmitCodeEvent,
264    };
265    use crate::prelude::*;
266
267    #[idm_test]
268    async fn test_identity_verification_unavailable(
269        idms: &IdmServer,
270        _idms_delayed: &IdmServerDelayed,
271    ) {
272        let ct = duration_from_epoch_now();
273        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
274
275        let invalid_user_uuid = Uuid::new_v4();
276        let valid_user_uuid = Uuid::new_v4();
277
278        let e1 = create_invalid_user_account(invalid_user_uuid);
279
280        let e2 = create_valid_user_account(valid_user_uuid);
281
282        let ce = CreateEvent::new_internal(vec![e1, e2]);
283        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
284        assert!(idms_prox_write.commit().is_ok());
285
286        let mut idms_prox_read = idms.proxy_read().await.unwrap();
287
288        let ident = idms_prox_read
289            .qs_read
290            .internal_search_uuid(invalid_user_uuid)
291            .map(Identity::from_impersonate_entry_readonly)
292            .expect("Failed to impersonate identity");
293
294        let res = idms_prox_read.handle_identify_user_start(
295            &IdentifyUserStartEvent::new(invalid_user_uuid, ident.clone()),
296            ct,
297        );
298
299        assert_eq!(
300            res,
301            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
302        );
303
304        let res = idms_prox_read.handle_identify_user_start(
305            &IdentifyUserStartEvent::new(valid_user_uuid, ident.clone()),
306            ct,
307        );
308
309        assert_eq!(
310            res,
311            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
312        );
313
314        let res = idms_prox_read.handle_identify_user_display_code(
315            &IdentifyUserDisplayCodeEvent::new(valid_user_uuid, ident.clone()),
316            ct,
317        );
318
319        assert_eq!(
320            res,
321            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
322        );
323
324        let res = idms_prox_read.handle_identify_user_submit_code(
325            &IdentifyUserSubmitCodeEvent::new(valid_user_uuid, ident, 123456),
326            ct,
327        );
328
329        assert_eq!(
330            res,
331            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
332        );
333    }
334
335    #[idm_test]
336    async fn test_invalid_user_id(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
337        let ct = duration_from_epoch_now();
338        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
339
340        let invalid_user_uuid = Uuid::new_v4();
341        let valid_user_a_uuid = Uuid::new_v4();
342        let valid_user_b_uuid = Uuid::new_v4();
343
344        let e1 = create_invalid_user_account(invalid_user_uuid);
345
346        let e2 = create_valid_user_account(valid_user_a_uuid);
347
348        let e3 = create_valid_user_account(valid_user_b_uuid);
349
350        let ce = CreateEvent::new_internal(vec![e1, e2, e3]);
351
352        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
353        assert!(idms_prox_write.commit().is_ok());
354
355        let mut idms_prox_read = idms.proxy_read().await.unwrap();
356
357        let ident = idms_prox_read
358            .qs_read
359            .internal_search_uuid(valid_user_a_uuid)
360            .map(Identity::from_impersonate_entry_readonly)
361            .expect("Failed to impersonate identity");
362
363        let res = idms_prox_read.handle_identify_user_start(
364            &IdentifyUserStartEvent::new(invalid_user_uuid, ident.clone()),
365            ct,
366        );
367
368        assert!(matches!(
369            res,
370            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
371        ));
372
373        let res = idms_prox_read.handle_identify_user_start(
374            &IdentifyUserStartEvent::new(invalid_user_uuid, ident.clone()),
375            ct,
376        );
377
378        assert!(matches!(
379            res,
380            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
381        ));
382
383        let res = idms_prox_read.handle_identify_user_display_code(
384            &IdentifyUserDisplayCodeEvent::new(invalid_user_uuid, ident.clone()),
385            ct,
386        );
387
388        assert!(matches!(
389            res,
390            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
391        ));
392        let res = idms_prox_read.handle_identify_user_submit_code(
393            &IdentifyUserSubmitCodeEvent::new(invalid_user_uuid, ident, 123456),
394            ct,
395        );
396
397        assert!(matches!(
398            res,
399            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
400        ));
401    }
402
403    #[idm_test]
404    async fn test_start_event(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
405        let ct = duration_from_epoch_now();
406        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
407
408        let valid_user_a_uuid = Uuid::new_v4();
409
410        let e = create_valid_user_account(valid_user_a_uuid);
411        let ce = CreateEvent::new_internal(vec![e]);
412        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
413        assert!(idms_prox_write.commit().is_ok());
414
415        let mut idms_prox_read = idms.proxy_read().await.unwrap();
416
417        let ident = idms_prox_read
418            .qs_read
419            .internal_search_uuid(valid_user_a_uuid)
420            .map(Identity::from_impersonate_entry_readonly)
421            .expect("Failed to impersonate identity");
422
423        let res = idms_prox_read.handle_identify_user_start(
424            &IdentifyUserStartEvent::new(valid_user_a_uuid, ident.clone()),
425            ct,
426        );
427
428        assert!(matches!(
429            res,
430            Ok(IdentifyUserResponse::IdentityVerificationUnavailable)
431        ));
432    }
433
434    #[idm_test] // actually this is somewhat a duplicate of `test_full_identification_flow` inside the testkit, with the exception that this
435                //tests ONLY the totp code correctness and not the flow correctness. To test the correctness it obviously needs to also
436                // enforce some flow checks, but this is not the primary scope of this test
437    async fn test_code_correctness(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
438        let ct = duration_from_epoch_now();
439        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
440        let user_a_uuid = Uuid::new_v4();
441        let user_b_uuid = Uuid::new_v4();
442        let e1 = create_valid_user_account(user_a_uuid);
443        let e2 = create_valid_user_account(user_b_uuid);
444        let ce = CreateEvent::new_internal(vec![e1, e2]);
445
446        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
447        assert!(idms_prox_write.commit().is_ok());
448
449        let mut idms_prox_read = idms.proxy_read().await.unwrap();
450
451        let ident_a = idms_prox_read
452            .qs_read
453            .internal_search_uuid(user_a_uuid)
454            .map(Identity::from_impersonate_entry_readonly)
455            .expect("Failed to impersonate identity");
456
457        let ident_b = idms_prox_read
458            .qs_read
459            .internal_search_uuid(user_b_uuid)
460            .map(Identity::from_impersonate_entry_readonly)
461            .expect("Failed to impersonate identity");
462
463        let (lower_user, lower_user_uuid, higher_user, higher_user_uuid) =
464            if user_a_uuid < user_b_uuid {
465                (ident_a, user_a_uuid, ident_b, user_b_uuid)
466            } else {
467                (ident_b, user_b_uuid, ident_a, user_a_uuid)
468            };
469
470        // First the user with the lowest uuid receives the uuid from the other user
471
472        let res_higher_user = idms_prox_read.handle_identify_user_start(
473            &IdentifyUserStartEvent::new(lower_user_uuid, higher_user.clone()),
474            ct,
475        );
476
477        let Ok(IdentifyUserResponse::ProvideCode { totp, .. }) = res_higher_user else {
478            panic!();
479        };
480
481        let res_lower_user_wrong = idms_prox_read.handle_identify_user_submit_code(
482            &IdentifyUserSubmitCodeEvent::new(higher_user_uuid, lower_user.clone(), totp + 1),
483            ct,
484        );
485
486        assert!(matches!(
487            res_lower_user_wrong,
488            Ok(IdentifyUserResponse::CodeFailure)
489        ));
490
491        let res_lower_user_correct = idms_prox_read.handle_identify_user_submit_code(
492            &IdentifyUserSubmitCodeEvent::new(higher_user_uuid, lower_user.clone(), totp),
493            ct,
494        );
495
496        assert!(matches!(
497            res_lower_user_correct,
498            Ok(IdentifyUserResponse::ProvideCode { .. })
499        ));
500
501        // now we need to get the code from the lower_user and submit it to the higher_user
502
503        let Ok(IdentifyUserResponse::ProvideCode { totp, .. }) = res_lower_user_correct else {
504            panic!("Invalid");
505        };
506
507        let res_higher_user_2_wrong = idms_prox_read.handle_identify_user_submit_code(
508            &IdentifyUserSubmitCodeEvent::new(lower_user_uuid, higher_user.clone(), totp + 1),
509            ct,
510        );
511
512        assert!(matches!(
513            res_higher_user_2_wrong,
514            Ok(IdentifyUserResponse::CodeFailure)
515        ));
516
517        let res_higher_user_2_correct = idms_prox_read.handle_identify_user_submit_code(
518            &IdentifyUserSubmitCodeEvent::new(lower_user_uuid, higher_user.clone(), totp),
519            ct,
520        );
521
522        assert!(matches!(
523            res_higher_user_2_correct,
524            Ok(IdentifyUserResponse::Success)
525        ));
526    }
527
528    #[idm_test]
529    async fn test_totps_differ(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
530        let ct = duration_from_epoch_now();
531        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
532        let user_a_uuid = Uuid::new_v4();
533        let user_b_uuid = Uuid::new_v4();
534        let e1 = create_valid_user_account(user_a_uuid);
535        let e2 = create_valid_user_account(user_b_uuid);
536        let ce = CreateEvent::new_internal(vec![e1, e2]);
537
538        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
539        assert!(idms_prox_write.commit().is_ok());
540
541        let mut idms_prox_read = idms.proxy_read().await.unwrap();
542
543        let ident_a = idms_prox_read
544            .qs_read
545            .internal_search_uuid(user_a_uuid)
546            .map(Identity::from_impersonate_entry_readonly)
547            .expect("Failed to impersonate identity");
548
549        let ident_b = idms_prox_read
550            .qs_read
551            .internal_search_uuid(user_b_uuid)
552            .map(Identity::from_impersonate_entry_readonly)
553            .expect("Failed to impersonate identity");
554
555        let (lower_user, lower_user_uuid, higher_user, higher_user_uuid) =
556            if user_a_uuid < user_b_uuid {
557                (ident_a, user_a_uuid, ident_b, user_b_uuid)
558            } else {
559                (ident_b, user_b_uuid, ident_a, user_a_uuid)
560            };
561
562        // First twe retrieve the higher user code
563
564        let res_higher_user = idms_prox_read.handle_identify_user_start(
565            &IdentifyUserStartEvent::new(lower_user_uuid, higher_user.clone()),
566            ct,
567        );
568
569        let Ok(IdentifyUserResponse::ProvideCode {
570            totp: higher_user_totp,
571            ..
572        }) = res_higher_user
573        else {
574            panic!();
575        };
576
577        // then we get the lower user code
578
579        let res_lower_user_correct = idms_prox_read.handle_identify_user_submit_code(
580            &IdentifyUserSubmitCodeEvent::new(
581                higher_user_uuid,
582                lower_user.clone(),
583                higher_user_totp,
584            ),
585            ct,
586        );
587
588        if let Ok(IdentifyUserResponse::ProvideCode {
589            totp: lower_user_totp,
590            ..
591        }) = res_lower_user_correct
592        {
593            assert_ne!(higher_user_totp, lower_user_totp);
594        } else {
595            debug_assert!(false);
596        }
597    }
598
599    fn create_valid_user_account(uuid: Uuid) -> EntryInitNew {
600        let mut name = String::from("valid_user");
601        name.push_str(&uuid.to_string());
602        // if anyone from the future will see this test failing because of a schema violation
603        // and wonders to this line of code I'm sorry to have wasted your time
604        name.truncate(14);
605        entry_init!(
606            (Attribute::Class, EntryClass::Object.to_value()),
607            (Attribute::Class, EntryClass::Account.to_value()),
608            (Attribute::Class, EntryClass::Person.to_value()),
609            (Attribute::Name, Value::new_iname(&name)),
610            (Attribute::Uuid, Value::Uuid(uuid)),
611            (Attribute::Description, Value::new_utf8s("some valid user")),
612            (Attribute::DisplayName, Value::new_utf8s("Some valid user"))
613        )
614    }
615
616    fn create_invalid_user_account(uuid: Uuid) -> EntryInitNew {
617        entry_init!(
618            (Attribute::Class, EntryClass::Object.to_value()),
619            (Attribute::Class, EntryClass::Account.to_value()),
620            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
621            (Attribute::Name, Value::new_iname("invalid_user")),
622            (Attribute::Uuid, Value::Uuid(uuid)),
623            (Attribute::Description, Value::new_utf8s("invalid_user")),
624            (Attribute::DisplayName, Value::new_utf8s("Invalid user"))
625        )
626    }
627}