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
12static 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(¤t_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(¤t_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 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(¤t_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 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 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 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 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 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 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 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 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 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 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 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 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}