blob: 15f0bc080d4a2a4c32164f85e0b83139d077314c [file] [log] [blame]
David Drysdalef7ed95a2024-05-08 13:51:45 +01001// Copyright 2024, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Tests for user authentication interactions (via `IKeystoreAuthorization`).
16
David Drysdale89e87d52024-10-04 13:07:43 +010017use crate::keystore2_client_test_utils::{BarrierReached, BarrierReachedWithData};
David Drysdalef7ed95a2024-05-08 13:51:45 +010018use android_security_authorization::aidl::android::security::authorization::{
19 IKeystoreAuthorization::IKeystoreAuthorization
20};
21use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::{
22 IKeystoreMaintenance,
23};
24use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
David Drysdale89e87d52024-10-04 13:07:43 +010025 Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode,
26 HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
27 KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
David Drysdalef7ed95a2024-05-08 13:51:45 +010028};
29use android_system_keystore2::aidl::android::system::keystore2::{
30 CreateOperationResponse::CreateOperationResponse, Domain::Domain, KeyDescriptor::KeyDescriptor,
31 KeyMetadata::KeyMetadata,
32};
33use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
34 Timestamp::Timestamp,
35};
36use keystore2_test_utils::{
37 get_keystore_service, run_as, authorizations::AuthSetBuilder,
David Drysdale89e87d52024-10-04 13:07:43 +010038 key_generations::assert_km_error, run_as::{ChannelReader,ChannelWriter},
David Drysdalef7ed95a2024-05-08 13:51:45 +010039};
40use log::{warn, info};
41use nix::unistd::{Gid, Uid};
42use rustutils::users::AID_USER_OFFSET;
David Drysdale89e87d52024-10-04 13:07:43 +010043use std::{time::Duration, thread::sleep};
David Drysdalef7ed95a2024-05-08 13:51:45 +010044
David Drysdale89e87d52024-10-04 13:07:43 +010045/// SELinux context.
46const CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
David Drysdalef7ed95a2024-05-08 13:51:45 +010047/// Test user ID.
48const TEST_USER_ID: i32 = 100;
David Drysdale89e87d52024-10-04 13:07:43 +010049/// Corresponding uid value.
50const UID: u32 = TEST_USER_ID as u32 * AID_USER_OFFSET + 1001;
51/// Fake synthetic password blob.
52static SYNTHETIC_PASSWORD: &[u8] = &[
David Drysdalef7ed95a2024-05-08 13:51:45 +010053 0x42, 0x39, 0x30, 0x37, 0x44, 0x37, 0x32, 0x37, 0x39, 0x39, 0x43, 0x42, 0x39, 0x41, 0x42, 0x30,
54 0x34, 0x31, 0x30, 0x38, 0x46, 0x44, 0x33, 0x45, 0x39, 0x42, 0x32, 0x38, 0x36, 0x35, 0x41, 0x36,
55 0x33, 0x44, 0x42, 0x42, 0x43, 0x36, 0x33, 0x42, 0x34, 0x39, 0x37, 0x33, 0x35, 0x45, 0x41, 0x41,
56 0x32, 0x45, 0x31, 0x35, 0x43, 0x43, 0x46, 0x32, 0x39, 0x36, 0x33, 0x34, 0x31, 0x32, 0x41, 0x39,
57];
58/// Fake SID value corresponding to Gatekeeper.
David Drysdale89e87d52024-10-04 13:07:43 +010059static GK_FAKE_SID: i64 = 123456;
David Drysdalef7ed95a2024-05-08 13:51:45 +010060/// Fake SID value corresponding to a biometric authenticator.
David Drysdale89e87d52024-10-04 13:07:43 +010061static BIO_FAKE_SID1: i64 = 345678;
David Drysdalef7ed95a2024-05-08 13:51:45 +010062/// Fake SID value corresponding to a biometric authenticator.
David Drysdale89e87d52024-10-04 13:07:43 +010063static BIO_FAKE_SID2: i64 = 456789;
David Drysdalef7ed95a2024-05-08 13:51:45 +010064
65const WEAK_UNLOCK_ENABLED: bool = true;
66const WEAK_UNLOCK_DISABLED: bool = false;
67const UNFORCED: bool = false;
68
69fn get_authorization() -> binder::Strong<dyn IKeystoreAuthorization> {
70 binder::get_interface("android.security.authorization").unwrap()
71}
72
73fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> {
74 binder::get_interface("android.security.maintenance").unwrap()
75}
76
77fn abort_op(result: binder::Result<CreateOperationResponse>) {
78 if let Ok(rsp) = result {
79 if let Some(op) = rsp.iOperation {
80 if let Err(e) = op.abort() {
81 warn!("abort op failed: {e:?}");
82 }
83 } else {
84 warn!("can't abort op with missing iOperation");
85 }
86 } else {
87 warn!("can't abort failed op: {result:?}");
88 }
89}
90
91/// RAII structure to ensure that test users are removed at the end of a test.
92struct TestUser {
93 id: i32,
94 maint: binder::Strong<dyn IKeystoreMaintenance>,
95}
96
97impl TestUser {
98 fn new() -> Self {
David Drysdale89e87d52024-10-04 13:07:43 +010099 Self::new_user(TEST_USER_ID, SYNTHETIC_PASSWORD)
David Drysdalef7ed95a2024-05-08 13:51:45 +0100100 }
101 fn new_user(user_id: i32, password: &[u8]) -> Self {
102 let maint = get_maintenance();
103 maint.onUserAdded(user_id).expect("failed to add test user");
104 maint
105 .initUserSuperKeys(user_id, password, /* allowExisting= */ false)
106 .expect("failed to init test user");
107 Self { id: user_id, maint }
108 }
109}
110
111impl Drop for TestUser {
112 fn drop(&mut self) {
113 let _ = self.maint.onUserRemoved(self.id);
114 }
115}
116
117#[test]
David Drysdale89e87d52024-10-04 13:07:43 +0100118fn test_auth_bound_timeout_failure() {
David Drysdalef7ed95a2024-05-08 13:51:45 +0100119 android_logger::init_once(
120 android_logger::Config::default()
121 .with_tag("keystore2_client_tests")
122 .with_max_level(log::LevelFilter::Debug),
123 );
David Drysdalef7ed95a2024-05-08 13:51:45 +0100124
David Drysdale89e87d52024-10-04 13:07:43 +0100125 let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
126 writer: &mut ChannelWriter<BarrierReached>|
127 -> Result<(), String> {
128 // Now we're in a new process, wait to be notified before starting.
129 reader.recv();
130
131 // Action A: create a new auth-bound key which requires auth in the last 3 seconds,
132 // and fail to start an operation using it.
133 let ks2 = get_keystore_service();
134
135 let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
136 let params = AuthSetBuilder::new()
137 .user_secure_id(BIO_FAKE_SID1)
138 .user_secure_id(BIO_FAKE_SID2)
139 .user_auth_type(HardwareAuthenticatorType::ANY)
140 .auth_timeout(3)
141 .algorithm(Algorithm::EC)
142 .purpose(KeyPurpose::SIGN)
143 .purpose(KeyPurpose::VERIFY)
144 .digest(Digest::SHA_2_256)
145 .ec_curve(EcCurve::P_256);
146
147 let KeyMetadata { key, .. } = sec_level
148 .generateKey(
149 &KeyDescriptor {
150 domain: Domain::APP,
151 nspace: -1,
152 alias: Some("auth-bound-timeout".to_string()),
153 blob: None,
154 },
155 None,
156 &params,
157 0,
158 b"entropy",
159 )
160 .expect("key generation failed");
161 info!("A: created auth-timeout key {key:?}");
162
163 // No HATs so cannot create an operation using the key.
164 let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
165 let result = sec_level.createOperation(&key, &params, UNFORCED);
166 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
167 info!("A: failed auth-bound operation (no HAT) as expected {result:?}");
168
169 writer.send(&BarrierReached {}); // A done.
170
171 // Action B: fail again when an invalid HAT is available.
172 reader.recv();
173
174 let result = sec_level.createOperation(&key, &params, UNFORCED);
175 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
176 info!("B: failed auth-bound operation (HAT is invalid) as expected {result:?}");
177
178 writer.send(&BarrierReached {}); // B done.
179
180 // Action C: fail again when the HAT is old enough to not even be checked.
181 reader.recv();
182 info!("C: wait so that any HAT times out");
183 sleep(Duration::from_secs(4));
184 let result = sec_level.createOperation(&key, &params, UNFORCED);
185 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
186 info!("C: failed auth-bound operation (HAT is too old) as expected {result:?}");
187 writer.send(&BarrierReached {}); // C done.
188
189 Ok(())
190 };
191
192 // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
193 // `--test-threads=1`), and nothing yet done with binder.
David Drysdalef7ed95a2024-05-08 13:51:45 +0100194 let mut child_handle = unsafe {
195 // Perform keystore actions while running as the test user.
David Drysdale89e87d52024-10-04 13:07:43 +0100196 run_as::run_as_child(CTX, Uid::from_raw(UID), Gid::from_raw(UID), child_fn)
197 }
198 .unwrap();
Rajesh Nyamagoudca4f7af2024-07-29 18:39:01 +0000199
David Drysdale89e87d52024-10-04 13:07:43 +0100200 // Now that the separate process has been forked off, it's safe to use binder to setup a test
201 // user.
202 let _ks2 = get_keystore_service();
203 let user = TestUser::new();
204 let user_id = user.id;
205 let auth_service = get_authorization();
Rajesh Nyamagoudca4f7af2024-07-29 18:39:01 +0000206
David Drysdale89e87d52024-10-04 13:07:43 +0100207 // Lock and unlock to ensure super keys are already created.
208 auth_service
209 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
210 .unwrap();
211 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
212 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100213
David Drysdale89e87d52024-10-04 13:07:43 +0100214 info!("trigger child process action A and wait for completion");
215 child_handle.send(&BarrierReached {});
216 child_handle.recv();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100217
David Drysdale89e87d52024-10-04 13:07:43 +0100218 // Unlock with password and a fake auth token that matches the key
219 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
220 auth_service.addAuthToken(&fake_bio_lskf_token(GK_FAKE_SID, BIO_FAKE_SID1)).unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100221
David Drysdale89e87d52024-10-04 13:07:43 +0100222 info!("trigger child process action B and wait for completion");
223 child_handle.send(&BarrierReached {});
224 child_handle.recv();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100225
David Drysdale89e87d52024-10-04 13:07:43 +0100226 info!("trigger child process action C and wait for completion");
227 child_handle.send(&BarrierReached {});
228 child_handle.recv();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100229
David Drysdale89e87d52024-10-04 13:07:43 +0100230 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
231}
232
233#[test]
234fn test_auth_bound_per_op_failure() {
235 type Barrier = BarrierReachedWithData<i64>;
236 android_logger::init_once(
237 android_logger::Config::default()
238 .with_tag("keystore2_client_tests")
239 .with_max_level(log::LevelFilter::Debug),
240 );
241
242 let child_fn = move |reader: &mut ChannelReader<Barrier>,
243 writer: &mut ChannelWriter<Barrier>|
244 -> Result<(), String> {
245 // Now we're in a new process, wait to be notified before starting.
246 reader.recv();
247
248 // Action A: create a new auth-bound key which requires auth-per-operation (because
249 // AUTH_TIMEOUT is not specified), and fail to finish an operation using it.
250 let ks2 = get_keystore_service();
251
252 let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
253 let params = AuthSetBuilder::new()
254 .user_secure_id(GK_FAKE_SID)
255 .user_secure_id(BIO_FAKE_SID1)
256 .user_auth_type(HardwareAuthenticatorType::ANY)
257 .algorithm(Algorithm::EC)
258 .purpose(KeyPurpose::SIGN)
259 .purpose(KeyPurpose::VERIFY)
260 .digest(Digest::SHA_2_256)
261 .ec_curve(EcCurve::P_256);
262
263 let KeyMetadata { key, .. } = sec_level
264 .generateKey(
265 &KeyDescriptor {
266 domain: Domain::APP,
267 nspace: -1,
268 alias: Some("auth-per-op".to_string()),
269 blob: None,
270 },
271 None,
272 &params,
273 0,
274 b"entropy",
275 )
276 .expect("key generation failed");
277 info!("A: created auth-per-op key {key:?}");
278
279 // We can create an operation using the key...
280 let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
281 let result = sec_level
282 .createOperation(&key, &params, UNFORCED)
283 .expect("failed to create auth-per-op operation");
284 let op = result.iOperation.expect("no operation in result");
285 info!("A: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
286
287 // .. but attempting to finish the operation fails because Keystore can't find a HAT.
288 let result = op.finish(Some(b"data"), None);
289 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
290 info!("A: failed auth-per-op op (no HAT) as expected {result:?}");
291
292 writer.send(&Barrier::new(0)); // A done.
293
294 // Action B: fail again when an irrelevant HAT is available.
295 reader.recv();
296
297 let result = sec_level
298 .createOperation(&key, &params, UNFORCED)
299 .expect("failed to create auth-per-op operation");
300 let op = result.iOperation.expect("no operation in result");
301 info!("B: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
302 // The operation fails because the HAT that Keystore received is not related to the
303 // challenge.
304 let result = op.finish(Some(b"data"), None);
305 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
306 info!("B: failed auth-per-op op (HAT is not per-op) as expected {result:?}");
307
308 writer.send(&Barrier::new(0)); // B done.
309
310 // Action C: start an operation and pass out the challenge
311 reader.recv();
312 let result = sec_level
313 .createOperation(&key, &params, UNFORCED)
314 .expect("failed to create auth-per-op operation");
315 let op = result.iOperation.expect("no operation in result");
316 info!("C: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
317 writer.send(&Barrier::new(result.operationChallenge.unwrap().challenge)); // C done.
318
319 // Action D: finishing the operation still fails because the per-op HAT
320 // is invalid (the HMAC signature is faked and so the secure world
321 // rejects the HAT).
322 reader.recv();
323 let result = op.finish(Some(b"data"), None);
324 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
325 info!("D: failed auth-per-op op (HAT is per-op but invalid) as expected {result:?}");
326 writer.send(&Barrier::new(0)); // D done.
327
328 Ok(())
329 };
330
331 // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
332 // `--test-threads=1`), and nothing yet done with binder.
333 let mut child_handle = unsafe {
334 // Perform keystore actions while running as the test user.
335 run_as::run_as_child(CTX, Uid::from_raw(UID), Gid::from_raw(UID), child_fn)
336 }
337 .unwrap();
338
339 // Now that the separate process has been forked off, it's safe to use binder to setup a test
340 // user.
341 let _ks2 = get_keystore_service();
342 let user = TestUser::new();
343 let user_id = user.id;
344 let auth_service = get_authorization();
345
346 // Lock and unlock to ensure super keys are already created.
347 auth_service
348 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
349 .unwrap();
350 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
351 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
352
353 info!("trigger child process action A and wait for completion");
354 child_handle.send(&Barrier::new(0));
355 child_handle.recv();
356
357 // Unlock with password and a fake auth token.
358 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
359 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
360
361 info!("trigger child process action B and wait for completion");
362 child_handle.send(&Barrier::new(0));
363 child_handle.recv();
364
365 info!("trigger child process action C and wait for completion");
366 child_handle.send(&Barrier::new(0));
367 let challenge = child_handle.recv().0;
368
369 // Add a fake auth token with the challenge value.
370 auth_service.addAuthToken(&fake_lskf_token_with_challenge(GK_FAKE_SID, challenge)).unwrap();
371
372 info!("trigger child process action D and wait for completion");
373 child_handle.send(&Barrier::new(0));
374 child_handle.recv();
375
376 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
377}
378
379#[test]
380fn test_unlocked_device_required() {
381 android_logger::init_once(
382 android_logger::Config::default()
383 .with_tag("keystore2_client_tests")
384 .with_max_level(log::LevelFilter::Debug),
385 );
386
387 let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
388 writer: &mut ChannelWriter<BarrierReached>|
389 -> Result<(), String> {
390 let ks2 = get_keystore_service();
391 if ks2.getInterfaceVersion().unwrap() < 4 {
392 // Assuming `IKeystoreAuthorization::onDeviceLocked` and
393 // `IKeystoreAuthorization::onDeviceUnlocked` APIs will be supported on devices
394 // with `IKeystoreService` >= 4.
395 return Ok(());
396 }
397
398 // Now we're in a new process, wait to be notified before starting.
399 reader.recv();
400
401 // Action A: create a new unlocked-device-required key (which thus requires
402 // super-encryption), while the device is unlocked.
403 let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
404 let params = AuthSetBuilder::new()
405 .no_auth_required()
406 .unlocked_device_required()
407 .algorithm(Algorithm::EC)
408 .purpose(KeyPurpose::SIGN)
409 .purpose(KeyPurpose::VERIFY)
410 .digest(Digest::SHA_2_256)
411 .ec_curve(EcCurve::P_256);
412
413 let KeyMetadata { key, .. } = sec_level
414 .generateKey(
415 &KeyDescriptor {
416 domain: Domain::APP,
417 nspace: -1,
418 alias: Some("unlocked-device-required".to_string()),
419 blob: None,
420 },
421 None,
422 &params,
423 0,
424 b"entropy",
425 )
426 .expect("key generation failed");
427 info!("A: created unlocked-device-required key while unlocked {key:?}");
428 writer.send(&BarrierReached {}); // A done.
429
430 // Action B: fail to use the unlocked-device-required key while locked.
431 reader.recv();
432 let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
433 let result = sec_level.createOperation(&key, &params, UNFORCED);
434 info!("B: use unlocked-device-required key while locked => {result:?}");
435 assert_km_error(&result, ErrorCode::DEVICE_LOCKED);
436 writer.send(&BarrierReached {}); // B done.
437
438 // Action C: try to use the unlocked-device-required key while unlocked with a
439 // password.
440 reader.recv();
441 let result = sec_level.createOperation(&key, &params, UNFORCED);
442 info!("C: use unlocked-device-required key while lskf-unlocked => {result:?}");
443 assert!(result.is_ok(), "failed with {result:?}");
444 abort_op(result);
445 writer.send(&BarrierReached {}); // C done.
446
447 // Action D: try to use the unlocked-device-required key while unlocked with a weak
448 // biometric.
449 reader.recv();
450 let result = sec_level.createOperation(&key, &params, UNFORCED);
451 info!("D: use unlocked-device-required key while weak-locked => {result:?}");
452 assert!(result.is_ok(), "createOperation failed: {result:?}");
453 abort_op(result);
454 writer.send(&BarrierReached {}); // D done.
455
456 Ok(())
457 };
458
459 // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
460 // `--test-threads=1`), and nothing yet done with binder.
461 let mut child_handle = unsafe {
462 // Perform keystore actions while running as the test user.
463 run_as::run_as_child(CTX, Uid::from_raw(UID), Gid::from_raw(UID), child_fn)
David Drysdalef7ed95a2024-05-08 13:51:45 +0100464 }
465 .unwrap();
466
Rajesh Nyamagoudca4f7af2024-07-29 18:39:01 +0000467 let ks2 = get_keystore_service();
468 if ks2.getInterfaceVersion().unwrap() < 4 {
469 // Assuming `IKeystoreAuthorization::onDeviceLocked` and
470 // `IKeystoreAuthorization::onDeviceUnlocked` APIs will be supported on devices
471 // with `IKeystoreService` >= 4.
472 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
473 return;
474 }
David Drysdalef7ed95a2024-05-08 13:51:45 +0100475 // Now that the separate process has been forked off, it's safe to use binder.
476 let user = TestUser::new();
477 let user_id = user.id;
478 let auth_service = get_authorization();
479
480 // Lock and unlock to ensure super keys are already created.
David Drysdale89e87d52024-10-04 13:07:43 +0100481 auth_service
482 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
483 .unwrap();
484 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
485 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100486
487 info!("trigger child process action A while unlocked and wait for completion");
488 child_handle.send(&BarrierReached {});
489 child_handle.recv();
490
491 // Move to locked and don't allow weak unlock, so super keys are wiped.
David Drysdale89e87d52024-10-04 13:07:43 +0100492 auth_service
493 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
494 .unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100495
496 info!("trigger child process action B while locked and wait for completion");
497 child_handle.send(&BarrierReached {});
498 child_handle.recv();
499
500 // Unlock with password => loads super key from database.
David Drysdale89e87d52024-10-04 13:07:43 +0100501 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
502 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100503
504 info!("trigger child process action C while lskf-unlocked and wait for completion");
505 child_handle.send(&BarrierReached {});
506 child_handle.recv();
507
508 // Move to locked and allow weak unlock, then do a weak unlock.
David Drysdale89e87d52024-10-04 13:07:43 +0100509 auth_service
510 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_ENABLED)
511 .unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100512 auth_service.onDeviceUnlocked(user_id, None).unwrap();
513
514 info!("trigger child process action D while weak-unlocked and wait for completion");
515 child_handle.send(&BarrierReached {});
516 child_handle.recv();
517
518 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
519}
520
521/// Generate a fake [`HardwareAuthToken`] for the given sid.
522fn fake_lskf_token(gk_sid: i64) -> HardwareAuthToken {
David Drysdale89e87d52024-10-04 13:07:43 +0100523 fake_lskf_token_with_challenge(gk_sid, 0)
524}
525
526/// Generate a fake [`HardwareAuthToken`] for the given sid and challenge.
527fn fake_lskf_token_with_challenge(gk_sid: i64, challenge: i64) -> HardwareAuthToken {
528 HardwareAuthToken {
529 challenge,
530 userId: gk_sid,
531 authenticatorId: 0,
532 authenticatorType: HardwareAuthenticatorType::PASSWORD,
533 timestamp: Timestamp { milliSeconds: 123 },
534 mac: vec![1, 2, 3],
535 }
536}
537
538/// Generate a fake [`HardwareAuthToken`] for the given sids
539fn fake_bio_lskf_token(gk_sid: i64, bio_sid: i64) -> HardwareAuthToken {
David Drysdalef7ed95a2024-05-08 13:51:45 +0100540 HardwareAuthToken {
541 challenge: 0,
542 userId: gk_sid,
David Drysdale89e87d52024-10-04 13:07:43 +0100543 authenticatorId: bio_sid,
David Drysdalef7ed95a2024-05-08 13:51:45 +0100544 authenticatorType: HardwareAuthenticatorType::PASSWORD,
545 timestamp: Timestamp { milliSeconds: 123 },
546 mac: vec![1, 2, 3],
547 }
548}