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