Refactor of Ext4Crypt.cpp in preparation for DE keys
Mainly a refactor, but with a substantive change: Keys are created in
a temporary location, then moved to their final destination, for
atomicity.
Bug: 26704408
Change-Id: I0b2dc70d6bfa1f8a65536dd05b73c4b36a4699cf
diff --git a/Ext4Crypt.cpp b/Ext4Crypt.cpp
index 04d7a12..5c1b921 100644
--- a/Ext4Crypt.cpp
+++ b/Ext4Crypt.cpp
@@ -21,9 +21,11 @@
#include <iomanip>
#include <map>
+#include <set>
#include <string>
#include <sstream>
+#include <stdio.h>
#include <errno.h>
#include <dirent.h>
#include <sys/mount.h>
@@ -75,6 +77,7 @@
const int password_max_age_seconds = 60;
const std::string user_key_dir = std::string() + DATA_MNT_POINT + "/misc/vold/user_keys";
+ const std::string user_key_temp = user_key_dir + "/temp";
// How is device encrypted
struct keys {
@@ -83,10 +86,10 @@
time_t expiry_time;
};
std::map<std::string, keys> s_key_store;
- // Maps the key paths of ephemeral keys to the keys
- std::map<std::string, std::string> s_ephemeral_user_keys;
- // Map user serial numbers to key references
- std::map<int, std::string> s_key_raw_refs;
+ // Some users are ephemeral, don't try to wipe their keys from disk
+ std::set<userid_t> s_ephemeral_users;
+ // Map user ids to key references
+ std::map<userid_t, std::string> s_ce_key_raw_refs;
// ext4enc:TODO get this const from somewhere good
const int EXT4_KEY_DESCRIPTOR_SIZE = 8;
@@ -120,7 +123,7 @@
}
}
-static std::string e4crypt_install_key(const std::string &key);
+static bool install_key(const std::string &key, std::string &raw_ref);
static int put_crypt_ftr_and_key(const crypt_mnt_ftr& crypt_ftr,
UnencryptedProperties& props)
@@ -382,10 +385,11 @@
clock_gettime(CLOCK_BOOTTIME, &now);
s_key_store[path] = keys{master_key, password,
now.tv_sec + password_max_age_seconds};
- auto raw_ref = e4crypt_install_key(master_key);
- if (raw_ref.empty()) {
+ std::string raw_ref;
+ if (!install_key(master_key, raw_ref)) {
return -1;
}
+ SLOGD("Installed master key");
// Save reference to key so we can set policy later
if (!props.Set(properties::ref, raw_ref)) {
@@ -427,32 +431,28 @@
return keyctl_search(KEY_SPEC_SESSION_KEYRING, "keyring", "e4crypt", 0);
}
-static int e4crypt_install_key(const ext4_encryption_key &ext4_key, const std::string &ref)
+// Install password into global keyring
+// Return raw key reference for use in policy
+static bool install_key(const std::string &key, std::string &raw_ref)
{
+ if (key.size() != key_length/8) {
+ LOG(ERROR) << "Wrong size key " << key.size();
+ return false;
+ }
+ auto ext4_key = fill_key(key);
+ raw_ref = generate_key_ref(ext4_key.raw, ext4_key.size);
+ auto ref = keyname(raw_ref);
key_serial_t device_keyring = e4crypt_keyring();
key_serial_t key_id = add_key("logon", ref.c_str(),
(void*)&ext4_key, sizeof(ext4_key),
device_keyring);
if (key_id == -1) {
PLOG(ERROR) << "Failed to insert key into keyring " << device_keyring;
- return -1;
+ return false;
}
LOG(INFO) << "Added key " << key_id << " (" << ref << ") to keyring "
<< device_keyring << " in process " << getpid();
- return 0;
-}
-
-// Install password into global keyring
-// Return raw key reference for use in policy
-static std::string e4crypt_install_key(const std::string &key)
-{
- auto ext4_key = fill_key(key);
- auto raw_ref = generate_key_ref(ext4_key.raw, ext4_key.size);
- auto ref = keyname(raw_ref);
- if (e4crypt_install_key(ext4_key, ref) == -1) {
- return "";
- }
- return raw_ref;
+ return true;
}
int e4crypt_restart(const char* path)
@@ -549,31 +549,31 @@
.Set(fieldname, std::string(value)) ? 0 : -1;
}
-static std::string get_key_path(userid_t user_id) {
+static std::string get_ce_key_path(userid_t user_id) {
return StringPrintf("%s/user_%d/current", user_key_dir.c_str(), user_id);
}
-static bool e4crypt_is_key_ephemeral(const std::string &key_path) {
- return s_ephemeral_user_keys.find(key_path) != s_ephemeral_user_keys.end();
+static bool read_and_install_key(const std::string &key_path, std::string &raw_ref)
+{
+ std::string key;
+ if (!android::vold::retrieveKey(key_path, key)) return false;
+ if (!install_key(key, raw_ref)) return false;
+ return true;
}
-static bool read_user_key(userid_t user_id, std::string &key)
+static bool read_and_install_user_ce_key(userid_t user_id)
{
- const auto key_path = get_key_path(user_id);
- const auto ephemeral_key_it = s_ephemeral_user_keys.find(key_path);
- if (ephemeral_key_it != s_ephemeral_user_keys.end()) {
- key = ephemeral_key_it->second;
- return true;
- }
- if (!android::vold::retrieveKey(key_path, key)) return false;
- if (key.size() != key_length/8) {
- LOG(ERROR) << "Wrong size key " << key.size() << " in " << key_path;
- return false;
- }
+ if (s_ce_key_raw_refs.count(user_id) != 0) return true;
+ const auto key_path = get_ce_key_path(user_id);
+ std::string raw_ref;
+ if (!read_and_install_key(key_path, raw_ref)) return false;
+ s_ce_key_raw_refs[user_id] = raw_ref;
+ LOG(DEBUG) << "Installed ce key for user " << user_id;
return true;
}
static bool prepare_dir(const std::string &dir, mode_t mode, uid_t uid, gid_t gid) {
+ LOG(DEBUG) << "Preparing: " << dir;
if (fs_prepare_dir(dir.c_str(), mode, uid, gid) != 0) {
PLOG(ERROR) << "Failed to prepare " << dir;
return false;
@@ -581,59 +581,88 @@
return true;
}
-static bool create_user_key(userid_t user_id, bool create_ephemeral) {
- const auto key_path = get_key_path(user_id);
- std::string key;
+static bool random_key(std::string &key) {
if (android::vold::ReadRandomBytes(key_length / 8, key) != 0) {
// TODO status_t plays badly with PLOG, fix it.
LOG(ERROR) << "Random read failed";
return false;
}
- if (create_ephemeral) {
- // If the key should be created as ephemeral, store it in memory only.
- s_ephemeral_user_keys[key_path] = key;
- } else {
- if (!prepare_dir(user_key_dir + "/user_" + std::to_string(user_id),
- 0700, AID_ROOT, AID_ROOT)) return false;
- if (!android::vold::storeKey(key_path, key)) return false;
+ return true;
+}
+
+static bool path_exists(const std::string &path) {
+ return access(path.c_str(), F_OK) == 0;
+}
+
+// NB this assumes that there is only one thread listening for crypt commands, because
+// it creates keys in a fixed location.
+static bool store_key(const std::string &key_path, const std::string &key) {
+ if (path_exists(key_path)) {
+ LOG(ERROR) << "Already exists, cannot create key at: " << key_path;
+ return false;
+ }
+ if (path_exists(user_key_temp)) {
+ android::vold::destroyKey(user_key_temp);
+ }
+ if (!android::vold::storeKey(user_key_temp, key)) return false;
+ if (rename(user_key_temp.c_str(), key_path.c_str()) != 0) {
+ PLOG(ERROR) << "Unable to move new key to location: " << key_path;
+ return false;
}
LOG(DEBUG) << "Created key " << key_path;
return true;
}
-static int e4crypt_set_user_policy(userid_t user_id, int serial, std::string& path) {
- LOG(DEBUG) << "e4crypt_set_user_policy for " << user_id << " serial " << serial;
- if (s_key_raw_refs.count(serial) != 1) {
- LOG(ERROR) << "Key unknown, can't e4crypt_set_user_policy for "
- << user_id << " serial " << serial;
- return -1;
+static bool create_and_install_user_key(userid_t user_id, bool create_ephemeral) {
+ std::string ce_key;
+ if (!random_key(ce_key)) return false;
+ if (create_ephemeral) {
+ // If the key should be created as ephemeral, don't store it.
+ s_ephemeral_users.insert(user_id);
+ } else {
+ if (!prepare_dir(user_key_dir + "/user_" + std::to_string(user_id),
+ 0700, AID_ROOT, AID_ROOT)) return false;
+ if (!store_key(get_ce_key_path(user_id), ce_key)) return false;
}
- auto raw_ref = s_key_raw_refs[serial];
- return do_policy_set(path.c_str(), raw_ref.data(), raw_ref.size());
+ std::string ce_raw_ref;
+ if (!install_key(ce_key, ce_raw_ref)) return false;
+ s_ce_key_raw_refs[user_id] = ce_raw_ref;
+ LOG(DEBUG) << "Created key for user " << user_id;
+ return true;
+}
+
+static bool lookup_key_ref(const std::map<userid_t, std::string> &key_map,
+ userid_t user_id, std::string &raw_ref) {
+ auto refi = key_map.find(user_id);
+ if (refi == key_map.end()) {
+ LOG(ERROR) << "Cannot find key for " << user_id;
+ return false;
+ }
+ raw_ref = refi->second;
+ return true;
+}
+
+static bool set_policy(const std::string &raw_ref, const std::string& path) {
+ if (do_policy_set(path.c_str(), raw_ref.data(), raw_ref.size()) != 0) {
+ LOG(ERROR) << "Failed to set policy on: " << path;
+ return false;
+ }
+ return true;
}
int e4crypt_init_user0() {
LOG(DEBUG) << "e4crypt_init_user0";
if (e4crypt_is_native()) {
if (!prepare_dir(user_key_dir, 0700, AID_ROOT, AID_ROOT)) return -1;
- std::string user_key;
- if (!read_user_key(0, user_key)) {
- // FIXME if the key exists and we just failed to read it, this destroys it.
- if (!create_user_key(0, false)) {
- return -1;
- }
- if (!read_user_key(0, user_key)) {
- LOG(ERROR) << "Couldn't read just-created key for user 0";
- return -1;
- }
+ auto ce_path = get_ce_key_path(0);
+ if (!path_exists(ce_path)) {
+ if (!create_and_install_user_key(0, false)) return -1;
}
- auto raw_ref = e4crypt_install_key(user_key);
- if (raw_ref.empty()) {
- return -1;
- }
- s_key_raw_refs[0] = raw_ref;
}
// Ignore failures. FIXME this is horrid
+ // FIXME: we need an idempotent policy-setting call, which simply verifies the
+ // policy is already set on a second run, even if the directory is nonempty.
+ // Then we need to call it all the time.
e4crypt_prepare_user_storage(nullptr, 0, 0, false);
return 0;
}
@@ -643,29 +672,22 @@
if (!e4crypt_is_native()) {
return 0;
}
- std::string key;
- if (read_user_key(user_id, key)) {
+ // FIXME test for existence of key that is not loaded yet
+ if (s_ce_key_raw_refs.count(user_id) != 0) {
LOG(ERROR) << "Already exists, can't e4crypt_vold_create_user_key for "
<< user_id << " serial " << serial;
// FIXME should we fail the command?
return 0;
}
- if (!create_user_key(user_id, ephemeral)) {
- return -1;
- }
- if (e4crypt_unlock_user_key(user_id, serial, nullptr) != 0) {
+ if (!create_and_install_user_key(user_id, ephemeral)) {
return -1;
}
// TODO: create second key for user_de data
return 0;
}
-static bool evict_user_key(userid_t user_id) {
- auto key_path = get_key_path(user_id);
- std::string key;
- if (!read_user_key(user_id, key)) return false;
- auto ext4_key = fill_key(key);
- auto ref = keyname(generate_key_ref(ext4_key.raw, ext4_key.size));
+static bool evict_key(const std::string &raw_ref) {
+ auto ref = keyname(raw_ref);
auto key_serial = keyctl_search(e4crypt_keyring(), "logon", ref.c_str(), 0);
if (keyctl_revoke(key_serial) != 0) {
PLOG(ERROR) << "Failed to revoke key with serial " << key_serial << " ref " << ref;
@@ -681,16 +703,16 @@
return 0;
}
// TODO: destroy second key for user_de data
- bool evict_success = evict_user_key(user_id);
- auto key_path = get_key_path(user_id);
- if (e4crypt_is_key_ephemeral(key_path)) {
- s_ephemeral_user_keys.erase(key_path);
+ bool success = true;
+ std::string raw_ref;
+ success &= lookup_key_ref(s_ce_key_raw_refs, user_id, raw_ref) && evict_key(raw_ref);
+ auto it = s_ephemeral_users.find(user_id);
+ if (it != s_ephemeral_users.end()) {
+ s_ephemeral_users.erase(it);
} else {
- if (!android::vold::destroyKey(key_path)) {
- return -1;
- }
+ success &= android::vold::destroyKey(get_ce_key_path(user_id));
}
- return evict_success ? 0 : -1;
+ return success ? 0 : -1;
}
static int emulated_lock(const std::string& path) {
@@ -726,16 +748,10 @@
int e4crypt_unlock_user_key(userid_t user_id, int serial, const char* token) {
LOG(DEBUG) << "e4crypt_unlock_user_key " << user_id << " " << (token != nullptr);
if (e4crypt_is_native()) {
- std::string user_key;
- if (!read_user_key(user_id, user_key)) {
+ if (!read_and_install_user_ce_key(user_id)) {
LOG(ERROR) << "Couldn't read key for " << user_id;
return -1;
}
- auto raw_ref = e4crypt_install_key(user_key);
- if (raw_ref.empty()) {
- return -1;
- }
- s_key_raw_refs[serial] = raw_ref;
} else {
// When in emulation mode, we just use chmod. However, we also
// unlock directories when not in emulation mode, to bring devices
@@ -787,11 +803,12 @@
if (!prepare_dir(user_de_path, 0771, AID_SYSTEM, AID_SYSTEM)) return -1;
if (e4crypt_crypto_complete(DATA_MNT_POINT) == 0) {
- if (e4crypt_set_user_policy(user_id, serial, system_ce_path)
- || e4crypt_set_user_policy(user_id, serial, media_ce_path)
- || e4crypt_set_user_policy(user_id, serial, user_ce_path)) {
- return -1;
- }
+ std::string ce_raw_ref;
+ if (!lookup_key_ref(s_ce_key_raw_refs, user_id, ce_raw_ref)) return -1;
+ if (!set_policy(ce_raw_ref, system_ce_path)) return -1;
+ if (!set_policy(ce_raw_ref, media_ce_path)) return -1;
+ if (!set_policy(ce_raw_ref, user_ce_path)) return -1;
+ // ext4enc:TODO set DE policy too
}
return 0;