blob: 301f9dc9c9a7aef83e4ccadbb24defce22f56b28 [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};
David Drysdaleacd3d982024-10-04 16:18:16 +010029use android_hardware_gatekeeper::aidl::android::hardware::gatekeeper::{
30 IGatekeeper::IGatekeeper, IGatekeeper::ERROR_RETRY_TIMEOUT,
31};
David Drysdalef7ed95a2024-05-08 13:51:45 +010032use android_system_keystore2::aidl::android::system::keystore2::{
33 CreateOperationResponse::CreateOperationResponse, Domain::Domain, KeyDescriptor::KeyDescriptor,
34 KeyMetadata::KeyMetadata,
35};
David Drysdaleacd3d982024-10-04 16:18:16 +010036use android_system_keystore2::binder::{ExceptionCode, Result as BinderResult};
David Drysdalef7ed95a2024-05-08 13:51:45 +010037use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
38 Timestamp::Timestamp,
39};
40use keystore2_test_utils::{
David Drysdaleacd3d982024-10-04 16:18:16 +010041 authorizations::AuthSetBuilder, get_keystore_service, run_as,
42 run_as::{ChannelReader, ChannelWriter}, key_generations::assert_km_error,
David Drysdalef7ed95a2024-05-08 13:51:45 +010043};
44use log::{warn, info};
45use nix::unistd::{Gid, Uid};
46use rustutils::users::AID_USER_OFFSET;
David Drysdale89e87d52024-10-04 13:07:43 +010047use std::{time::Duration, thread::sleep};
David Drysdalef7ed95a2024-05-08 13:51:45 +010048
David Drysdale89e87d52024-10-04 13:07:43 +010049/// SELinux context.
50const CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
David Drysdalef7ed95a2024-05-08 13:51:45 +010051/// Test user ID.
52const TEST_USER_ID: i32 = 100;
David Drysdale89e87d52024-10-04 13:07:43 +010053/// Corresponding uid value.
54const UID: u32 = TEST_USER_ID as u32 * AID_USER_OFFSET + 1001;
55/// Fake synthetic password blob.
56static SYNTHETIC_PASSWORD: &[u8] = &[
David Drysdalef7ed95a2024-05-08 13:51:45 +010057 0x42, 0x39, 0x30, 0x37, 0x44, 0x37, 0x32, 0x37, 0x39, 0x39, 0x43, 0x42, 0x39, 0x41, 0x42, 0x30,
58 0x34, 0x31, 0x30, 0x38, 0x46, 0x44, 0x33, 0x45, 0x39, 0x42, 0x32, 0x38, 0x36, 0x35, 0x41, 0x36,
59 0x33, 0x44, 0x42, 0x42, 0x43, 0x36, 0x33, 0x42, 0x34, 0x39, 0x37, 0x33, 0x35, 0x45, 0x41, 0x41,
60 0x32, 0x45, 0x31, 0x35, 0x43, 0x43, 0x46, 0x32, 0x39, 0x36, 0x33, 0x34, 0x31, 0x32, 0x41, 0x39,
61];
David Drysdaleacd3d982024-10-04 16:18:16 +010062/// Gatekeeper password.
63static GK_PASSWORD: &[u8] = b"correcthorsebatterystaple";
David Drysdalef7ed95a2024-05-08 13:51:45 +010064/// Fake SID value corresponding to Gatekeeper.
David Drysdale89e87d52024-10-04 13:07:43 +010065static GK_FAKE_SID: i64 = 123456;
David Drysdalef7ed95a2024-05-08 13:51:45 +010066/// Fake SID value corresponding to a biometric authenticator.
David Drysdale89e87d52024-10-04 13:07:43 +010067static BIO_FAKE_SID1: i64 = 345678;
David Drysdalef7ed95a2024-05-08 13:51:45 +010068/// Fake SID value corresponding to a biometric authenticator.
David Drysdale89e87d52024-10-04 13:07:43 +010069static BIO_FAKE_SID2: i64 = 456789;
David Drysdalef7ed95a2024-05-08 13:51:45 +010070
71const WEAK_UNLOCK_ENABLED: bool = true;
72const WEAK_UNLOCK_DISABLED: bool = false;
73const UNFORCED: bool = false;
74
75fn get_authorization() -> binder::Strong<dyn IKeystoreAuthorization> {
76 binder::get_interface("android.security.authorization").unwrap()
77}
78
79fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> {
80 binder::get_interface("android.security.maintenance").unwrap()
81}
82
David Drysdaleacd3d982024-10-04 16:18:16 +010083/// Get the default Gatekeeper instance. This may fail on older devices where Gatekeeper is still a
84/// HIDL interface rather than AIDL.
85fn get_gatekeeper() -> Option<binder::Strong<dyn IGatekeeper>> {
86 binder::get_interface("android.hardware.gatekeeper.IGatekeeper/default").ok()
87}
88
89/// Indicate whether a Gatekeeper result indicates a delayed-retry is needed.
90fn is_gk_retry<T: std::fmt::Debug>(result: &BinderResult<T>) -> bool {
91 matches!(result, Err(s) if s.exception_code() == ExceptionCode::SERVICE_SPECIFIC
92 && s.service_specific_error() == ERROR_RETRY_TIMEOUT)
93}
94
David Drysdalef7ed95a2024-05-08 13:51:45 +010095fn abort_op(result: binder::Result<CreateOperationResponse>) {
96 if let Ok(rsp) = result {
97 if let Some(op) = rsp.iOperation {
98 if let Err(e) = op.abort() {
99 warn!("abort op failed: {e:?}");
100 }
101 } else {
102 warn!("can't abort op with missing iOperation");
103 }
104 } else {
105 warn!("can't abort failed op: {result:?}");
106 }
107}
108
109/// RAII structure to ensure that test users are removed at the end of a test.
110struct TestUser {
111 id: i32,
112 maint: binder::Strong<dyn IKeystoreMaintenance>,
David Drysdaleacd3d982024-10-04 16:18:16 +0100113 gk: Option<binder::Strong<dyn IGatekeeper>>,
114 gk_sid: Option<i64>,
115 gk_handle: Vec<u8>,
David Drysdalef7ed95a2024-05-08 13:51:45 +0100116}
117
118impl TestUser {
119 fn new() -> Self {
David Drysdale89e87d52024-10-04 13:07:43 +0100120 Self::new_user(TEST_USER_ID, SYNTHETIC_PASSWORD)
David Drysdalef7ed95a2024-05-08 13:51:45 +0100121 }
David Drysdaleacd3d982024-10-04 16:18:16 +0100122 fn new_user(user_id: i32, sp: &[u8]) -> Self {
David Drysdalef7ed95a2024-05-08 13:51:45 +0100123 let maint = get_maintenance();
124 maint.onUserAdded(user_id).expect("failed to add test user");
125 maint
David Drysdaleacd3d982024-10-04 16:18:16 +0100126 .initUserSuperKeys(user_id, sp, /* allowExisting= */ false)
David Drysdalef7ed95a2024-05-08 13:51:45 +0100127 .expect("failed to init test user");
David Drysdaleacd3d982024-10-04 16:18:16 +0100128 let gk = get_gatekeeper();
129 let (gk_sid, gk_handle) = if let Some(gk) = &gk {
130 // AIDL Gatekeeper is available, so enroll a password.
131 loop {
132 let result = gk.enroll(user_id, &[], &[], GK_PASSWORD);
133 if is_gk_retry(&result) {
134 sleep(Duration::from_secs(1));
135 continue;
136 }
137 let rsp = result.expect("gk.enroll() failed");
138 info!("registered test user {user_id} as sid {} with GK", rsp.secureUserId);
139 break (Some(rsp.secureUserId), rsp.data);
140 }
141 } else {
142 (None, vec![])
143 };
144 Self { id: user_id, maint, gk, gk_sid, gk_handle }
145 }
146
147 /// Perform Gatekeeper verification, which will return a HAT on success.
148 fn gk_verify(&self, challenge: i64) -> Option<HardwareAuthToken> {
149 let Some(gk) = &self.gk else { return None };
150 loop {
151 let result = gk.verify(self.id, challenge, &self.gk_handle, GK_PASSWORD);
152 if is_gk_retry(&result) {
153 sleep(Duration::from_secs(1));
154 continue;
155 }
156 let rsp = result.expect("gk.verify failed");
157 break Some(rsp.hardwareAuthToken);
158 }
David Drysdalef7ed95a2024-05-08 13:51:45 +0100159 }
160}
161
162impl Drop for TestUser {
163 fn drop(&mut self) {
164 let _ = self.maint.onUserRemoved(self.id);
David Drysdaleacd3d982024-10-04 16:18:16 +0100165 if let Some(gk) = &self.gk {
166 info!("deregister test user {} with GK", self.id);
167 if let Err(e) = gk.deleteUser(self.id) {
168 warn!("failed to deregister test user {}: {e:?}", self.id);
169 }
170 }
David Drysdalef7ed95a2024-05-08 13:51:45 +0100171 }
172}
173
174#[test]
David Drysdaleacd3d982024-10-04 16:18:16 +0100175fn test_auth_bound_timeout_with_gk() {
176 type Barrier = BarrierReachedWithData<Option<i64>>;
177 android_logger::init_once(
178 android_logger::Config::default()
179 .with_tag("keystore2_client_tests")
180 .with_max_level(log::LevelFilter::Debug),
181 );
182
183 let child_fn = move |reader: &mut ChannelReader<Barrier>,
184 writer: &mut ChannelWriter<Barrier>|
185 -> Result<(), String> {
186 // Now we're in a new process, wait to be notified before starting.
187 let gk_sid: i64 = match reader.recv().0 {
188 Some(sid) => sid,
189 None => {
190 // There is no AIDL Gatekeeper available, so abandon the test. It would be nice to
191 // know this before starting the child process, but finding it out requires Binder,
192 // which can't be used until after the child has forked.
193 return Ok(());
194 }
195 };
196
197 // Action A: create a new auth-bound key which requires auth in the last 3 seconds,
198 // and fail to start an operation using it.
199 let ks2 = get_keystore_service();
200 let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
201 let params = AuthSetBuilder::new()
202 .user_secure_id(gk_sid)
203 .user_secure_id(BIO_FAKE_SID1)
204 .user_secure_id(BIO_FAKE_SID2)
205 .user_auth_type(HardwareAuthenticatorType::ANY)
206 .auth_timeout(3)
207 .algorithm(Algorithm::EC)
208 .purpose(KeyPurpose::SIGN)
209 .purpose(KeyPurpose::VERIFY)
210 .digest(Digest::SHA_2_256)
211 .ec_curve(EcCurve::P_256);
212
213 let KeyMetadata { key, .. } = sec_level
214 .generateKey(
215 &KeyDescriptor {
216 domain: Domain::APP,
217 nspace: -1,
218 alias: Some("auth-bound-timeout".to_string()),
219 blob: None,
220 },
221 None,
222 &params,
223 0,
224 b"entropy",
225 )
226 .expect("key generation failed");
227 info!("A: created auth-timeout key {key:?}");
228
229 // No HATs so cannot create an operation using the key.
230 let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
231 let result = sec_level.createOperation(&key, &params, UNFORCED);
232 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
233 info!("A: failed auth-bound operation (no HAT) as expected {result:?}");
234
235 writer.send(&Barrier::new(None)); // A done.
236
237 // Action B: succeed when a valid HAT is available.
238 reader.recv();
239
240 let result = sec_level.createOperation(&key, &params, UNFORCED);
241 assert!(result.is_ok());
242 let op = result.unwrap().iOperation.expect("no operation in result");
243 let result = op.finish(Some(b"data"), None);
244 assert!(result.is_ok());
245 info!("B: performed auth-bound operation (with valid GK HAT) as expected");
246
247 writer.send(&Barrier::new(None)); // B done.
248
249 // Action C: fail again when the HAT is old enough to not even be checked.
250 reader.recv();
251 info!("C: wait so that any HAT times out");
252 sleep(Duration::from_secs(4));
253 let result = sec_level.createOperation(&key, &params, UNFORCED);
254 info!("C: failed auth-bound operation (HAT is too old) as expected {result:?}");
255 writer.send(&Barrier::new(None)); // C done.
256
257 Ok(())
258 };
259
260 // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
261 // `--test-threads=1`), and nothing yet done with binder.
262 let mut child_handle = unsafe {
263 // Perform keystore actions while running as the test user.
264 run_as::run_as_child(CTX, Uid::from_raw(UID), Gid::from_raw(UID), child_fn)
265 }
266 .unwrap();
267
268 // Now that the separate process has been forked off, it's safe to use binder to setup a test
269 // user.
270 let _ks2 = get_keystore_service();
271 let user = TestUser::new();
272 if user.gk.is_none() {
273 // Can't run this test if there's no AIDL Gatekeeper.
274 child_handle.send(&Barrier::new(None));
275 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
276 return;
277 }
278 let user_id = user.id;
279 let auth_service = get_authorization();
280
281 // Lock and unlock to ensure super keys are already created.
282 auth_service
283 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
284 .unwrap();
285 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
286
287 info!("trigger child process action A and wait for completion");
288 child_handle.send(&Barrier::new(Some(user.gk_sid.unwrap())));
289 child_handle.recv();
290
291 // Unlock with GK password to get a genuine auth token.
292 let real_hat = user.gk_verify(0).expect("failed to perform GK verify");
293 auth_service.addAuthToken(&real_hat).unwrap();
294
295 info!("trigger child process action B and wait for completion");
296 child_handle.send(&Barrier::new(None));
297 child_handle.recv();
298
299 info!("trigger child process action C and wait for completion");
300 child_handle.send(&Barrier::new(None));
301 child_handle.recv();
302
303 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
304}
305
306#[test]
David Drysdale89e87d52024-10-04 13:07:43 +0100307fn test_auth_bound_timeout_failure() {
David Drysdalef7ed95a2024-05-08 13:51:45 +0100308 android_logger::init_once(
309 android_logger::Config::default()
310 .with_tag("keystore2_client_tests")
311 .with_max_level(log::LevelFilter::Debug),
312 );
David Drysdalef7ed95a2024-05-08 13:51:45 +0100313
David Drysdale89e87d52024-10-04 13:07:43 +0100314 let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
315 writer: &mut ChannelWriter<BarrierReached>|
316 -> Result<(), String> {
317 // Now we're in a new process, wait to be notified before starting.
318 reader.recv();
319
320 // Action A: create a new auth-bound key which requires auth in the last 3 seconds,
321 // and fail to start an operation using it.
322 let ks2 = get_keystore_service();
323
324 let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
325 let params = AuthSetBuilder::new()
326 .user_secure_id(BIO_FAKE_SID1)
327 .user_secure_id(BIO_FAKE_SID2)
328 .user_auth_type(HardwareAuthenticatorType::ANY)
329 .auth_timeout(3)
330 .algorithm(Algorithm::EC)
331 .purpose(KeyPurpose::SIGN)
332 .purpose(KeyPurpose::VERIFY)
333 .digest(Digest::SHA_2_256)
334 .ec_curve(EcCurve::P_256);
335
336 let KeyMetadata { key, .. } = sec_level
337 .generateKey(
338 &KeyDescriptor {
339 domain: Domain::APP,
340 nspace: -1,
341 alias: Some("auth-bound-timeout".to_string()),
342 blob: None,
343 },
344 None,
345 &params,
346 0,
347 b"entropy",
348 )
349 .expect("key generation failed");
350 info!("A: created auth-timeout key {key:?}");
351
352 // No HATs so cannot create an operation using the key.
353 let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
354 let result = sec_level.createOperation(&key, &params, UNFORCED);
355 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
356 info!("A: failed auth-bound operation (no HAT) as expected {result:?}");
357
358 writer.send(&BarrierReached {}); // A done.
359
360 // Action B: fail again when an invalid HAT is available.
361 reader.recv();
362
363 let result = sec_level.createOperation(&key, &params, UNFORCED);
364 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
365 info!("B: failed auth-bound operation (HAT is invalid) as expected {result:?}");
366
367 writer.send(&BarrierReached {}); // B done.
368
369 // Action C: fail again when the HAT is old enough to not even be checked.
370 reader.recv();
371 info!("C: wait so that any HAT times out");
372 sleep(Duration::from_secs(4));
373 let result = sec_level.createOperation(&key, &params, UNFORCED);
374 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
375 info!("C: failed auth-bound operation (HAT is too old) as expected {result:?}");
376 writer.send(&BarrierReached {}); // C done.
377
378 Ok(())
379 };
380
381 // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
382 // `--test-threads=1`), and nothing yet done with binder.
David Drysdalef7ed95a2024-05-08 13:51:45 +0100383 let mut child_handle = unsafe {
384 // Perform keystore actions while running as the test user.
David Drysdale89e87d52024-10-04 13:07:43 +0100385 run_as::run_as_child(CTX, Uid::from_raw(UID), Gid::from_raw(UID), child_fn)
386 }
387 .unwrap();
Rajesh Nyamagoudca4f7af2024-07-29 18:39:01 +0000388
David Drysdale89e87d52024-10-04 13:07:43 +0100389 // Now that the separate process has been forked off, it's safe to use binder to setup a test
390 // user.
391 let _ks2 = get_keystore_service();
392 let user = TestUser::new();
393 let user_id = user.id;
394 let auth_service = get_authorization();
Rajesh Nyamagoudca4f7af2024-07-29 18:39:01 +0000395
David Drysdale89e87d52024-10-04 13:07:43 +0100396 // Lock and unlock to ensure super keys are already created.
397 auth_service
398 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
399 .unwrap();
400 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
401 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100402
David Drysdale89e87d52024-10-04 13:07:43 +0100403 info!("trigger child process action A and wait for completion");
404 child_handle.send(&BarrierReached {});
405 child_handle.recv();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100406
David Drysdale89e87d52024-10-04 13:07:43 +0100407 // Unlock with password and a fake auth token that matches the key
408 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
409 auth_service.addAuthToken(&fake_bio_lskf_token(GK_FAKE_SID, BIO_FAKE_SID1)).unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100410
David Drysdale89e87d52024-10-04 13:07:43 +0100411 info!("trigger child process action B and wait for completion");
412 child_handle.send(&BarrierReached {});
413 child_handle.recv();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100414
David Drysdale89e87d52024-10-04 13:07:43 +0100415 info!("trigger child process action C and wait for completion");
416 child_handle.send(&BarrierReached {});
417 child_handle.recv();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100418
David Drysdale89e87d52024-10-04 13:07:43 +0100419 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
420}
421
422#[test]
David Drysdaleacd3d982024-10-04 16:18:16 +0100423fn test_auth_bound_per_op_with_gk() {
424 type Barrier = BarrierReachedWithData<Option<i64>>;
425 android_logger::init_once(
426 android_logger::Config::default()
427 .with_tag("keystore2_client_tests")
428 .with_max_level(log::LevelFilter::Debug),
429 );
430
431 let child_fn = move |reader: &mut ChannelReader<Barrier>,
432 writer: &mut ChannelWriter<Barrier>|
433 -> Result<(), String> {
434 // Now we're in a new process, wait to be notified before starting.
435 let gk_sid: i64 = match reader.recv().0 {
436 Some(sid) => sid,
437 None => {
438 // There is no AIDL Gatekeeper available, so abandon the test. It would be nice to
439 // know this before starting the child process, but finding it out requires Binder,
440 // which can't be used until after the child has forked.
441 return Ok(());
442 }
443 };
444
445 // Action A: create a new auth-bound key which requires auth-per-operation (because
446 // AUTH_TIMEOUT is not specified), and fail to finish an operation using it.
447 let ks2 = get_keystore_service();
448 let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
449 let params = AuthSetBuilder::new()
450 .user_secure_id(gk_sid)
451 .user_secure_id(BIO_FAKE_SID1)
452 .user_auth_type(HardwareAuthenticatorType::ANY)
453 .algorithm(Algorithm::EC)
454 .purpose(KeyPurpose::SIGN)
455 .purpose(KeyPurpose::VERIFY)
456 .digest(Digest::SHA_2_256)
457 .ec_curve(EcCurve::P_256);
458
459 let KeyMetadata { key, .. } = sec_level
460 .generateKey(
461 &KeyDescriptor {
462 domain: Domain::APP,
463 nspace: -1,
464 alias: Some("auth-per-op".to_string()),
465 blob: None,
466 },
467 None,
468 &params,
469 0,
470 b"entropy",
471 )
472 .expect("key generation failed");
473 info!("A: created auth-per-op key {key:?}");
474
475 // We can create an operation using the key...
476 let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
477 let result = sec_level
478 .createOperation(&key, &params, UNFORCED)
479 .expect("failed to create auth-per-op operation");
480 let op = result.iOperation.expect("no operation in result");
481 info!("A: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
482
483 // .. but attempting to finish the operation fails because Keystore can't find a HAT.
484 let result = op.finish(Some(b"data"), None);
485 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
486 info!("A: failed auth-per-op op (no HAT) as expected {result:?}");
487
488 writer.send(&Barrier::new(None)); // A done.
489
490 // Action B: start an operation and pass out the challenge
491 reader.recv();
492 let result = sec_level
493 .createOperation(&key, &params, UNFORCED)
494 .expect("failed to create auth-per-op operation");
495 let op = result.iOperation.expect("no operation in result");
496 info!("B: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
497 writer.send(&Barrier::new(Some(result.operationChallenge.unwrap().challenge))); // B done.
498
499 // Action C: finishing the operation succeeds now there's a per-op HAT.
500 reader.recv();
501 let result = op.finish(Some(b"data"), None);
502 assert!(result.is_ok());
503 info!("C: performed auth-per-op op expected");
504 writer.send(&Barrier::new(None)); // D done.
505
506 Ok(())
507 };
508
509 // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
510 // `--test-threads=1`), and nothing yet done with binder.
511 let mut child_handle = unsafe {
512 // Perform keystore actions while running as the test user.
513 run_as::run_as_child(CTX, Uid::from_raw(UID), Gid::from_raw(UID), child_fn)
514 }
515 .unwrap();
516
517 // Now that the separate process has been forked off, it's safe to use binder to setup a test
518 // user.
519 let _ks2 = get_keystore_service();
520 let user = TestUser::new();
521 if user.gk.is_none() {
522 // Can't run this test if there's no AIDL Gatekeeper.
523 child_handle.send(&Barrier::new(None));
524 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
525 return;
526 }
527 let user_id = user.id;
528 let auth_service = get_authorization();
529
530 // Lock and unlock to ensure super keys are already created.
531 auth_service
532 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
533 .unwrap();
534 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
535
536 info!("trigger child process action A and wait for completion");
537 child_handle.send(&Barrier::new(Some(user.gk_sid.unwrap())));
538 child_handle.recv();
539
540 info!("trigger child process action B and wait for completion");
541 child_handle.send(&Barrier::new(None));
542 let challenge = child_handle.recv().0.expect("no challenge");
543
544 // Unlock with GK and the challenge to get a genuine per-op auth token
545 let real_hat = user.gk_verify(challenge).expect("failed to perform GK verify");
546 auth_service.addAuthToken(&real_hat).unwrap();
547
548 info!("trigger child process action C and wait for completion");
549 child_handle.send(&Barrier::new(None));
550 child_handle.recv();
551
552 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
553}
554
555#[test]
David Drysdale89e87d52024-10-04 13:07:43 +0100556fn test_auth_bound_per_op_failure() {
557 type Barrier = BarrierReachedWithData<i64>;
558 android_logger::init_once(
559 android_logger::Config::default()
560 .with_tag("keystore2_client_tests")
561 .with_max_level(log::LevelFilter::Debug),
562 );
563
564 let child_fn = move |reader: &mut ChannelReader<Barrier>,
565 writer: &mut ChannelWriter<Barrier>|
566 -> Result<(), String> {
567 // Now we're in a new process, wait to be notified before starting.
568 reader.recv();
569
570 // Action A: create a new auth-bound key which requires auth-per-operation (because
571 // AUTH_TIMEOUT is not specified), and fail to finish an operation using it.
572 let ks2 = get_keystore_service();
573
574 let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
575 let params = AuthSetBuilder::new()
576 .user_secure_id(GK_FAKE_SID)
577 .user_secure_id(BIO_FAKE_SID1)
578 .user_auth_type(HardwareAuthenticatorType::ANY)
579 .algorithm(Algorithm::EC)
580 .purpose(KeyPurpose::SIGN)
581 .purpose(KeyPurpose::VERIFY)
582 .digest(Digest::SHA_2_256)
583 .ec_curve(EcCurve::P_256);
584
585 let KeyMetadata { key, .. } = sec_level
586 .generateKey(
587 &KeyDescriptor {
588 domain: Domain::APP,
589 nspace: -1,
590 alias: Some("auth-per-op".to_string()),
591 blob: None,
592 },
593 None,
594 &params,
595 0,
596 b"entropy",
597 )
598 .expect("key generation failed");
599 info!("A: created auth-per-op key {key:?}");
600
601 // We can create an operation using the key...
602 let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
603 let result = sec_level
604 .createOperation(&key, &params, UNFORCED)
605 .expect("failed to create auth-per-op operation");
606 let op = result.iOperation.expect("no operation in result");
607 info!("A: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
608
609 // .. but attempting to finish the operation fails because Keystore can't find a HAT.
610 let result = op.finish(Some(b"data"), None);
611 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
612 info!("A: failed auth-per-op op (no HAT) as expected {result:?}");
613
614 writer.send(&Barrier::new(0)); // A done.
615
616 // Action B: fail again when an irrelevant HAT is available.
617 reader.recv();
618
619 let result = sec_level
620 .createOperation(&key, &params, UNFORCED)
621 .expect("failed to create auth-per-op operation");
622 let op = result.iOperation.expect("no operation in result");
623 info!("B: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
624 // The operation fails because the HAT that Keystore received is not related to the
625 // challenge.
626 let result = op.finish(Some(b"data"), None);
627 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
628 info!("B: failed auth-per-op op (HAT is not per-op) as expected {result:?}");
629
630 writer.send(&Barrier::new(0)); // B done.
631
632 // Action C: start an operation and pass out the challenge
633 reader.recv();
634 let result = sec_level
635 .createOperation(&key, &params, UNFORCED)
636 .expect("failed to create auth-per-op operation");
637 let op = result.iOperation.expect("no operation in result");
638 info!("C: created auth-per-op operation, got challenge {:?}", result.operationChallenge);
639 writer.send(&Barrier::new(result.operationChallenge.unwrap().challenge)); // C done.
640
641 // Action D: finishing the operation still fails because the per-op HAT
642 // is invalid (the HMAC signature is faked and so the secure world
643 // rejects the HAT).
644 reader.recv();
645 let result = op.finish(Some(b"data"), None);
646 assert_km_error(&result, ErrorCode::KEY_USER_NOT_AUTHENTICATED);
647 info!("D: failed auth-per-op op (HAT is per-op but invalid) as expected {result:?}");
648 writer.send(&Barrier::new(0)); // D done.
649
650 Ok(())
651 };
652
653 // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
654 // `--test-threads=1`), and nothing yet done with binder.
655 let mut child_handle = unsafe {
656 // Perform keystore actions while running as the test user.
657 run_as::run_as_child(CTX, Uid::from_raw(UID), Gid::from_raw(UID), child_fn)
658 }
659 .unwrap();
660
661 // Now that the separate process has been forked off, it's safe to use binder to setup a test
662 // user.
663 let _ks2 = get_keystore_service();
664 let user = TestUser::new();
665 let user_id = user.id;
666 let auth_service = get_authorization();
667
668 // Lock and unlock to ensure super keys are already created.
669 auth_service
670 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
671 .unwrap();
672 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
673 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
674
675 info!("trigger child process action A and wait for completion");
676 child_handle.send(&Barrier::new(0));
677 child_handle.recv();
678
679 // Unlock with password and a fake auth token.
680 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
681 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
682
683 info!("trigger child process action B and wait for completion");
684 child_handle.send(&Barrier::new(0));
685 child_handle.recv();
686
687 info!("trigger child process action C and wait for completion");
688 child_handle.send(&Barrier::new(0));
689 let challenge = child_handle.recv().0;
690
691 // Add a fake auth token with the challenge value.
692 auth_service.addAuthToken(&fake_lskf_token_with_challenge(GK_FAKE_SID, challenge)).unwrap();
693
694 info!("trigger child process action D and wait for completion");
695 child_handle.send(&Barrier::new(0));
696 child_handle.recv();
697
698 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
699}
700
701#[test]
702fn test_unlocked_device_required() {
703 android_logger::init_once(
704 android_logger::Config::default()
705 .with_tag("keystore2_client_tests")
706 .with_max_level(log::LevelFilter::Debug),
707 );
708
709 let child_fn = move |reader: &mut ChannelReader<BarrierReached>,
710 writer: &mut ChannelWriter<BarrierReached>|
711 -> Result<(), String> {
712 let ks2 = get_keystore_service();
713 if ks2.getInterfaceVersion().unwrap() < 4 {
714 // Assuming `IKeystoreAuthorization::onDeviceLocked` and
715 // `IKeystoreAuthorization::onDeviceUnlocked` APIs will be supported on devices
716 // with `IKeystoreService` >= 4.
717 return Ok(());
718 }
719
720 // Now we're in a new process, wait to be notified before starting.
721 reader.recv();
722
723 // Action A: create a new unlocked-device-required key (which thus requires
724 // super-encryption), while the device is unlocked.
725 let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
726 let params = AuthSetBuilder::new()
727 .no_auth_required()
728 .unlocked_device_required()
729 .algorithm(Algorithm::EC)
730 .purpose(KeyPurpose::SIGN)
731 .purpose(KeyPurpose::VERIFY)
732 .digest(Digest::SHA_2_256)
733 .ec_curve(EcCurve::P_256);
734
735 let KeyMetadata { key, .. } = sec_level
736 .generateKey(
737 &KeyDescriptor {
738 domain: Domain::APP,
739 nspace: -1,
740 alias: Some("unlocked-device-required".to_string()),
741 blob: None,
742 },
743 None,
744 &params,
745 0,
746 b"entropy",
747 )
748 .expect("key generation failed");
749 info!("A: created unlocked-device-required key while unlocked {key:?}");
750 writer.send(&BarrierReached {}); // A done.
751
752 // Action B: fail to use the unlocked-device-required key while locked.
753 reader.recv();
754 let params = AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
755 let result = sec_level.createOperation(&key, &params, UNFORCED);
756 info!("B: use unlocked-device-required key while locked => {result:?}");
757 assert_km_error(&result, ErrorCode::DEVICE_LOCKED);
758 writer.send(&BarrierReached {}); // B done.
759
760 // Action C: try to use the unlocked-device-required key while unlocked with a
761 // password.
762 reader.recv();
763 let result = sec_level.createOperation(&key, &params, UNFORCED);
764 info!("C: use unlocked-device-required key while lskf-unlocked => {result:?}");
765 assert!(result.is_ok(), "failed with {result:?}");
766 abort_op(result);
767 writer.send(&BarrierReached {}); // C done.
768
769 // Action D: try to use the unlocked-device-required key while unlocked with a weak
770 // biometric.
771 reader.recv();
772 let result = sec_level.createOperation(&key, &params, UNFORCED);
773 info!("D: use unlocked-device-required key while weak-locked => {result:?}");
774 assert!(result.is_ok(), "createOperation failed: {result:?}");
775 abort_op(result);
776 writer.send(&BarrierReached {}); // D done.
777
778 Ok(())
779 };
780
781 // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
782 // `--test-threads=1`), and nothing yet done with binder.
783 let mut child_handle = unsafe {
784 // Perform keystore actions while running as the test user.
785 run_as::run_as_child(CTX, Uid::from_raw(UID), Gid::from_raw(UID), child_fn)
David Drysdalef7ed95a2024-05-08 13:51:45 +0100786 }
787 .unwrap();
788
Rajesh Nyamagoudca4f7af2024-07-29 18:39:01 +0000789 let ks2 = get_keystore_service();
790 if ks2.getInterfaceVersion().unwrap() < 4 {
791 // Assuming `IKeystoreAuthorization::onDeviceLocked` and
792 // `IKeystoreAuthorization::onDeviceUnlocked` APIs will be supported on devices
793 // with `IKeystoreService` >= 4.
794 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
795 return;
796 }
David Drysdalef7ed95a2024-05-08 13:51:45 +0100797 // Now that the separate process has been forked off, it's safe to use binder.
798 let user = TestUser::new();
799 let user_id = user.id;
800 let auth_service = get_authorization();
801
802 // Lock and unlock to ensure super keys are already created.
David Drysdale89e87d52024-10-04 13:07:43 +0100803 auth_service
804 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
805 .unwrap();
806 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
807 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100808
809 info!("trigger child process action A while unlocked and wait for completion");
810 child_handle.send(&BarrierReached {});
811 child_handle.recv();
812
813 // Move to locked and don't allow weak unlock, so super keys are wiped.
David Drysdale89e87d52024-10-04 13:07:43 +0100814 auth_service
815 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_DISABLED)
816 .unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100817
818 info!("trigger child process action B while locked and wait for completion");
819 child_handle.send(&BarrierReached {});
820 child_handle.recv();
821
822 // Unlock with password => loads super key from database.
David Drysdale89e87d52024-10-04 13:07:43 +0100823 auth_service.onDeviceUnlocked(user_id, Some(SYNTHETIC_PASSWORD)).unwrap();
824 auth_service.addAuthToken(&fake_lskf_token(GK_FAKE_SID)).unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100825
826 info!("trigger child process action C while lskf-unlocked and wait for completion");
827 child_handle.send(&BarrierReached {});
828 child_handle.recv();
829
830 // Move to locked and allow weak unlock, then do a weak unlock.
David Drysdale89e87d52024-10-04 13:07:43 +0100831 auth_service
832 .onDeviceLocked(user_id, &[BIO_FAKE_SID1, BIO_FAKE_SID2], WEAK_UNLOCK_ENABLED)
833 .unwrap();
David Drysdalef7ed95a2024-05-08 13:51:45 +0100834 auth_service.onDeviceUnlocked(user_id, None).unwrap();
835
836 info!("trigger child process action D while weak-unlocked and wait for completion");
837 child_handle.send(&BarrierReached {});
838 child_handle.recv();
839
840 assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
841}
842
843/// Generate a fake [`HardwareAuthToken`] for the given sid.
844fn fake_lskf_token(gk_sid: i64) -> HardwareAuthToken {
David Drysdale89e87d52024-10-04 13:07:43 +0100845 fake_lskf_token_with_challenge(gk_sid, 0)
846}
847
848/// Generate a fake [`HardwareAuthToken`] for the given sid and challenge.
849fn fake_lskf_token_with_challenge(gk_sid: i64, challenge: i64) -> HardwareAuthToken {
850 HardwareAuthToken {
851 challenge,
852 userId: gk_sid,
853 authenticatorId: 0,
854 authenticatorType: HardwareAuthenticatorType::PASSWORD,
855 timestamp: Timestamp { milliSeconds: 123 },
856 mac: vec![1, 2, 3],
857 }
858}
859
860/// Generate a fake [`HardwareAuthToken`] for the given sids
861fn fake_bio_lskf_token(gk_sid: i64, bio_sid: i64) -> HardwareAuthToken {
David Drysdalef7ed95a2024-05-08 13:51:45 +0100862 HardwareAuthToken {
863 challenge: 0,
864 userId: gk_sid,
David Drysdale89e87d52024-10-04 13:07:43 +0100865 authenticatorId: bio_sid,
David Drysdalef7ed95a2024-05-08 13:51:45 +0100866 authenticatorType: HardwareAuthenticatorType::PASSWORD,
867 timestamp: Timestamp { milliSeconds: 123 },
868 mac: vec![1, 2, 3],
869 }
870}