diff --git a/keystore2/Android.bp b/keystore2/Android.bp index 32493c0e..9dc34d27 100644 --- a/keystore2/Android.bp +++ b/keystore2/Android.bp @@ -108,7 +108,7 @@ rust_binary { "libbinder_rs", "libkeystore2", "liblog_rust", - "libvpnprofilestore-rust", + "liblegacykeystore-rust", ], init_rc: ["keystore2.rc"], diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp index 06fdb48e..5e4e22c3 100644 --- a/keystore2/aidl/Android.bp +++ b/keystore2/aidl/Android.bp @@ -151,8 +151,8 @@ aidl_interface { } aidl_interface { - name: "android.security.vpnprofilestore", - srcs: [ "android/security/vpnprofilestore/*.aidl" ], + name: "android.security.legacykeystore", + srcs: [ "android/security/legacykeystore/*.aidl" ], unstable: true, backend: { java: { @@ -162,6 +162,10 @@ aidl_interface { rust: { enabled: true, }, + ndk: { + enabled: true, + apps_enabled: false, + } }, } diff --git a/keystore2/aidl/android/security/legacykeystore/ILegacyKeystore.aidl b/keystore2/aidl/android/security/legacykeystore/ILegacyKeystore.aidl new file mode 100644 index 00000000..fe93673e --- /dev/null +++ b/keystore2/aidl/android/security/legacykeystore/ILegacyKeystore.aidl @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 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. + */ + +package android.security.legacykeystore; + +/** + * Internal interface for accessing and storing legacy keystore blobs. + * Before Android S, Keystore offered a key-value store that was intended for storing + * data associated with certain types of keys. E.g., public certificates for asymmetric keys. + * This key value store no longer exists as part of the Keystore 2.0 protocol. + * However, there are some clients that used Keystore in an unintended way. + * This interface exists to give these clients a grace period to migrate their keys + * out of legacy keystore. In Android S, this legacy keystore may be used as keystore was + * used in earlier versions, and provides access to entries that were put into keystore + * before Android S. + * + * DEPRECATION NOTICE: In Android T, the `put` function is slated to be removed. + * This will allow clients to use the `get`, `list`, and `remove` API to migrate blobs out + * of legacy keystore. + * @hide + */ +interface ILegacyKeystore { + + /** + * Special value indicating the callers uid. + */ + const int UID_SELF = -1; + + /** + * Service specific error code indicating that an unexpected system error occurred. + */ + const int ERROR_SYSTEM_ERROR = 4; + + /** + * Service specific error code indicating that the caller does not have the + * right to access the requested uid. + */ + const int ERROR_PERMISSION_DENIED = 6; + + /** + * Service specific error code indicating that the entry was not found. + */ + const int ERROR_ENTRY_NOT_FOUND = 7; + + /** + * Returns the blob stored under the given name. + * + * @param alias name of the blob entry. + * @param uid designates the legacy namespace. Specify UID_SELF for the caller's namespace. + * @return The unstructured blob that was passed as blob parameter into put() + */ + byte[] get(in String alias, int uid); + + /** + * Stores one entry as unstructured blob under the given alias. + * Overwrites existing entries with the same alias. + * + * @param alias name of the new entry. + * @param uid designates the legacy namespace. Specify UID_SELF for the caller's namespace. + * @param blob the payload of the new entry. + * + * IMPORTANT DEPRECATION NOTICE: This function is slated to be removed in Android T. + * Do not add new callers. The remaining functionality will remain for the purpose + * of migrating legacy configuration out. + */ + void put(in String alias, int uid, in byte[] blob); + + /** + * Deletes the entry under the given alias. + * + * @param alias name of the entry to be removed. + * @param uid designates the legacy namespace of the entry. Specify UID_SELF for the caller's + * namespace. + */ + void remove(in String alias, int uid); + + /** + * Returns a list of aliases of entries stored. The list is filtered by prefix. + * The resulting strings are the full aliases including the prefix. + * + * @param prefix used to filter results. + * @param uid legacy namespace to list. Specify UID_SELF for caller's namespace. + */ + String[] list(in String prefix, int uid); +} diff --git a/keystore2/aidl/android/security/vpnprofilestore/IVpnProfileStore.aidl b/keystore2/aidl/android/security/vpnprofilestore/IVpnProfileStore.aidl deleted file mode 100644 index 8375b7b9..00000000 --- a/keystore2/aidl/android/security/vpnprofilestore/IVpnProfileStore.aidl +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -package android.security.vpnprofilestore; - -/** - * Internal interface for accessing and storing VPN profiles. - * @hide - */ -interface IVpnProfileStore { - /** - * Service specific error code indicating that the profile was not found. - */ - const int ERROR_PROFILE_NOT_FOUND = 1; - - /** - * Service specific error code indicating that an unexpected system error occurred. - */ - const int ERROR_SYSTEM_ERROR = 2; - - /** - * Returns the profile stored under the given alias. - * - * @param alias name of the profile. - * @return The unstructured blob that was passed as profile parameter into put() - */ - byte[] get(in String alias); - - /** - * Stores one profile as unstructured blob under the given alias. - */ - void put(in String alias, in byte[] profile); - - /** - * Deletes the profile under the given alias. - */ - void remove(in String alias); - - /** - * Returns a list of aliases of profiles stored. The list is filtered by prefix. - * The resulting strings are the full aliases including the prefix. - */ - String[] list(in String prefix); -} \ No newline at end of file diff --git a/keystore2/vpnprofilestore/Android.bp b/keystore2/legacykeystore/Android.bp similarity index 85% rename from keystore2/vpnprofilestore/Android.bp rename to keystore2/legacykeystore/Android.bp index 7ddf0d68..c2890cc6 100644 --- a/keystore2/vpnprofilestore/Android.bp +++ b/keystore2/legacykeystore/Android.bp @@ -22,13 +22,13 @@ package { } rust_library { - name: "libvpnprofilestore-rust", - crate_name: "vpnprofilestore", + name: "liblegacykeystore-rust", + crate_name: "legacykeystore", srcs: [ "lib.rs", ], rustlibs: [ - "android.security.vpnprofilestore-rust", + "android.security.legacykeystore-rust", "libanyhow", "libbinder_rs", "libkeystore2", @@ -39,13 +39,13 @@ rust_library { } rust_test { - name: "vpnprofilestore_test", - crate_name: "vpnprofilestore", + name: "legacykeystore_test", + crate_name: "legacykeystore", srcs: ["lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, rustlibs: [ - "android.security.vpnprofilestore-rust", + "android.security.legacykeystore-rust", "libanyhow", "libbinder_rs", "libkeystore2", diff --git a/keystore2/vpnprofilestore/lib.rs b/keystore2/legacykeystore/lib.rs similarity index 63% rename from keystore2/vpnprofilestore/lib.rs rename to keystore2/legacykeystore/lib.rs index 548bec5e..8a4d7d82 100644 --- a/keystore2/vpnprofilestore/lib.rs +++ b/keystore2/legacykeystore/lib.rs @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Implements the android.security.vpnprofilestore interface. +//! Implements the android.security.legacykeystore interface. -use android_security_vpnprofilestore::aidl::android::security::vpnprofilestore::{ - IVpnProfileStore::BnVpnProfileStore, IVpnProfileStore::IVpnProfileStore, - IVpnProfileStore::ERROR_PROFILE_NOT_FOUND, IVpnProfileStore::ERROR_SYSTEM_ERROR, +use android_security_legacykeystore::aidl::android::security::legacykeystore::{ + ILegacyKeystore::BnLegacyKeystore, ILegacyKeystore::ILegacyKeystore, + ILegacyKeystore::ERROR_ENTRY_NOT_FOUND, ILegacyKeystore::ERROR_PERMISSION_DENIED, + ILegacyKeystore::ERROR_SYSTEM_ERROR, ILegacyKeystore::UID_SELF, }; -use android_security_vpnprofilestore::binder::{ +use android_security_legacykeystore::binder::{ BinderFeatures, ExceptionCode, Result as BinderResult, Status as BinderStatus, Strong, ThreadState, }; @@ -42,7 +43,7 @@ impl DB { conn: Connection::open(db_file).context("Failed to initialize SQLite connection.")?, }; - db.init_tables().context("Trying to initialize vpnstore db.")?; + db.init_tables().context("Trying to initialize legacy keystore db.")?; Ok(db) } @@ -110,11 +111,11 @@ impl DB { }) } - fn put(&mut self, caller_uid: u32, alias: &str, profile: &[u8]) -> Result<()> { + fn put(&mut self, caller_uid: u32, alias: &str, entry: &[u8]) -> Result<()> { self.with_transaction(TransactionBehavior::Immediate, |tx| { tx.execute( "INSERT OR REPLACE INTO profiles (owner, alias, profile) values (?, ?, ?)", - params![caller_uid, alias, profile,], + params![caller_uid, alias, entry,], ) .context("In put: Failed to insert or replace.")?; Ok(()) @@ -129,7 +130,7 @@ impl DB { |row| row.get(0), ) .optional() - .context("In get: failed loading profile.") + .context("In get: failed loading entry.") }) } @@ -145,11 +146,11 @@ impl DB { } } -/// This is the main VpnProfileStore error type, it wraps binder exceptions and the -/// VnpStore errors. +/// This is the main LegacyKeystore error type, it wraps binder exceptions and the +/// LegacyKeystore errors. #[derive(Debug, thiserror::Error, PartialEq)] pub enum Error { - /// Wraps a VpnProfileStore error code. + /// Wraps a LegacyKeystore error code. #[error("Error::Error({0:?})")] Error(i32), /// Wraps a Binder exception code other than a service specific exception. @@ -163,16 +164,21 @@ impl Error { Error::Error(ERROR_SYSTEM_ERROR) } - /// Short hand for `Error::Error(ERROR_PROFILE_NOT_FOUND)` + /// Short hand for `Error::Error(ERROR_ENTRY_NOT_FOUND)` pub fn not_found() -> Self { - Error::Error(ERROR_PROFILE_NOT_FOUND) + Error::Error(ERROR_ENTRY_NOT_FOUND) + } + + /// Short hand for `Error::Error(ERROR_PERMISSION_DENIED)` + pub fn perm() -> Self { + Error::Error(ERROR_PERMISSION_DENIED) } } -/// This function should be used by vpnprofilestore service calls to translate error conditions +/// This function should be used by legacykeystore service calls to translate error conditions /// into service specific exceptions. /// -/// All error conditions get logged by this function, except for ERROR_PROFILE_NOT_FOUND error. +/// All error conditions get logged by this function, except for ERROR_ENTRY_NOT_FOUND error. /// /// `Error::Error(x)` variants get mapped onto a service specific error code of `x`. /// @@ -189,8 +195,8 @@ where |e| { let root_cause = e.root_cause(); let (rc, log_error) = match root_cause.downcast_ref::() { - // Make the profile not found errors silent. - Some(Error::Error(ERROR_PROFILE_NOT_FOUND)) => (ERROR_PROFILE_NOT_FOUND, false), + // Make the entry not found errors silent. + Some(Error::Error(ERROR_ENTRY_NOT_FOUND)) => (ERROR_ENTRY_NOT_FOUND, false), Some(Error::Error(e)) => (*e, true), Some(Error::Binder(_, _)) | None => (ERROR_SYSTEM_ERROR, true), }; @@ -203,8 +209,8 @@ where ) } -/// Implements IVpnProfileStore AIDL interface. -pub struct VpnProfileStore { +/// Implements ILegacyKeystore AIDL interface. +pub struct LegacyKeystore { db_path: PathBuf, async_task: AsyncTask, } @@ -215,72 +221,93 @@ struct AsyncState { db_path: PathBuf, } -impl VpnProfileStore { - /// Creates a new VpnProfileStore instance. - pub fn new_native_binder(path: &Path) -> Strong { +impl LegacyKeystore { + /// Note: The filename was chosen before the purpose of this module was extended. + /// It is kept for backward compatibility with early adopters. + const LEGACY_KEYSTORE_FILE_NAME: &'static str = "vpnprofilestore.sqlite"; + + /// Creates a new LegacyKeystore instance. + pub fn new_native_binder(path: &Path) -> Strong { let mut db_path = path.to_path_buf(); - db_path.push("vpnprofilestore.sqlite"); + db_path.push(Self::LEGACY_KEYSTORE_FILE_NAME); let result = Self { db_path, async_task: Default::default() }; result.init_shelf(path); - BnVpnProfileStore::new_binder(result, BinderFeatures::default()) + BnLegacyKeystore::new_binder(result, BinderFeatures::default()) } fn open_db(&self) -> Result { DB::new(&self.db_path).context("In open_db: Failed to open db.") } - fn get(&self, alias: &str) -> Result> { - let mut db = self.open_db().context("In get.")?; + fn get_effective_uid(uid: i32) -> Result { + const AID_SYSTEM: u32 = 1000; + const AID_WIFI: u32 = 1010; let calling_uid = ThreadState::get_calling_uid(); + let uid = uid as u32; - if let Some(profile) = - db.get(calling_uid, alias).context("In get: Trying to load profile from DB.")? - { - return Ok(profile); + if uid == UID_SELF as u32 || uid == calling_uid { + Ok(calling_uid) + } else if calling_uid == AID_SYSTEM && uid == AID_WIFI { + // The only exception for legacy reasons is allowing SYSTEM to access + // the WIFI namespace. + // IMPORTANT: If you attempt to add more exceptions, it means you are adding + // more callers to this deprecated feature. DON'T! + Ok(AID_WIFI) + } else { + Err(Error::perm()).with_context(|| { + format!("In get_effective_uid: caller: {}, requested uid: {}.", calling_uid, uid) + }) } - if self.get_legacy(calling_uid, alias).context("In get: Trying to migrate legacy blob.")? { + } + + fn get(&self, alias: &str, uid: i32) -> Result> { + let mut db = self.open_db().context("In get.")?; + let uid = Self::get_effective_uid(uid).context("In get.")?; + + if let Some(entry) = db.get(uid, alias).context("In get: Trying to load entry from DB.")? { + return Ok(entry); + } + if self.get_legacy(uid, alias).context("In get: Trying to migrate legacy blob.")? { // If we were able to migrate a legacy blob try again. - if let Some(profile) = - db.get(calling_uid, alias).context("In get: Trying to load profile from DB.")? + if let Some(entry) = + db.get(uid, alias).context("In get: Trying to load entry from DB.")? { - return Ok(profile); + return Ok(entry); } } - Err(Error::not_found()).context("In get: No such profile.") + Err(Error::not_found()).context("In get: No such entry.") } - fn put(&self, alias: &str, profile: &[u8]) -> Result<()> { - let calling_uid = ThreadState::get_calling_uid(); - // In order to make sure that we don't have stale legacy profiles, make sure they are + fn put(&self, alias: &str, uid: i32, entry: &[u8]) -> Result<()> { + let uid = Self::get_effective_uid(uid).context("In put.")?; + // In order to make sure that we don't have stale legacy entries, make sure they are // migrated before replacing them. - let _ = self.get_legacy(calling_uid, alias); + let _ = self.get_legacy(uid, alias); let mut db = self.open_db().context("In put.")?; - db.put(calling_uid, alias, profile).context("In put: Trying to insert profile into DB.") + db.put(uid, alias, entry).context("In put: Trying to insert entry into DB.") } - fn remove(&self, alias: &str) -> Result<()> { - let calling_uid = ThreadState::get_calling_uid(); + fn remove(&self, alias: &str, uid: i32) -> Result<()> { + let uid = Self::get_effective_uid(uid).context("In remove.")?; let mut db = self.open_db().context("In remove.")?; - // In order to make sure that we don't have stale legacy profiles, make sure they are + // In order to make sure that we don't have stale legacy entries, make sure they are // migrated before removing them. - let _ = self.get_legacy(calling_uid, alias); - let removed = db - .remove(calling_uid, alias) - .context("In remove: Trying to remove profile from DB.")?; + let _ = self.get_legacy(uid, alias); + let removed = + db.remove(uid, alias).context("In remove: Trying to remove entry from DB.")?; if removed { Ok(()) } else { - Err(Error::not_found()).context("In remove: No such profile.") + Err(Error::not_found()).context("In remove: No such entry.") } } - fn list(&self, prefix: &str) -> Result> { + fn list(&self, prefix: &str, uid: i32) -> Result> { let mut db = self.open_db().context("In list.")?; - let calling_uid = ThreadState::get_calling_uid(); - let mut result = self.list_legacy(calling_uid).context("In list.")?; - result - .append(&mut db.list(calling_uid).context("In list: Trying to get list of profiles.")?); + let uid = Self::get_effective_uid(uid).context("In list.")?; + let mut result = self.list_legacy(uid).context("In list.")?; + result.append(&mut db.list(uid).context("In list: Trying to get list of entries.")?); result = result.into_iter().filter(|s| s.starts_with(prefix)).collect(); result.sort_unstable(); result.dedup(); @@ -291,7 +318,7 @@ impl VpnProfileStore { let mut db_path = path.to_path_buf(); self.async_task.queue_hi(move |shelf| { let legacy_loader = LegacyBlobLoader::new(&db_path); - db_path.push("vpnprofilestore.sqlite"); + db_path.push(Self::LEGACY_KEYSTORE_FILE_NAME); shelf.put(AsyncState { legacy_loader, db_path, recently_imported: Default::default() }); }) @@ -313,8 +340,8 @@ impl VpnProfileStore { self.do_serialized(move |state| { state .legacy_loader - .list_vpn_profiles(uid) - .context("Trying to list legacy vnp profiles.") + .list_legacy_keystore_entries(uid) + .context("Trying to list legacy keystore entries.") }) .context("In list_legacy.") } @@ -327,8 +354,8 @@ impl VpnProfileStore { } let mut db = DB::new(&state.db_path).context("In open_db: Failed to open db.")?; let migrated = - Self::migrate_one_legacy_profile(uid, &alias, &state.legacy_loader, &mut db) - .context("Trying to migrate legacy vpn profile.")?; + Self::migrate_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db) + .context("Trying to migrate legacy keystore entries.")?; if migrated { state.recently_imported.insert((uid, alias)); } @@ -337,21 +364,21 @@ impl VpnProfileStore { .context("In get_legacy.") } - fn migrate_one_legacy_profile( + fn migrate_one_legacy_entry( uid: u32, alias: &str, legacy_loader: &LegacyBlobLoader, db: &mut DB, ) -> Result { let blob = legacy_loader - .read_vpn_profile(uid, alias) - .context("In migrate_one_legacy_profile: Trying to read legacy vpn profile.")?; - if let Some(profile) = blob { - db.put(uid, alias, &profile) - .context("In migrate_one_legacy_profile: Trying to insert profile into DB.")?; + .read_legacy_keystore_entry(uid, alias) + .context("In migrate_one_legacy_entry: Trying to read legacy keystore entry.")?; + if let Some(entry) = blob { + db.put(uid, alias, &entry) + .context("In migrate_one_legacy_entry: Trying to insert entry into DB.")?; legacy_loader - .remove_vpn_profile(uid, alias) - .context("In migrate_one_legacy_profile: Trying to delete legacy profile.")?; + .remove_legacy_keystore_entry(uid, alias) + .context("In migrate_one_legacy_entry: Trying to delete legacy keystore entry.")?; Ok(true) } else { Ok(false) @@ -359,24 +386,24 @@ impl VpnProfileStore { } } -impl binder::Interface for VpnProfileStore {} +impl binder::Interface for LegacyKeystore {} -impl IVpnProfileStore for VpnProfileStore { - fn get(&self, alias: &str) -> BinderResult> { - let _wp = wd::watch_millis("IVpnProfileStore::get", 500); - map_or_log_err(self.get(alias), Ok) +impl ILegacyKeystore for LegacyKeystore { + fn get(&self, alias: &str, uid: i32) -> BinderResult> { + let _wp = wd::watch_millis("ILegacyKeystore::get", 500); + map_or_log_err(self.get(alias, uid), Ok) } - fn put(&self, alias: &str, profile: &[u8]) -> BinderResult<()> { - let _wp = wd::watch_millis("IVpnProfileStore::put", 500); - map_or_log_err(self.put(alias, profile), Ok) + fn put(&self, alias: &str, uid: i32, entry: &[u8]) -> BinderResult<()> { + let _wp = wd::watch_millis("ILegacyKeystore::put", 500); + map_or_log_err(self.put(alias, uid, entry), Ok) } - fn remove(&self, alias: &str) -> BinderResult<()> { - let _wp = wd::watch_millis("IVpnProfileStore::remove", 500); - map_or_log_err(self.remove(alias), Ok) + fn remove(&self, alias: &str, uid: i32) -> BinderResult<()> { + let _wp = wd::watch_millis("ILegacyKeystore::remove", 500); + map_or_log_err(self.remove(alias, uid), Ok) } - fn list(&self, prefix: &str) -> BinderResult> { - let _wp = wd::watch_millis("IVpnProfileStore::list", 500); - map_or_log_err(self.list(prefix), Ok) + fn list(&self, prefix: &str, uid: i32) -> BinderResult> { + let _wp = wd::watch_millis("ILegacyKeystore::list", 500); + map_or_log_err(self.list(prefix, uid), Ok) } } @@ -396,12 +423,12 @@ mod db_test { static TEST_BLOB4: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0]; #[test] - fn test_profile_db() { - let test_dir = TempDir::new("profiledb_test_").expect("Failed to create temp dir."); - let mut db = - DB::new(&test_dir.build().push("vpnprofile.sqlite")).expect("Failed to open database."); + fn test_entry_db() { + let test_dir = TempDir::new("entrydb_test_").expect("Failed to create temp dir."); + let mut db = DB::new(&test_dir.build().push(LegacyKeystore::LEGACY_KEYSTORE_FILE_NAME)) + .expect("Failed to open database."); - // Insert three profiles for owner 2. + // Insert three entries for owner 2. db.put(2, "test1", TEST_BLOB1).expect("Failed to insert test1."); db.put(2, "test2", TEST_BLOB2).expect("Failed to insert test2."); db.put(2, "test3", TEST_BLOB3).expect("Failed to insert test3."); @@ -409,73 +436,59 @@ mod db_test { // Check list returns all inserted aliases. assert_eq!( vec!["test1".to_string(), "test2".to_string(), "test3".to_string(),], - db.list(2).expect("Failed to list profiles.") + db.list(2).expect("Failed to list entries.") ); - // There should be no profiles for owner 1. - assert_eq!(Vec::::new(), db.list(1).expect("Failed to list profiles.")); + // There should be no entries for owner 1. + assert_eq!(Vec::::new(), db.list(1).expect("Failed to list entries.")); // Check the content of the three entries. - assert_eq!( - Some(TEST_BLOB1), - db.get(2, "test1").expect("Failed to get profile.").as_deref() - ); - assert_eq!( - Some(TEST_BLOB2), - db.get(2, "test2").expect("Failed to get profile.").as_deref() - ); - assert_eq!( - Some(TEST_BLOB3), - db.get(2, "test3").expect("Failed to get profile.").as_deref() - ); + assert_eq!(Some(TEST_BLOB1), db.get(2, "test1").expect("Failed to get entry.").as_deref()); + assert_eq!(Some(TEST_BLOB2), db.get(2, "test2").expect("Failed to get entry.").as_deref()); + assert_eq!(Some(TEST_BLOB3), db.get(2, "test3").expect("Failed to get entry.").as_deref()); // Remove test2 and check and check that it is no longer retrievable. - assert!(db.remove(2, "test2").expect("Failed to remove profile.")); - assert!(db.get(2, "test2").expect("Failed to get profile.").is_none()); + assert!(db.remove(2, "test2").expect("Failed to remove entry.")); + assert!(db.get(2, "test2").expect("Failed to get entry.").is_none()); // test2 should now no longer be in the list. assert_eq!( vec!["test1".to_string(), "test3".to_string(),], - db.list(2).expect("Failed to list profiles.") + db.list(2).expect("Failed to list entries.") ); // Put on existing alias replaces it. // Verify test1 is TEST_BLOB1. - assert_eq!( - Some(TEST_BLOB1), - db.get(2, "test1").expect("Failed to get profile.").as_deref() - ); + assert_eq!(Some(TEST_BLOB1), db.get(2, "test1").expect("Failed to get entry.").as_deref()); db.put(2, "test1", TEST_BLOB4).expect("Failed to replace test1."); // Verify test1 is TEST_BLOB4. - assert_eq!( - Some(TEST_BLOB4), - db.get(2, "test1").expect("Failed to get profile.").as_deref() - ); + assert_eq!(Some(TEST_BLOB4), db.get(2, "test1").expect("Failed to get entry.").as_deref()); } #[test] - fn concurrent_vpn_profile_test() -> Result<()> { + fn concurrent_legacy_keystore_entry_test() -> Result<()> { let temp_dir = Arc::new( - TempDir::new("concurrent_vpn_profile_test_").expect("Failed to create temp dir."), + TempDir::new("concurrent_legacy_keystore_entry_test_") + .expect("Failed to create temp dir."), ); - let db_path = temp_dir.build().push("vpnprofile.sqlite").to_owned(); + let db_path = temp_dir.build().push(LegacyKeystore::LEGACY_KEYSTORE_FILE_NAME).to_owned(); let test_begin = Instant::now(); let mut db = DB::new(&db_path).expect("Failed to open database."); - const PROFILE_COUNT: u32 = 5000u32; - const PROFILE_DB_COUNT: u32 = 5000u32; + const ENTRY_COUNT: u32 = 5000u32; + const ENTRY_DB_COUNT: u32 = 5000u32; - let mut actual_profile_count = PROFILE_COUNT; - // First insert PROFILE_COUNT profiles. - for count in 0..PROFILE_COUNT { + let mut actual_entry_count = ENTRY_COUNT; + // First insert ENTRY_COUNT entries. + for count in 0..ENTRY_COUNT { if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) { - actual_profile_count = count; + actual_entry_count = count; break; } let alias = format!("test_alias_{}", count); - db.put(1, &alias, TEST_BLOB1).expect("Failed to add profile (1)."); + db.put(1, &alias, TEST_BLOB1).expect("Failed to add entry (1)."); } // Insert more keys from a different thread and into a different namespace. @@ -483,16 +496,16 @@ mod db_test { let handle1 = thread::spawn(move || { let mut db = DB::new(&db_path1).expect("Failed to open database."); - for count in 0..actual_profile_count { + for count in 0..actual_entry_count { if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { return; } let alias = format!("test_alias_{}", count); - db.put(2, &alias, TEST_BLOB2).expect("Failed to add profile (2)."); + db.put(2, &alias, TEST_BLOB2).expect("Failed to add entry (2)."); } // Then delete them again. - for count in 0..actual_profile_count { + for count in 0..actual_entry_count { if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { return; } @@ -501,12 +514,12 @@ mod db_test { } }); - // And start deleting the first set of profiles. + // And start deleting the first set of entries. let db_path2 = db_path.clone(); let handle2 = thread::spawn(move || { let mut db = DB::new(&db_path2).expect("Failed to open database."); - for count in 0..actual_profile_count { + for count in 0..actual_entry_count { if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { return; } @@ -516,16 +529,16 @@ mod db_test { }); // While a lot of inserting and deleting is going on we have to open database connections - // successfully and then insert and delete a specific profile. + // successfully and then insert and delete a specific entry. let db_path3 = db_path.clone(); let handle3 = thread::spawn(move || { - for _count in 0..PROFILE_DB_COUNT { + for _count in 0..ENTRY_DB_COUNT { if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { return; } let mut db = DB::new(&db_path3).expect("Failed to open database."); - db.put(3, &TEST_ALIAS, TEST_BLOB3).expect("Failed to add profile (3)."); + db.put(3, &TEST_ALIAS, TEST_BLOB3).expect("Failed to add entry (3)."); db.remove(3, &TEST_ALIAS).expect("Remove failed (3)."); } @@ -534,14 +547,14 @@ mod db_test { // While thread 3 is inserting and deleting TEST_ALIAS, we try to get the alias. // This may yield an entry or none, but it must not fail. let handle4 = thread::spawn(move || { - for _count in 0..PROFILE_DB_COUNT { + for _count in 0..ENTRY_DB_COUNT { if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { return; } let mut db = DB::new(&db_path).expect("Failed to open database."); // This may return Some or None but it must not fail. - db.get(3, &TEST_ALIAS).expect("Failed to get profile (4)."); + db.get(3, &TEST_ALIAS).expect("Failed to get entry (4)."); } }); diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs index 53461da6..45338c4a 100644 --- a/keystore2/src/keystore2_main.rs +++ b/keystore2/src/keystore2_main.rs @@ -22,16 +22,16 @@ use keystore2::remote_provisioning::RemoteProvisioningService; use keystore2::service::KeystoreService; use keystore2::{apc::ApcManager, shared_secret_negotiation}; use keystore2::{authorization::AuthorizationManager, id_rotation::IdRotationState}; +use legacykeystore::LegacyKeystore; use log::{error, info}; use std::{panic, path::Path, sync::mpsc::channel}; -use vpnprofilestore::VpnProfileStore; static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default"; static APC_SERVICE_NAME: &str = "android.security.apc"; static AUTHORIZATION_SERVICE_NAME: &str = "android.security.authorization"; static REMOTE_PROVISIONING_SERVICE_NAME: &str = "android.security.remoteprovisioning"; static USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance"; -static VPNPROFILESTORE_SERVICE_NAME: &str = "android.security.vpnprofilestore"; +static LEGACY_KEYSTORE_SERVICE_NAME: &str = "android.security.legacykeystore"; /// Keystore 2.0 takes one argument which is a path indicating its designated working directory. fn main() { @@ -120,14 +120,14 @@ fn main() { }); } - let vpnprofilestore = VpnProfileStore::new_native_binder( + let legacykeystore = LegacyKeystore::new_native_binder( &keystore2::globals::DB_PATH.read().expect("Could not get DB_PATH."), ); - binder::add_service(VPNPROFILESTORE_SERVICE_NAME, vpnprofilestore.as_binder()).unwrap_or_else( + binder::add_service(LEGACY_KEYSTORE_SERVICE_NAME, legacykeystore.as_binder()).unwrap_or_else( |e| { panic!( "Failed to register service {} because of {:?}.", - VPNPROFILESTORE_SERVICE_NAME, e + LEGACY_KEYSTORE_SERVICE_NAME, e ); }, ); diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs index 9eebb363..e0d21334 100644 --- a/keystore2/src/legacy_blob.rs +++ b/keystore2/src/legacy_blob.rs @@ -599,6 +599,15 @@ impl LegacyBlobLoader { // * USRCERT was used for public certificates of USRPKEY entries. But KeyChain also // used this for user installed certificates without private key material. + const KNOWN_KEYSTORE_PREFIXES: &'static [&'static str] = + &["USRPKEY_", "USRSKEY_", "USRCERT_", "CACERT_"]; + + fn is_keystore_alias(encoded_alias: &str) -> bool { + // We can check the encoded alias because the prefixes we are interested + // in are all in the printable range that don't get mangled. + Self::KNOWN_KEYSTORE_PREFIXES.iter().any(|prefix| encoded_alias.starts_with(prefix)) + } + fn read_km_blob_file(&self, uid: u32, alias: &str) -> Result> { let mut iter = ["USRPKEY", "USRSKEY"].iter(); @@ -630,28 +639,28 @@ impl LegacyBlobLoader { Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?)) } - /// Read a legacy vpn profile blob. - pub fn read_vpn_profile(&self, uid: u32, alias: &str) -> Result>> { - let path = match self.make_vpn_profile_filename(uid, alias) { + /// Read a legacy keystore entry blob. + pub fn read_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result>> { + let path = match self.make_legacy_keystore_entry_filename(uid, alias) { Some(path) => path, None => return Ok(None), }; - let blob = - Self::read_generic_blob(&path).context("In read_vpn_profile: Failed to read blob.")?; + let blob = Self::read_generic_blob(&path) + .context("In read_legacy_keystore_entry: Failed to read blob.")?; Ok(blob.and_then(|blob| match blob.value { BlobValue::Generic(blob) => Some(blob), _ => { - log::info!("Unexpected vpn profile blob type. Ignoring"); + log::info!("Unexpected legacy keystore entry blob type. Ignoring"); None } })) } - /// Remove a vpn profile by the name alias with owner uid. - pub fn remove_vpn_profile(&self, uid: u32, alias: &str) -> Result<()> { - let path = match self.make_vpn_profile_filename(uid, alias) { + /// Remove a legacy keystore entry by the name alias with owner uid. + pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<()> { + let path = match self.make_legacy_keystore_entry_filename(uid, alias) { Some(path) => path, None => return Ok(()), }; @@ -659,25 +668,17 @@ impl LegacyBlobLoader { if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) { match e.kind() { ErrorKind::NotFound => return Ok(()), - _ => return Err(e).context("In remove_vpn_profile."), + _ => return Err(e).context("In remove_legacy_keystore_entry."), } } let user_id = uid_to_android_user(uid); self.remove_user_dir_if_empty(user_id) - .context("In remove_vpn_profile: Trying to remove empty user dir.") + .context("In remove_legacy_keystore_entry: Trying to remove empty user dir.") } - fn is_vpn_profile(encoded_alias: &str) -> bool { - // We can check the encoded alias because the prefixes we are interested - // in are all in the printable range that don't get mangled. - encoded_alias.starts_with("VPN_") - || encoded_alias.starts_with("PLATFORM_VPN_") - || encoded_alias == "LOCKDOWN_VPN" - } - - /// List all profiles belonging to the given uid. - pub fn list_vpn_profiles(&self, uid: u32) -> Result> { + /// List all entries belonging to the given uid. + pub fn list_legacy_keystore_entries(&self, uid: u32) -> Result> { let mut path = self.path.clone(); let user_id = uid_to_android_user(uid); path.push(format!("user_{}", user_id)); @@ -688,7 +689,10 @@ impl LegacyBlobLoader { ErrorKind::NotFound => return Ok(Default::default()), _ => { return Err(e).context(format!( - "In list_vpn_profiles: Failed to open legacy blob database. {:?}", + concat!( + "In list_legacy_keystore_entries: ,", + "Failed to open legacy blob database: {:?}" + ), path )) } @@ -696,14 +700,15 @@ impl LegacyBlobLoader { }; let mut result: Vec = Vec::new(); for entry in dir { - let file_name = - entry.context("In list_vpn_profiles: Trying to access dir entry")?.file_name(); + let file_name = entry + .context("In list_legacy_keystore_entries: Trying to access dir entry")? + .file_name(); if let Some(f) = file_name.to_str() { let encoded_alias = &f[uid_str.len() + 1..]; - if f.starts_with(&uid_str) && Self::is_vpn_profile(encoded_alias) { + if f.starts_with(&uid_str) && !Self::is_keystore_alias(encoded_alias) { result.push( Self::decode_alias(encoded_alias) - .context("In list_vpn_profiles: Trying to decode alias.")?, + .context("In list_legacy_keystore_entries: Trying to decode alias.")?, ) } } @@ -711,12 +716,15 @@ impl LegacyBlobLoader { Ok(result) } - /// This function constructs the vpn_profile file name which has the form: - /// user_/_. - fn make_vpn_profile_filename(&self, uid: u32, alias: &str) -> Option { - // legacy vpn entries must start with VPN_ or PLATFORM_VPN_ or are literally called - // LOCKDOWN_VPN. - if !Self::is_vpn_profile(alias) { + /// This function constructs the legacy blob file name which has the form: + /// user_/_. Legacy blob file names must not use + /// known keystore prefixes. + fn make_legacy_keystore_entry_filename(&self, uid: u32, alias: &str) -> Option { + // Legacy entries must not use known keystore prefixes. + if Self::is_keystore_alias(alias) { + log::warn!( + "Known keystore prefixes cannot be used with legacy keystore -> ignoring request." + ); return None; } @@ -1376,11 +1384,11 @@ mod test { } #[test] - fn list_vpn_profiles_on_non_existing_user() -> Result<()> { - let temp_dir = TempDir::new("list_vpn_profiles_on_non_existing_user")?; + fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> { + let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user")?; let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - assert!(legacy_blob_loader.list_vpn_profiles(20)?.is_empty()); + assert!(legacy_blob_loader.list_legacy_keystore_entries(20)?.is_empty()); Ok(()) }