// Copyright 2020, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! This crate provides access control primitives for Keystore 2.0. //! It provides high level functions for checking permissions in the keystore2 and keystore2_key //! SELinux classes based on the keystore2_selinux backend. //! It also provides KeystorePerm and KeyPerm as convenience wrappers for the SELinux permission //! defined by keystore2 and keystore2_key respectively. use crate::error::Error as KsError; use crate::error::ResponseCode; use crate::ks_err; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, }; use anyhow::Context as AnyhowContext; use keystore2_selinux as selinux; use selinux::{implement_class, Backend, ClassPermission}; use std::cmp::PartialEq; use std::convert::From; use std::ffi::CStr; use std::sync::LazyLock; // Replace getcon with a mock in the test situation #[cfg(not(test))] use selinux::getcon; #[cfg(test)] use tests::test_getcon as getcon; #[cfg(test)] mod tests; // Panicking here is allowed because keystore cannot function without this backend // and it would happen early and indicate a gross misconfiguration of the device. static KEYSTORE2_KEY_LABEL_BACKEND: LazyLock = LazyLock::new(|| selinux::KeystoreKeyBackend::new().unwrap()); fn lookup_keystore2_key_context(namespace: i64) -> anyhow::Result { KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string()) } implement_class!( /// KeyPerm provides a convenient abstraction from the SELinux class `keystore2_key`. /// At the same time it maps `KeyPermissions` from the Keystore 2.0 AIDL Grant interface to /// the SELinux permissions. #[repr(i32)] #[selinux(class_name = keystore2_key)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum KeyPerm { /// Checked when convert_storage_key_to_ephemeral is called. #[selinux(name = convert_storage_key_to_ephemeral)] ConvertStorageKeyToEphemeral = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0, /// Checked when the caller tries do delete a key. #[selinux(name = delete)] Delete = KeyPermission::DELETE.0, /// Checked when the caller tries to use a unique id. #[selinux(name = gen_unique_id)] GenUniqueId = KeyPermission::GEN_UNIQUE_ID.0, /// Checked when the caller tries to load a key. #[selinux(name = get_info)] GetInfo = KeyPermission::GET_INFO.0, /// Checked when the caller attempts to grant a key to another uid. /// Also used for gating key migration attempts. #[selinux(name = grant)] Grant = KeyPermission::GRANT.0, /// Checked when the caller attempts to use Domain::BLOB. #[selinux(name = manage_blob)] ManageBlob = KeyPermission::MANAGE_BLOB.0, /// Checked when the caller tries to create a key which implies rebinding /// an alias to the new key. #[selinux(name = rebind)] Rebind = KeyPermission::REBIND.0, /// Checked when the caller attempts to create a forced operation. #[selinux(name = req_forced_op)] ReqForcedOp = KeyPermission::REQ_FORCED_OP.0, /// Checked when the caller attempts to update public key artifacts. #[selinux(name = update)] Update = KeyPermission::UPDATE.0, /// Checked when the caller attempts to use a private or public key. #[selinux(name = use)] Use = KeyPermission::USE.0, /// Does nothing, and is not checked. For use of device identifiers, /// the caller must hold the READ_PRIVILEGED_PHONE_STATE Android /// permission. #[selinux(name = use_dev_id)] UseDevId = KeyPermission::USE_DEV_ID.0, } ); implement_class!( /// KeystorePerm provides a convenient abstraction from the SELinux class `keystore2`. /// Using the implement_permission macro we get the same features as `KeyPerm`. #[selinux(class_name = keystore2)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum KeystorePerm { /// Checked when a new auth token is installed. #[selinux(name = add_auth)] AddAuth, /// Checked when an app is uninstalled or wiped. #[selinux(name = clear_ns)] ClearNs, /// Checked when Keystore 2.0 is asked to list a namespace that the caller /// does not have the get_info permission for. #[selinux(name = list)] List, /// Checked when Keystore 2.0 gets locked. #[selinux(name = lock)] Lock, /// Checked when Keystore 2.0 shall be reset. #[selinux(name = reset)] Reset, /// Checked when Keystore 2.0 shall be unlocked. #[selinux(name = unlock)] Unlock, /// Checked when user is added or removed. #[selinux(name = change_user)] ChangeUser, /// Checked when password of the user is changed. #[selinux(name = change_password)] ChangePassword, /// Checked when a UID is cleared. #[selinux(name = clear_uid)] ClearUID, /// Checked when Credstore calls IKeystoreAuthorization to obtain auth tokens. #[selinux(name = get_auth_token)] GetAuthToken, /// Checked when earlyBootEnded() is called. #[selinux(name = early_boot_ended)] EarlyBootEnded, /// Checked when IKeystoreMetrics::pullMetrics is called. #[selinux(name = pull_metrics)] PullMetrics, /// Checked when IKeystoreMaintenance::deleteAllKeys is called. #[selinux(name = delete_all_keys)] DeleteAllKeys, /// Checked on calls to IRemotelyProvisionedKeyPool::getAttestationKey #[selinux(name = get_attestation_key)] GetAttestationKey, /// Checked on IKeystoreAuthorization::getLastAuthTime() is called. #[selinux(name = get_last_auth_time)] GetLastAuthTime, } ); /// Represents a set of `KeyPerm` permissions. /// `IntoIterator` is implemented for this struct allowing the iteration through all the /// permissions in the set. /// It also implements a function `includes(self, other)` that checks if the permissions /// in `other` are included in `self`. /// /// KeyPermSet can be created with the macro `key_perm_set![]`. /// /// ## Example /// ``` /// let perms1 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob, KeyPerm::Grant]; /// let perms2 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob]; /// /// assert!(perms1.includes(perms2)) /// assert!(!perms2.includes(perms1)) /// /// let i = perms1.into_iter(); /// // iteration in ascending order of the permission's numeric representation. /// assert_eq(Some(KeyPerm::ManageBlob), i.next()); /// assert_eq(Some(KeyPerm::Grant), i.next()); /// assert_eq(Some(KeyPerm::Use), i.next()); /// assert_eq(None, i.next()); /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct KeyPermSet(pub i32); mod perm { use super::*; pub struct IntoIter { vec: KeyPermSet, pos: u8, } impl IntoIter { pub fn new(v: KeyPermSet) -> Self { Self { vec: v, pos: 0 } } } impl std::iter::Iterator for IntoIter { type Item = KeyPerm; fn next(&mut self) -> Option { loop { if self.pos == 32 { return None; } let p = self.vec.0 & (1 << self.pos); self.pos += 1; if p != 0 { return Some(KeyPerm::from(p)); } } } } } impl From for KeyPermSet { fn from(p: KeyPerm) -> Self { Self(p as i32) } } /// allow conversion from the AIDL wire type i32 to a permission set. impl From for KeyPermSet { fn from(p: i32) -> Self { Self(p) } } impl From for i32 { fn from(p: KeyPermSet) -> i32 { p.0 } } impl KeyPermSet { /// Returns true iff this permission set has all of the permissions that are in `other`. pub fn includes>(&self, other: T) -> bool { let o: KeyPermSet = other.into(); (self.0 & o.0) == o.0 } } /// This macro can be used to create a `KeyPermSet` from a list of `KeyPerm` values. /// /// ## Example /// ``` /// let v = key_perm_set![Perm::delete(), Perm::manage_blob()]; /// ``` #[macro_export] macro_rules! key_perm_set { () => { KeyPermSet(0) }; ($head:expr $(, $tail:expr)* $(,)?) => { KeyPermSet($head as i32 $(| $tail as i32)*) }; } impl IntoIterator for KeyPermSet { type Item = KeyPerm; type IntoIter = perm::IntoIter; fn into_iter(self) -> Self::IntoIter { Self::IntoIter::new(self) } } /// Uses `selinux::check_permission` to check if the given caller context `caller_cxt` may access /// the given permision `perm` of the `keystore2` security class. pub fn check_keystore_permission(caller_ctx: &CStr, perm: KeystorePerm) -> anyhow::Result<()> { let target_context = getcon().context("check_keystore_permission: getcon failed.")?; selinux::check_permission(caller_ctx, &target_context, perm) } /// Uses `selinux::check_permission` to check if the given caller context `caller_cxt` has /// all the permissions indicated in `access_vec` for the target domain indicated by the key /// descriptor `key` in the security class `keystore2_key`. /// /// Also checks if the caller has the grant permission for the given target domain. /// /// Attempts to grant the grant permission are always denied. /// /// The only viable target domains are /// * `Domain::APP` in which case u:r:keystore:s0 is used as target context and /// * `Domain::SELINUX` in which case the `key.nspace` parameter is looked up in /// SELinux keystore key backend, and the result is used /// as target context. pub fn check_grant_permission( caller_uid: u32, caller_ctx: &CStr, access_vec: KeyPermSet, key: &KeyDescriptor, ) -> anyhow::Result<()> { let target_context = match key.domain { Domain::APP => { if caller_uid as i64 != key.nspace { return Err(selinux::Error::perm()) .context("Trying to access key without ownership."); } getcon().context("check_grant_permission: getcon failed.")? } Domain::SELINUX => lookup_keystore2_key_context(key.nspace) .context("check_grant_permission: Domain::SELINUX: Failed to lookup namespace.")?, _ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)), }; selinux::check_permission(caller_ctx, &target_context, KeyPerm::Grant) .context("Grant permission is required when granting.")?; if access_vec.includes(KeyPerm::Grant) { return Err(selinux::Error::perm()).context("Grant permission cannot be granted."); } for p in access_vec.into_iter() { selinux::check_permission(caller_ctx, &target_context, p).context(ks_err!( "check_permission failed. \ The caller may have tried to grant a permission that they don't possess. {:?}", p ))? } Ok(()) } /// Uses `selinux::check_permission` to check if the given caller context `caller_cxt` /// has the permissions indicated by `perm` for the target domain indicated by the key /// descriptor `key` in the security class `keystore2_key`. /// /// The behavior differs slightly depending on the selected target domain: /// * `Domain::APP` u:r:keystore:s0 is used as target context. /// * `Domain::SELINUX` `key.nspace` parameter is looked up in the SELinux keystore key /// backend, and the result is used as target context. /// * `Domain::BLOB` Same as SELinux but the "manage_blob" permission is always checked additionally /// to the one supplied in `perm`. /// * `Domain::GRANT` Does not use selinux::check_permission. Instead the `access_vector` /// parameter is queried for permission, which must be supplied in this case. /// /// ## Return values. /// * Ok(()) If the requested permissions were granted. /// * Err(selinux::Error::perm()) If the requested permissions were denied. /// * Err(KsError::sys()) This error is produced if `Domain::GRANT` is selected but no `access_vec` /// was supplied. It is also produced if `Domain::KEY_ID` was selected, and /// on various unexpected backend failures. pub fn check_key_permission( caller_uid: u32, caller_ctx: &CStr, perm: KeyPerm, key: &KeyDescriptor, access_vector: &Option, ) -> anyhow::Result<()> { // If an access vector was supplied, the key is either accessed by GRANT or by KEY_ID. // In the former case, key.domain was set to GRANT and we check the failure cases // further below. If the access is requested by KEY_ID, key.domain would have been // resolved to APP or SELINUX depending on where the key actually resides. // Either way we can return here immediately if the access vector covers the requested // permission. If it does not, we can still check if the caller has access by means of // ownership. if let Some(access_vector) = access_vector { if access_vector.includes(perm) { return Ok(()); } } let target_context = match key.domain { // apps get the default keystore context Domain::APP => { if caller_uid as i64 != key.nspace { return Err(selinux::Error::perm()) .context("Trying to access key without ownership."); } getcon().context(ks_err!("getcon failed."))? } Domain::SELINUX => lookup_keystore2_key_context(key.nspace) .context(ks_err!("Domain::SELINUX: Failed to lookup namespace."))?, Domain::GRANT => { match access_vector { Some(_) => { return Err(selinux::Error::perm()) .context(format!("\"{}\" not granted", perm.name())); } None => { // If DOMAIN_GRANT was selected an access vector must be supplied. return Err(KsError::sys()).context(ks_err!( "Cannot check permission for Domain::GRANT without access vector.", )); } } } Domain::KEY_ID => { // We should never be called with `Domain::KEY_ID. The database // lookup should have converted this into one of `Domain::APP` // or `Domain::SELINUX`. return Err(KsError::sys()) .context(ks_err!("Cannot check permission for Domain::KEY_ID.",)); } Domain::BLOB => { let tctx = lookup_keystore2_key_context(key.nspace) .context(ks_err!("Domain::BLOB: Failed to lookup namespace."))?; // If DOMAIN_KEY_BLOB was specified, we check for the "manage_blob" // permission in addition to the requested permission. selinux::check_permission(caller_ctx, &tctx, KeyPerm::ManageBlob)?; tctx } _ => { return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) .context(format!("Unknown domain value: \"{:?}\".", key.domain)) } }; selinux::check_permission(caller_ctx, &target_context, perm) }