Keystore 2.0: VPN profile store legacy support
Test: Manually tested by storing profiles through the UI then upgrading
to keystore 2 and verifying that they are still visible.
Also checked that they were correctly imported into the sqlite
database.
Change-Id: I0cbfb026db032290163469ee8824b5f59edc06fb
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index b51f644..3fc77b7 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -634,8 +634,98 @@
Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?))
}
- /// This function constructs the blob file name which has the form:
+ /// Read a legacy vpn profile blob.
+ pub fn read_vpn_profile(&self, uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
+ let path = match self.make_vpn_profile_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.")?;
+
+ Ok(blob.and_then(|blob| match blob.value {
+ BlobValue::Generic(blob) => Some(blob),
+ _ => {
+ log::info!("Unexpected vpn profile 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) {
+ Some(path) => path,
+ None => return Ok(()),
+ };
+
+ 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."),
+ }
+ }
+
+ 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.")
+ }
+
+ 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<Vec<String>> {
+ let mut path = self.path.clone();
+ let user_id = uid_to_android_user(uid);
+ path.push(format!("user_{}", user_id));
+ let uid_str = uid.to_string();
+ let dir =
+ Self::with_retry_interrupted(|| fs::read_dir(path.as_path())).with_context(|| {
+ format!("In list_vpn_profiles: Failed to open legacy blob database. {:?}", path)
+ })?;
+ let mut result: Vec<String> = Vec::new();
+ for entry in dir {
+ let file_name =
+ entry.context("In list_vpn_profiles: 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) {
+ result.push(
+ Self::decode_alias(encoded_alias)
+ .context("In list_vpn_profiles: Trying to decode alias.")?,
+ )
+ }
+ }
+ }
+ Ok(result)
+ }
+
+ /// This function constructs the vpn_profile file name which has the form:
/// user_<android user id>/<uid>_<alias>.
+ fn make_vpn_profile_filename(&self, uid: u32, alias: &str) -> Option<PathBuf> {
+ // legacy vpn entries must start with VPN_ or PLATFORM_VPN_ or are literally called
+ // LOCKDOWN_VPN.
+ if !Self::is_vpn_profile(alias) {
+ return None;
+ }
+
+ let mut path = self.path.clone();
+ let user_id = uid_to_android_user(uid);
+ let encoded_alias = Self::encode_alias(alias);
+ path.push(format!("user_{}", user_id));
+ path.push(format!("{}_{}", uid, encoded_alias));
+ Some(path)
+ }
+
+ /// This function constructs the blob file name which has the form:
+ /// user_<android user id>/<uid>_<prefix>_<alias>.
fn make_blob_filename(&self, uid: u32, alias: &str, prefix: &str) -> PathBuf {
let user_id = uid_to_android_user(uid);
let encoded_alias = Self::encode_alias(&format!("{}_{}", prefix, alias));
@@ -838,18 +928,24 @@
if something_was_deleted {
let user_id = uid_to_android_user(uid);
- if self
- .is_empty_user(user_id)
- .context("In remove_keystore_entry: Trying to check for empty user dir.")?
- {
- let user_path = self.make_user_path_name(user_id);
- Self::with_retry_interrupted(|| fs::remove_dir(user_path.as_path())).ok();
- }
+ self.remove_user_dir_if_empty(user_id)
+ .context("In remove_keystore_entry: Trying to remove empty user dir.")?;
}
Ok(something_was_deleted)
}
+ fn remove_user_dir_if_empty(&self, user_id: u32) -> Result<()> {
+ if self
+ .is_empty_user(user_id)
+ .context("In remove_user_dir_if_empty: Trying to check for empty user dir.")?
+ {
+ let user_path = self.make_user_path_name(user_id);
+ Self::with_retry_interrupted(|| fs::remove_dir(user_path.as_path())).ok();
+ }
+ Ok(())
+ }
+
/// Load a legacy key blob entry by uid and alias.
pub fn load_by_uid_alias(
&self,