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::keys::KeyProvidersTransaction;
6use crate::server::QueryServerTransaction;
7use crypto_glue::hmac_s256::HmacSha256Key;
8use kanidm_proto::internal::IdentifyUserResponse;
9use std::sync::Arc;
10use uuid::Uuid;
11
12// This is longer than a normal TOTP step as we expect users to be talking
13// to each other, so it could take a few minutes.
14static TOTP_STEP: u64 = 300;
15
16#[derive(Debug)]
17pub struct IdentifyUserStartEvent {
18    pub target: Uuid,
19    pub ident: Identity,
20}
21
22impl IdentifyUserStartEvent {
23    pub fn new(target: Uuid, ident: Identity) -> Self {
24        IdentifyUserStartEvent { target, ident }
25    }
26}
27pub struct IdentifyUserDisplayCodeEvent {
28    pub target: Uuid,
29    pub ident: Identity,
30}
31
32impl IdentifyUserDisplayCodeEvent {
33    pub fn new(target: Uuid, ident: Identity) -> Self {
34        IdentifyUserDisplayCodeEvent { target, ident }
35    }
36}
37
38pub struct IdentifyUserSubmitCodeEvent {
39    pub code: u32,
40    pub target: Uuid,
41    pub ident: Identity,
42}
43
44impl IdentifyUserSubmitCodeEvent {
45    pub fn new(target: Uuid, ident: Identity, code: u32) -> Self {
46        IdentifyUserSubmitCodeEvent {
47            target,
48            ident,
49            code,
50        }
51    }
52}
53
54impl IdmServerProxyReadTransaction<'_> {
55    pub fn handle_identify_user_start(
56        &mut self,
57        IdentifyUserStartEvent { target, ident }: &IdentifyUserStartEvent,
58        current_time: Duration,
59    ) -> Result<IdentifyUserResponse, OperationError> {
60        let (ident_entry, target_entry) = match self.get_involved_entries(ident, *target) {
61            Ok(tuple) => tuple,
62            Err(early_response) => return Ok(early_response),
63        };
64
65        let response = if ident_entry.get_uuid() < target_entry.get_uuid() {
66            IdentifyUserResponse::WaitForCode
67        } else {
68            let totp_secret = self.get_totp(current_time, &ident_entry, &target_entry)?;
69
70            let totp_value = totp_secret
71                .do_totp_duration_from_epoch(&current_time)
72                .map_err(|_| OperationError::CryptographyError)?;
73
74            IdentifyUserResponse::ProvideCode {
75                step: TOTP_STEP as u32,
76                totp: totp_value,
77            }
78        };
79        Ok(response)
80    }
81
82    pub fn handle_identify_user_display_code(
83        &mut self,
84        IdentifyUserDisplayCodeEvent { target, ident }: &IdentifyUserDisplayCodeEvent,
85        current_time: Duration,
86    ) -> Result<IdentifyUserResponse, OperationError> {
87        let (ident_entry, target_entry) = match self.get_involved_entries(ident, *target) {
88            Ok(tuple) => tuple,
89            Err(early_response) => return Ok(early_response),
90        };
91
92        let totp_secret = self.get_totp(current_time, &ident_entry, &target_entry)?;
93
94        let totp_value = totp_secret
95            .do_totp_duration_from_epoch(&current_time)
96            .map_err(|_| OperationError::CryptographyError)?;
97
98        Ok(IdentifyUserResponse::ProvideCode {
99            step: TOTP_STEP as u32,
100            totp: totp_value,
101        })
102    }
103
104    pub fn handle_identify_user_submit_code(
105        &mut self,
106        IdentifyUserSubmitCodeEvent {
107            target,
108            ident,
109            code,
110        }: &IdentifyUserSubmitCodeEvent,
111        current_time: Duration,
112    ) -> Result<IdentifyUserResponse, OperationError> {
113        let (ident_entry, target_entry) = match self.get_involved_entries(ident, *target) {
114            Ok(tuple) => tuple,
115            Err(early_response) => return Ok(early_response),
116        };
117
118        let totp_secret = self.get_totp(current_time, &target_entry, &ident_entry)?;
119
120        if !totp_secret.verify(*code, current_time) {
121            return Ok(IdentifyUserResponse::CodeFailure);
122        }
123
124        // if we waited the first it means now it's time to go for ProvideCode, otherwise we just confirm that the code is correct
125        // (we know this for a fact as we have already checked that the code is correct)
126        let response = if ident_entry.get_uuid() < target_entry.get_uuid() {
127            let totp_secret = self.get_totp(current_time, &ident_entry, &target_entry)?;
128            let totp_value = totp_secret
129                .do_totp_duration_from_epoch(&current_time)
130                .map_err(|_| OperationError::CryptographyError)?;
131            IdentifyUserResponse::ProvideCode {
132                step: TOTP_STEP as u32,
133                totp: totp_value,
134            }
135        } else {
136            IdentifyUserResponse::Success
137        };
138        Ok(response)
139    }
140
141    fn get_involved_entries(
142        &mut self,
143        ident: &Identity,
144        target: Uuid,
145    ) -> Result<(Arc<EntrySealedCommitted>, Arc<EntrySealedCommitted>), IdentifyUserResponse> {
146        if self
147            .qs_read
148            .get_key_providers()
149            .get_key_object_handle(UUID_DOMAIN_ID_VERIFICATION_KEY)
150            .is_none()
151        {
152            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
153        }
154
155        let Some(ident_entry) = ident.get_user_entry() else {
156            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
157        };
158
159        let Ok(target_entry) = self.get_partner_entry(target) else {
160            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
161        };
162
163        if ident_entry.get_uuid() == target_entry.get_uuid() {
164            return Err(IdentifyUserResponse::IdentityVerificationUnavailable);
165        }
166
167        Ok((ident_entry, target_entry))
168    }
169
170    fn get_partner_entry(
171        &mut self,
172        target: Uuid,
173    ) -> Result<Arc<EntrySealedCommitted>, OperationError> {
174        self.qs_read
175            .internal_search_uuid(target)
176            .inspect_err(|err| error!(?err, ?target, "Failed to retrieve entry",))
177    }
178
179    fn get_totp(
180        &mut self,
181        current_time: Duration,
182        initiating_entry: &EntrySealedCommitted,
183        receiving_entry: &EntrySealedCommitted,
184    ) -> Result<Totp, OperationError> {
185        let key_object = self
186            .qs_read
187            .get_key_providers()
188            .get_key_object_handle(UUID_DOMAIN_ID_VERIFICATION_KEY)
189            .ok_or(OperationError::KP0078KeyObjectNotFound)?;
190
191        let initiating_uuid = initiating_entry.get_uuid();
192        let receiving_uuid = receiving_entry.get_uuid();
193
194        // Uuid's are always 16 bytes, so this is 32.
195        let mut info_bytes: [u8; 32] = [0; 32];
196        info_bytes[..16].copy_from_slice(initiating_uuid.as_bytes());
197        info_bytes[16..].copy_from_slice(receiving_uuid.as_bytes());
198
199        let mut shared_key = HmacSha256Key::default();
200        key_object.hkdf_s256_expand(&info_bytes, shared_key.as_mut_slice(), current_time)?;
201
202        let totp = Totp::new(
203            shared_key.as_slice().to_vec(),
204            TOTP_STEP,
205            TotpAlgo::Sha256,
206            TotpDigits::Six,
207        );
208        Ok(totp)
209    }
210}
211
212#[cfg(test)]
213mod test {
214    use crate::idm::identityverification::{
215        IdentifyUserDisplayCodeEvent, IdentifyUserStartEvent, IdentifyUserSubmitCodeEvent,
216    };
217    use crate::prelude::*;
218    use kanidm_proto::internal::IdentifyUserResponse;
219
220    #[idm_test]
221    async fn test_identity_verification_unavailable(
222        idms: &IdmServer,
223        _idms_delayed: &IdmServerDelayed,
224    ) {
225        let ct = duration_from_epoch_now();
226        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
227
228        let invalid_user_uuid = Uuid::new_v4();
229        let valid_user_uuid = Uuid::new_v4();
230
231        let e2 = create_valid_user_account(valid_user_uuid, "valid_idv_user");
232
233        let ce = CreateEvent::new_internal(vec![e2]);
234        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
235        assert!(idms_prox_write.commit().is_ok());
236
237        let mut idms_prox_read = idms.proxy_read().await.unwrap();
238
239        let ident = idms_prox_read
240            .qs_read
241            .internal_search_uuid(valid_user_uuid)
242            .map(Identity::from_impersonate_entry_readonly)
243            .expect("Failed to impersonate identity");
244
245        // Can't ID verify to system Internal
246        let res = idms_prox_read
247            .handle_identify_user_start(
248                &IdentifyUserStartEvent::new(valid_user_uuid, Identity::from_internal()),
249                ct,
250            )
251            .expect("failed to start id verification");
252
253        assert!(matches!(
254            res,
255            IdentifyUserResponse::IdentityVerificationUnavailable
256        ));
257
258        // We can't ID verify to ourself.
259        let res = idms_prox_read
260            .handle_identify_user_start(
261                &IdentifyUserStartEvent::new(valid_user_uuid, ident.clone()),
262                ct,
263            )
264            .expect("failed to start id verification");
265
266        assert!(matches!(
267            res,
268            IdentifyUserResponse::IdentityVerificationUnavailable
269        ));
270
271        // Can't do IDV to a UUID that doesn't exist.
272        let res = idms_prox_read
273            .handle_identify_user_start(
274                &IdentifyUserStartEvent::new(invalid_user_uuid, ident.clone()),
275                ct,
276            )
277            .expect("failed to start id verification");
278
279        assert!(matches!(
280            res,
281            IdentifyUserResponse::IdentityVerificationUnavailable
282        ));
283    }
284
285    #[idm_test]
286    async fn test_idv_flow(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
287        let ct = duration_from_epoch_now();
288        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
289        let user_a_uuid = uuid::uuid!("20f44860-7db3-40f4-a2c3-d9f163f855ec");
290        let user_b_uuid = uuid::uuid!("dde1de53-cbd2-439c-a3c9-bde6ee026e78");
291        let e1 = create_valid_user_account(user_a_uuid, "idv_user_a");
292        let e2 = create_valid_user_account(user_b_uuid, "idv_user_b");
293        let ce = CreateEvent::new_internal(vec![e1, e2]);
294
295        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
296        assert!(idms_prox_write.commit().is_ok());
297
298        let mut idms_prox_read = idms.proxy_read().await.unwrap();
299
300        let user_a = idms_prox_read
301            .qs_read
302            .internal_search_uuid(user_a_uuid)
303            .map(Identity::from_impersonate_entry_readonly)
304            .expect("Failed to impersonate identity");
305
306        let user_b = idms_prox_read
307            .qs_read
308            .internal_search_uuid(user_b_uuid)
309            .map(Identity::from_impersonate_entry_readonly)
310            .expect("Failed to impersonate identity");
311
312        // First we retrieve the higher user code
313        let res_higher_user = idms_prox_read
314            .handle_identify_user_start(
315                &IdentifyUserStartEvent::new(user_a_uuid, user_b.clone()),
316                ct,
317            )
318            .expect("Failed to retrieve code.");
319
320        let higher_user_totp = match res_higher_user {
321            IdentifyUserResponse::ProvideCode { totp, .. } => totp,
322            state => {
323                error!(?state);
324                unreachable!()
325            }
326        };
327
328        // DisplayCode shows the same result.
329        let res_higher_user = idms_prox_read
330            .handle_identify_user_display_code(
331                &IdentifyUserDisplayCodeEvent::new(user_a_uuid, user_b.clone()),
332                ct,
333            )
334            .expect("Failed to retrieve code.");
335
336        let higher_user_totp_display = match res_higher_user {
337            IdentifyUserResponse::ProvideCode { totp, .. } => totp,
338            state => {
339                error!(?state);
340                unreachable!()
341            }
342        };
343
344        assert_eq!(higher_user_totp_display, higher_user_totp);
345
346        // The lower user is in state "wait"
347        let lower_user_state = idms_prox_read
348            .handle_identify_user_start(
349                &IdentifyUserStartEvent::new(user_b_uuid, user_a.clone()),
350                ct,
351            )
352            .expect("Failed start idv.");
353
354        assert!(matches!(
355            lower_user_state,
356            IdentifyUserResponse::WaitForCode
357        ));
358
359        // Submit an incorrect code as the lower user.
360        let lower_user_state = idms_prox_read
361            .handle_identify_user_submit_code(
362                &IdentifyUserSubmitCodeEvent::new(
363                    user_b_uuid,
364                    user_a.clone(),
365                    higher_user_totp + 1,
366                ),
367                ct,
368            )
369            .expect("Failed to retrieve code.");
370
371        match lower_user_state {
372            IdentifyUserResponse::CodeFailure => {}
373            state => {
374                error!(?state);
375                unreachable!()
376            }
377        };
378
379        // Submit the correct code as the lower user,
380        let lower_user_state = idms_prox_read
381            .handle_identify_user_submit_code(
382                &IdentifyUserSubmitCodeEvent::new(user_b_uuid, user_a.clone(), higher_user_totp),
383                ct,
384            )
385            .expect("Failed to retrieve code.");
386
387        let lower_user_totp = match lower_user_state {
388            IdentifyUserResponse::ProvideCode { totp, .. } => totp,
389            state => {
390                error!(?state);
391                unreachable!()
392            }
393        };
394
395        debug!(?higher_user_totp, ?lower_user_totp);
396        assert_ne!(higher_user_totp, lower_user_totp);
397
398        // Assert that the lower user code display is correct.
399        let lower_user_state = idms_prox_read
400            .handle_identify_user_display_code(
401                &IdentifyUserDisplayCodeEvent::new(user_b_uuid, user_a.clone()),
402                ct,
403            )
404            .expect("Failed to retrieve code.");
405
406        let lower_user_totp_display = match lower_user_state {
407            IdentifyUserResponse::ProvideCode { totp, .. } => totp,
408            state => {
409                error!(?state);
410                unreachable!()
411            }
412        };
413
414        assert_eq!(lower_user_totp_display, lower_user_totp);
415
416        // Submit the wrong code as the higher user
417        let higher_user_state = idms_prox_read
418            .handle_identify_user_submit_code(
419                &IdentifyUserSubmitCodeEvent::new(user_a_uuid, user_b.clone(), lower_user_totp + 1),
420                ct,
421            )
422            .expect("Failed to retrieve code.");
423
424        match higher_user_state {
425            IdentifyUserResponse::CodeFailure => {}
426            state => {
427                error!(?state);
428                unreachable!()
429            }
430        };
431
432        // Now check that the higher user can submit correctly.
433        let higher_user_state = idms_prox_read
434            .handle_identify_user_submit_code(
435                &IdentifyUserSubmitCodeEvent::new(user_a_uuid, user_b.clone(), lower_user_totp),
436                ct,
437            )
438            .expect("Failed to retrieve code.");
439
440        match higher_user_state {
441            IdentifyUserResponse::Success => {}
442            state => {
443                error!(?state);
444                unreachable!()
445            }
446        };
447    }
448
449    fn create_valid_user_account(uuid: Uuid, name: &str) -> EntryInitNew {
450        entry_init!(
451            (Attribute::Class, EntryClass::Object.to_value()),
452            (Attribute::Class, EntryClass::Account.to_value()),
453            (Attribute::Class, EntryClass::Person.to_value()),
454            (Attribute::Name, Value::new_iname(name)),
455            (Attribute::Uuid, Value::Uuid(uuid)),
456            (Attribute::Description, Value::new_utf8s("some valid user")),
457            (Attribute::DisplayName, Value::new_utf8s("Some valid user"))
458        )
459    }
460}