Rework FBE crypto to match the N way of doing things

Major rework and refactor of FBE code to load the keys at the right
time and in a natural way. The old code was aimed at our goals for M,
with patches on top, and didn't quite work.

Bug: 22358539

Change-Id: I9bf7a0a86ee3f2abf0edbd5966f93efac2474c2c
diff --git a/Ext4Crypt.cpp b/Ext4Crypt.cpp
index 4eb7c3e..5523363 100644
--- a/Ext4Crypt.cpp
+++ b/Ext4Crypt.cpp
@@ -74,6 +74,8 @@
     // How long do we store passwords for?
     const int password_max_age_seconds = 60;
 
+    const std::string user_key_dir = std::string() + DATA_MNT_POINT + "/misc/vold/user_keys";
+
     // How is device encrypted
     struct keys {
         std::string master_key;
@@ -83,9 +85,10 @@
     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;
 
-    // ext4enc:TODO get these consts from somewhere good
-    const int SHA512_LENGTH = 64;
+    // ext4enc:TODO get this const from somewhere good
     const int EXT4_KEY_DESCRIPTOR_SIZE = 8;
 
     // ext4enc:TODO Include structure from somewhere sensible
@@ -333,12 +336,12 @@
 
     SHA512_Init(&c);
     SHA512_Update(&c, key, length);
-    unsigned char key_ref1[SHA512_LENGTH];
+    unsigned char key_ref1[SHA512_DIGEST_LENGTH];
     SHA512_Final(key_ref1, &c);
 
     SHA512_Init(&c);
-    SHA512_Update(&c, key_ref1, SHA512_LENGTH);
-    unsigned char key_ref2[SHA512_LENGTH];
+    SHA512_Update(&c, key_ref1, SHA512_DIGEST_LENGTH);
+    unsigned char key_ref2[SHA512_DIGEST_LENGTH];
     SHA512_Final(key_ref2, &c);
 
     return std::string((char*)key_ref2, EXT4_KEY_DESCRIPTOR_SIZE);
@@ -427,17 +430,15 @@
 static int e4crypt_install_key(const ext4_encryption_key &ext4_key, const std::string &ref)
 {
     key_serial_t device_keyring = e4crypt_keyring();
-    SLOGI("Found device_keyring - id is %d", device_keyring);
     key_serial_t key_id = add_key("logon", ref.c_str(),
                                   (void*)&ext4_key, sizeof(ext4_key),
                                   device_keyring);
     if (key_id == -1) {
-        SLOGE("Failed to insert key into keyring with error %s",
-              strerror(errno));
+        PLOG(ERROR) << "Failed to insert key into keyring " << device_keyring;
         return -1;
     }
-    SLOGI("Added key %d (%s) to keyring %d in process %d",
-          key_id, ref.c_str(), device_keyring, getpid());
+    LOG(INFO) << "Added key " << key_id << " (" << ref << ") to keyring "
+        << device_keyring << " in process " << getpid();
     return 0;
 }
 
@@ -548,148 +549,112 @@
         .Set(fieldname, std::string(value)) ? 0 : -1;
 }
 
