David Drysdale | f7ed95a | 2024-05-08 13:51:45 +0100 | [diff] [blame] | 1 | // 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 | |
| 17 | use crate::keystore2_client_test_utils::BarrierReached; |
| 18 | use android_security_authorization::aidl::android::security::authorization::{ |
| 19 | IKeystoreAuthorization::IKeystoreAuthorization |
| 20 | }; |
| 21 | use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::{ |
| 22 | IKeystoreMaintenance, |
| 23 | }; |
| 24 | use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ |
| 25 | Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, HardwareAuthToken::HardwareAuthToken, |
| 26 | HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel, |
| 27 | KeyPurpose::KeyPurpose |
| 28 | }; |
| 29 | use android_system_keystore2::aidl::android::system::keystore2::{ |
| 30 | CreateOperationResponse::CreateOperationResponse, Domain::Domain, KeyDescriptor::KeyDescriptor, |
| 31 | KeyMetadata::KeyMetadata, |
| 32 | }; |
| 33 | use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ |
| 34 | Timestamp::Timestamp, |
| 35 | }; |
| 36 | use keystore2_test_utils::{ |
| 37 | get_keystore_service, run_as, authorizations::AuthSetBuilder, |
| 38 | }; |
| 39 | use log::{warn, info}; |
| 40 | use nix::unistd::{Gid, Uid}; |
| 41 | use rustutils::users::AID_USER_OFFSET; |
| 42 | |
| 43 | /// Test user ID. |
| 44 | const TEST_USER_ID: i32 = 100; |
| 45 | /// Fake password blob. |
| 46 | static PASSWORD: &[u8] = &[ |
| 47 | 0x42, 0x39, 0x30, 0x37, 0x44, 0x37, 0x32, 0x37, 0x39, 0x39, 0x43, 0x42, 0x39, 0x41, 0x42, 0x30, |
| 48 | 0x34, 0x31, 0x30, 0x38, 0x46, 0x44, 0x33, 0x45, 0x39, 0x42, 0x32, 0x38, 0x36, 0x35, 0x41, 0x36, |
| 49 | 0x33, 0x44, 0x42, 0x42, 0x43, 0x36, 0x33, 0x42, 0x34, 0x39, 0x37, 0x33, 0x35, 0x45, 0x41, 0x41, |
| 50 | 0x32, 0x45, 0x31, 0x35, 0x43, 0x43, 0x46, 0x32, 0x39, 0x36, 0x33, 0x34, 0x31, 0x32, 0x41, 0x39, |
| 51 | ]; |
| 52 | /// Fake SID value corresponding to Gatekeeper. |
| 53 | static GK_SID: i64 = 123456; |
| 54 | /// Fake SID value corresponding to a biometric authenticator. |
| 55 | static BIO_SID1: i64 = 345678; |
| 56 | /// Fake SID value corresponding to a biometric authenticator. |
| 57 | static BIO_SID2: i64 = 456789; |
| 58 | |
| 59 | const WEAK_UNLOCK_ENABLED: bool = true; |
| 60 | const WEAK_UNLOCK_DISABLED: bool = false; |
| 61 | const UNFORCED: bool = false; |
| 62 | |
| 63 | fn get_authorization() -> binder::Strong<dyn IKeystoreAuthorization> { |
| 64 | binder::get_interface("android.security.authorization").unwrap() |
| 65 | } |
| 66 | |
| 67 | fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> { |
| 68 | binder::get_interface("android.security.maintenance").unwrap() |
| 69 | } |
| 70 | |
| 71 | fn abort_op(result: binder::Result<CreateOperationResponse>) { |
| 72 | if let Ok(rsp) = result { |
| 73 | if let Some(op) = rsp.iOperation { |
| 74 | if let Err(e) = op.abort() { |
| 75 | warn!("abort op failed: {e:?}"); |
| 76 | } |
| 77 | } else { |
| 78 | warn!("can't abort op with missing iOperation"); |
| 79 | } |
| 80 | } else { |
| 81 | warn!("can't abort failed op: {result:?}"); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | /// RAII structure to ensure that test users are removed at the end of a test. |
| 86 | struct TestUser { |
| 87 | id: i32, |
| 88 | maint: binder::Strong<dyn IKeystoreMaintenance>, |
| 89 | } |
| 90 | |
| 91 | impl TestUser { |
| 92 | fn new() -> Self { |
| 93 | Self::new_user(TEST_USER_ID, PASSWORD) |
| 94 | } |
| 95 | fn new_user(user_id: i32, password: &[u8]) -> Self { |
| 96 | let maint = get_maintenance(); |
| 97 | maint.onUserAdded(user_id).expect("failed to add test user"); |
| 98 | maint |
| 99 | .initUserSuperKeys(user_id, password, /* allowExisting= */ false) |
| 100 | .expect("failed to init test user"); |
| 101 | Self { id: user_id, maint } |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | impl Drop for TestUser { |
| 106 | fn drop(&mut self) { |
| 107 | let _ = self.maint.onUserRemoved(self.id); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | #[test] |
| 112 | fn keystore2_test_unlocked_device_required() { |
| 113 | android_logger::init_once( |
| 114 | android_logger::Config::default() |
| 115 | .with_tag("keystore2_client_tests") |
| 116 | .with_max_level(log::LevelFilter::Debug), |
| 117 | ); |
| 118 | static CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; |
| 119 | const UID: u32 = TEST_USER_ID as u32 * AID_USER_OFFSET + 1001; |
| 120 | |
| 121 | // Safety: only one thread at this point, and nothing yet done with binder. |
| 122 | let mut child_handle = unsafe { |
| 123 | // Perform keystore actions while running as the test user. |
| 124 | run_as::run_as_child( |
| 125 | CTX, |
| 126 | Uid::from_raw(UID), |
| 127 | Gid::from_raw(UID), |
| 128 | move |reader, writer| -> Result<(), String> { |
| 129 | // Now we're in a new process, wait to be notified before starting. |
| 130 | reader.recv(); |
| 131 | |
| 132 | // Action A: create a new unlocked-device-required key (which thus requires |
| 133 | // super-encryption), while the device is unlocked. |
| 134 | let ks2 = get_keystore_service(); |
| 135 | let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); |
| 136 | let params = AuthSetBuilder::new() |
| 137 | .unlocked_device_required() |
| 138 | .algorithm(Algorithm::EC) |
| 139 | .purpose(KeyPurpose::SIGN) |
| 140 | .purpose(KeyPurpose::VERIFY) |
| 141 | .digest(Digest::SHA_2_256) |
| 142 | .ec_curve(EcCurve::P_256); |
| 143 | |
| 144 | let KeyMetadata { key, .. } = sec_level |
| 145 | .generateKey( |
| 146 | &KeyDescriptor { |
| 147 | domain: Domain::APP, |
| 148 | nspace: -1, |
| 149 | alias: Some("unlocked-device-required".to_string()), |
| 150 | blob: None, |
| 151 | }, |
| 152 | None, |
| 153 | ¶ms, |
| 154 | 0, |
| 155 | b"entropy", |
| 156 | ) |
| 157 | .expect("key generation failed"); |
| 158 | info!("A: created unlocked-device-required key while unlocked {key:?}"); |
| 159 | writer.send(&BarrierReached {}); // A done. |
| 160 | |
| 161 | // Action B: fail to use the unlocked-device-required key while locked. |
| 162 | reader.recv(); |
| 163 | let params = |
| 164 | AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256); |
| 165 | let result = sec_level.createOperation(&key, ¶ms, UNFORCED); |
| 166 | info!("B: use unlocked-device-required key while locked => {result:?}"); |
| 167 | assert!(result.is_err()); |
| 168 | writer.send(&BarrierReached {}); // B done. |
| 169 | |
| 170 | // Action C: try to use the unlocked-device-required key while unlocked with a |
| 171 | // password. |
| 172 | reader.recv(); |
| 173 | let result = sec_level.createOperation(&key, ¶ms, UNFORCED); |
| 174 | info!("C: use unlocked-device-required key while lskf-unlocked => {result:?}"); |
| 175 | assert!(result.is_ok(), "failed with {result:?}"); |
| 176 | abort_op(result); |
| 177 | writer.send(&BarrierReached {}); // C done. |
| 178 | |
| 179 | // Action D: try to use the unlocked-device-required key while unlocked with a weak |
| 180 | // biometric. |
| 181 | reader.recv(); |
| 182 | let result = sec_level.createOperation(&key, ¶ms, UNFORCED); |
| 183 | info!("D: use unlocked-device-required key while weak-locked => {result:?}"); |
| 184 | assert!(result.is_ok(), "createOperation failed: {result:?}"); |
| 185 | abort_op(result); |
| 186 | writer.send(&BarrierReached {}); // D done. |
| 187 | |
| 188 | let _ = sec_level.deleteKey(&key); |
| 189 | Ok(()) |
| 190 | }, |
| 191 | ) |
| 192 | } |
| 193 | .unwrap(); |
| 194 | |
| 195 | // Now that the separate process has been forked off, it's safe to use binder. |
| 196 | let user = TestUser::new(); |
| 197 | let user_id = user.id; |
| 198 | let auth_service = get_authorization(); |
| 199 | |
| 200 | // Lock and unlock to ensure super keys are already created. |
| 201 | auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_DISABLED).unwrap(); |
| 202 | auth_service.onDeviceUnlocked(user_id, Some(PASSWORD)).unwrap(); |
| 203 | auth_service.addAuthToken(&fake_lskf_token(GK_SID)).unwrap(); |
| 204 | |
| 205 | info!("trigger child process action A while unlocked and wait for completion"); |
| 206 | child_handle.send(&BarrierReached {}); |
| 207 | child_handle.recv(); |
| 208 | |
| 209 | // Move to locked and don't allow weak unlock, so super keys are wiped. |
| 210 | auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_DISABLED).unwrap(); |
| 211 | |
| 212 | info!("trigger child process action B while locked and wait for completion"); |
| 213 | child_handle.send(&BarrierReached {}); |
| 214 | child_handle.recv(); |
| 215 | |
| 216 | // Unlock with password => loads super key from database. |
| 217 | auth_service.onDeviceUnlocked(user_id, Some(PASSWORD)).unwrap(); |
| 218 | auth_service.addAuthToken(&fake_lskf_token(GK_SID)).unwrap(); |
| 219 | |
| 220 | info!("trigger child process action C while lskf-unlocked and wait for completion"); |
| 221 | child_handle.send(&BarrierReached {}); |
| 222 | child_handle.recv(); |
| 223 | |
| 224 | // Move to locked and allow weak unlock, then do a weak unlock. |
| 225 | auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_ENABLED).unwrap(); |
| 226 | auth_service.onDeviceUnlocked(user_id, None).unwrap(); |
| 227 | |
| 228 | info!("trigger child process action D while weak-unlocked and wait for completion"); |
| 229 | child_handle.send(&BarrierReached {}); |
| 230 | child_handle.recv(); |
| 231 | |
| 232 | assert_eq!(child_handle.get_result(), Ok(()), "child process failed"); |
| 233 | } |
| 234 | |
| 235 | /// Generate a fake [`HardwareAuthToken`] for the given sid. |
| 236 | fn fake_lskf_token(gk_sid: i64) -> HardwareAuthToken { |
| 237 | HardwareAuthToken { |
| 238 | challenge: 0, |
| 239 | userId: gk_sid, |
| 240 | authenticatorId: 0, |
| 241 | authenticatorType: HardwareAuthenticatorType::PASSWORD, |
| 242 | timestamp: Timestamp { milliSeconds: 123 }, |
| 243 | mac: vec![1, 2, 3], |
| 244 | } |
| 245 | } |