-static std::string get_key_path(const char *mount_path, userid_t user_id) {
-    // ext4enc:TODO get the path properly
-    auto key_dir = StringPrintf("%s/misc/vold/user_keys", mount_path);
-    if (fs_prepare_dir(key_dir.c_str(), 0700, AID_ROOT, AID_ROOT)) {
-        PLOG(ERROR) << "Failed to prepare " << key_dir;
-        return "";
-    }
-    return StringPrintf("%s/%d", key_dir.c_str(), user_id);
+static std::string get_key_path(userid_t user_id) {
+    return StringPrintf("%s/%d", 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();
 }
 
-// ext4enc:TODO this can't be the only place keys are read from /dev/urandom
-// we should unite those places.
-static std::string e4crypt_get_key(
-    const std::string &key_path,
-    bool create_if_absent,
-    bool create_ephemeral)
+static std::string read_user_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()) {
         return ephemeral_key_it->second;
     }
 
     std::string content;
-    if (android::base::ReadFileToString(key_path, &content)) {
-        if (content.size() != key_length/8) {
-            SLOGE("Wrong size key %zu in  %s", content.size(), key_path.c_str());
-            return "";
-        }
-        return content;
-    }
-    if (!create_if_absent) {
-        SLOGE("No key found in %s", key_path.c_str());
+    if (!android::base::ReadFileToString(key_path, &content)) {
         return "";
     }
+    if (content.size() != key_length/8) {
+        LOG(ERROR) << "Wrong size key " << content.size() << " in " << key_path;
+        return "";
+    }
+    return content;
+}
+
+// ext4enc:TODO this can't be the only place keys are read from /dev/urandom
+// we should unite those places.
+static std::string get_random_string(size_t length) {
     std::ifstream urandom("/dev/urandom");
     if (!urandom) {
-        SLOGE("Unable to open /dev/urandom (%s)", strerror(errno));
+        PLOG(ERROR) << "Unable to open /dev/urandom";
         return "";
     }
-    char key_bytes[key_length / 8];
-    errno = 0;
-    urandom.read(key_bytes, sizeof(key_bytes));
+    std::string res(length, '\0');
+    urandom.read(&res[0], length);
     if (!urandom) {
-        SLOGE("Unable to read key from /dev/urandom (%s)", strerror(errno));
+        PLOG(ERROR) << "Unable to read from /dev/urandom";
         return "";
     }
-    std::string key(key_bytes, sizeof(key_bytes));
+    return res;
+}
+
+static bool create_user_key(userid_t user_id, bool create_ephemeral) {
+    if (fs_prepare_dir(user_key_dir.c_str(), 0700, AID_ROOT, AID_ROOT)) {
+        PLOG(ERROR) << "Failed to prepare " << user_key_dir;
+        return false;
+    }
+    const auto key_path = get_key_path(user_id);
+    auto key = get_random_string(key_length / 8);
+    if (key.empty()) {
+        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 (!android::base::WriteStringToFile(key, key_path)) {
-        SLOGE("Unable to write key to %s (%s)",
-                key_path.c_str(), strerror(errno));
-        return "";
+        PLOG(ERROR) << "Unable to write key to " << key_path;
+        return false;
     }
-    return key;
-}
-
-static int e4crypt_set_user_policy(const char *mount_path, userid_t user_id,
-        std::string& path, bool create_if_absent, bool create_ephemeral) {
-    SLOGD("e4crypt_set_user_policy for %d", user_id);
-    auto user_key = e4crypt_get_key(get_key_path(mount_path, user_id),
-            create_if_absent, create_ephemeral);
-    if (user_key.empty()) {
-        return -1;
-    }
-    auto raw_ref = e4crypt_install_key(user_key);
-    if (raw_ref.empty()) {
-        return -1;
-    }
-    return do_policy_set(path.c_str(), raw_ref.c_str(), raw_ref.size());
-}
-
-static bool is_numeric(const char *name) {
-    for (const char *p = name; *p != '\0'; p++) {
-        if (!isdigit(*p))
-            return false;
-    }
+    LOG(DEBUG) << "Created key " << key_path;
     return true;
 }
 
-int e4crypt_vold_set_user_crypto_policies(const char *dir)
-{
-    if (e4crypt_crypto_complete(DATA_MNT_POINT) != 0) {
-        return 0;
-    }
-    SLOGD("e4crypt_vold_set_user_crypto_policies");
-    std::unique_ptr<DIR, int(*)(DIR*)> dirp(opendir(dir), closedir);
-    if (!dirp) {
-        SLOGE("Unable to read directory %s, error %s\n",
-            dir, strerror(errno));
+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;
     }
-    for (;;) {
-        struct dirent *result = readdir(dirp.get());
-        if (!result) {
-            // ext4enc:TODO check errno
-            break;
-        }
-        if (result->d_type != DT_DIR || !is_numeric(result->d_name)) {
-            continue; // skips user 0, which is a symlink
-        }
-        auto user_id = atoi(result->d_name);
-        auto user_dir = std::string() + dir + "/" + result->d_name;
-        // ext4enc:TODO don't hardcode /data
-        if (e4crypt_set_user_policy("/data", user_id, user_dir, false, false)) {
-            // ext4enc:TODO If this function fails, stop the boot: we must
-            // deliver on promised encryption.
-            SLOGE("Unable to set policy on %s\n", user_dir.c_str());
-        }
-    }
-    return 0;
+    auto raw_ref = s_key_raw_refs[serial];
+    return do_policy_set(path.c_str(), raw_ref.data(), raw_ref.size());
 }
 
 int e4crypt_vold_create_user_key(userid_t user_id, int serial, bool ephemeral) {
-    SLOGD("e4crypt_vold_create_user_key(%d)", user_id);
-    // TODO: create second key for user_de data
-    if (e4crypt_get_key(
-            get_key_path(DATA_MNT_POINT, user_id), true, ephemeral).empty()) {
-        return -1;
-    } else {
+    LOG(DEBUG) << "e4crypt_vold_create_user_key for " << user_id << " serial " << serial;
+    if (!read_user_key(user_id).empty()) {
+        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) {
+        return -1;
+    }
+    // TODO: create second key for user_de data
+    return 0;
 }
 
 int e4crypt_destroy_user_key(userid_t user_id) {
-    SLOGD("e4crypt_destroy_user_key(%d)", user_id);
+    LOG(DEBUG) << "e4crypt_destroy_user_key(" << user_id << ")";
     // TODO: destroy second key for user_de data
-    auto key_path = get_key_path(DATA_MNT_POINT, user_id);
-    auto key = e4crypt_get_key(key_path, false, false);
+    auto key_path = get_key_path(user_id);
+    auto key = read_user_key(user_id);
     auto ext4_key = fill_key(key);
     auto ref = keyname(generate_key_ref(ext4_key.raw, ext4_key.size));
     auto key_serial = keyctl_search(e4crypt_keyring(), "logon", ref.c_str(), 0);
     if (keyctl_revoke(key_serial) == 0) {
-        SLOGD("Revoked key with serial %ld ref %s\n", key_serial, ref.c_str());
+        LOG(DEBUG) << "Revoked key with serial " << key_serial << " ref " << ref;
     } else {
-        SLOGE("Failed to revoke key with serial %ld ref %s: %s\n",
-            key_serial, ref.c_str(), strerror(errno));
+        PLOG(ERROR) << "Failed to revoke key with serial " << key_serial << " ref " << ref;
     }
     if (e4crypt_is_key_ephemeral(key_path)) {
         s_ephemeral_user_keys.erase(key_path);
@@ -697,18 +662,17 @@
     }
     int pid = fork();
     if (pid < 0) {
-        SLOGE("Unable to fork: %s", strerror(errno));
+        PLOG(ERROR) << "Unable to fork";
         return -1;
     }
     if (pid == 0) {
-        SLOGD("Forked for secdiscard");
+        LOG(DEBUG) << "Forked for secdiscard";
         execl("/system/bin/secdiscard",
             "/system/bin/secdiscard",
             "--",
             key_path.c_str(),
             NULL);
-        SLOGE("Unable to launch secdiscard on %s: %s\n", key_path.c_str(),
-            strerror(errno));
+        PLOG(ERROR) << "Unable to launch secdiscard on " << key_path;
         exit(-1);
     }
     // ext4enc:TODO reap the zombie
@@ -743,16 +707,37 @@
     return 0;
 }
 
-int e4crypt_unlock_user_key(userid_t user_id, const char* token) {
+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()) {
-        auto user_key = e4crypt_get_key(get_key_path(DATA_MNT_POINT, user_id), false, false);
+        auto user_key = read_user_key(user_id);
         if (user_key.empty()) {
-            return -1;
+            // FIXME special case for user 0
+            if (user_id != 0) {
+                LOG(ERROR) << "Couldn't read key for " << user_id;
+                return -1;
+            }
+            // FIXME if the key exists and we just failed to read it, this destroys it.
+            if (!create_user_key(user_id, false)) {
+                return -1;
+            }
+            user_key = read_user_key(user_id);
+            if (user_key.empty()) {
+                LOG(ERROR) << "Couldn't read just-created 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;
+        if (user_id == 0) {
+            // FIXME special case for user 0
+            // prepare their storage here
+            e4crypt_prepare_user_storage(nullptr, 0, 0, false);
+        }
+        return 0;
     } else {
         // When in emulation mode, we just use chmod. However, we also
         // unlock directories when not in emulation mode, to bring devices
@@ -788,6 +773,11 @@
                                  userid_t user_id,
                                  int serial,
                                  bool ephemeral) {
+    if (volume_uuid) {
+        LOG(DEBUG) << "e4crypt_prepare_user_storage " << volume_uuid << " " << user_id;
+    } else {
+        LOG(DEBUG) << "e4crypt_prepare_user_storage, null volume " << user_id;
+    }
     std::string system_ce_path(android::vold::BuildDataSystemCePath(user_id));
     std::string media_ce_path(android::vold::BuildDataMediaPath(volume_uuid, user_id));
     std::string user_ce_path(android::vold::BuildDataUserPath(volume_uuid, user_id));
@@ -811,10 +801,9 @@
     }
 
     if (e4crypt_crypto_complete(DATA_MNT_POINT) == 0) {
-        if (e4crypt_set_user_policy(DATA_MNT_POINT, user_id, system_ce_path, true, ephemeral)
-                || e4crypt_set_user_policy(DATA_MNT_POINT, user_id, media_ce_path, true, ephemeral)
-                || e4crypt_set_user_policy(DATA_MNT_POINT, user_id, user_ce_path, true,
-                        ephemeral)) {
+        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;
         }
     }