New granular encryption commands for framework.

We now have separate methods for key creation/destruction and
unlocking/locking.  Key unlocking can pass through an opaque token,
but it's left empty for now.

Extend user storage setup to also create system_ce and user_de
paths.  Bring over some path generation logic from installd.

Use strong type checking on user arguments.

Bug: 22358539
Change-Id: I00ba15c7b10dd682640b3f082feade4fb7cbbb5d
diff --git a/CryptCommandListener.cpp b/CryptCommandListener.cpp
index 575ec90..9feb5ec 100644
--- a/CryptCommandListener.cpp
+++ b/CryptCommandListener.cpp
@@ -16,6 +16,7 @@
 
 #include <stdlib.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -30,7 +31,9 @@
 
 #define LOG_TAG "VoldCryptCmdListener"
 
+#include <base/logging.h>
 #include <base/stringprintf.h>
+
 #include <cutils/fs.h>
 #include <cutils/log.h>
 #include <cutils/sockets.h>
@@ -43,6 +46,7 @@
 #include "ResponseCode.h"
 #include "cryptfs.h"
 #include "Ext4Crypt.h"
+#include "Utils.h"
 
 #define DUMP_ARGS 0
 
@@ -110,6 +114,14 @@
     }
 }
 
+static char* parseNull(char* arg) {
+    if (strcmp(arg, "!") == 0) {
+        return nullptr;
+    } else {
+        return arg;
+    }
+}
+
 int CryptCommandListener::CryptfsCmd::runCommand(SocketClient *cli,
                                                  int argc, char **argv) {
     if ((cli->getUid() != 0) && (cli->getUid() != AID_SYSTEM)) {
@@ -124,6 +136,7 @@
 
     int rc = 0;
 
+    std::string cmd(argv[1]);
     if (!strcmp(argv[1], "checkpw")) {
         if (argc != 3) {
             cli->sendMsg(ResponseCode::CommandSyntaxError, "Usage: cryptfs checkpw <passwd>", false);
@@ -338,26 +351,7 @@
         SLOGD("cryptfs setusercryptopolicies");
         dumpArgs(argc, argv, -1);
         rc = e4crypt_set_user_crypto_policies(argv[2]);
-    } else if (!strcmp(argv[1], "createnewuserdir")) {
-        if (argc != 4) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                "Usage: cryptfs createnewuserdir <userHandle> <path>", false);
-            return 0;
-        }
-        // ext4enc:TODO: send a CommandSyntaxError if argv[2] not an integer
-        SLOGD("cryptfs createnewuserdir");
-        dumpArgs(argc, argv, -1);
-        rc = e4crypt_create_new_user_dir(argv[2], argv[3]);
-    } else if (!strcmp(argv[1], "deleteuserkey")) {
-        if (argc != 3) {
-            cli->sendMsg(ResponseCode::CommandSyntaxError,
-                "Usage: cryptfs deleteuserkey <userHandle>", false);
-            return 0;
-        }
-        // ext4enc:TODO: send a CommandSyntaxError if argv[2] not an integer
-        SLOGD("cryptfs deleteuserkey");
-        dumpArgs(argc, argv, -1);
-        rc = e4crypt_delete_user_key(argv[2]);
+
     } else if (!strcmp(argv[1], "isConvertibleToFBE")) {
         if (argc != 2) {
             cli->sendMsg(ResponseCode::CommandSyntaxError,
@@ -368,6 +362,28 @@
         SLOGD("cryptfs isConvertibleToFBE");
         dumpArgs(argc, argv, -1);
         rc = cryptfs_isConvertibleToFBE();
+
+    } else if (cmd == "create_user_key" && argc > 3) {
+        // create_user_key [user] [serial]
+        return sendGenericOkFail(cli, e4crypt_create_user_key(atoi(argv[2])));
+
+    } else if (cmd == "destroy_user_key" && argc > 2) {
+        // destroy_user_key [user]
+        return sendGenericOkFail(cli, e4crypt_destroy_user_key(atoi(argv[2])));
+
+    } else if (cmd == "unlock_user_key" && argc > 4) {
+        // unlock_user_key [user] [serial] [token]
+        return sendGenericOkFail(cli, e4crypt_unlock_user_key(atoi(argv[2]), parseNull(argv[4])));
+
+    } else if (cmd == "lock_user_key" && argc > 2) {
+        // lock_user_key [user]
+        return sendGenericOkFail(cli, e4crypt_lock_user_key(atoi(argv[2])));
+
+    } else if (cmd == "prepare_user_storage" && argc > 4) {
+        // prepare_user_storage [uuid] [user] [serial]
+        return sendGenericOkFail(cli,
+                e4crypt_prepare_user_storage(parseNull(argv[2]), atoi(argv[3])));
+
     } else {
         dumpArgs(argc, argv, -1);
         cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown cryptfs cmd", false);
diff --git a/Ext4Crypt.cpp b/Ext4Crypt.cpp
index c337dbb..68aa240 100644
--- a/Ext4Crypt.cpp
+++ b/Ext4Crypt.cpp
@@ -1,5 +1,23 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
 #include "Ext4Crypt.h"
 
+#include "Utils.h"
+
 #include <iomanip>
 #include <map>
 #include <fstream>
@@ -23,11 +41,17 @@
 #include "ext4_crypt_init_extensions.h"
 
 #define LOG_TAG "Ext4Crypt"
-#include "cutils/log.h"
+
+#include <cutils/fs.h>
+#include <cutils/log.h>
 #include <cutils/klog.h>
+
 #include <base/file.h>
+#include <base/logging.h>
 #include <base/stringprintf.h>
 
+using android::base::StringPrintf;
+
 namespace {
     // Key length in bits
     const int key_length = 128;
@@ -509,18 +533,14 @@
         .Set(fieldname, std::string(value)) ? 0 : -1;
 }
 
-static std::string get_key_path(
-    const char *mount_path,
-    const char *user_handle)
-{
+static std::string get_key_path(const char *mount_path, userid_t user_id) {
     // ext4enc:TODO get the path properly
-    auto key_dir = android::base::StringPrintf("%s/misc/vold/user_keys",
-        mount_path);
-    if (mkdir(key_dir.c_str(), 0700) < 0 && errno != EEXIST) {
-        SLOGE("Unable to create %s (%s)", key_dir.c_str(), strerror(errno));
+    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 key_dir + "/" + user_handle;
+    return StringPrintf("%s/%d", key_dir.c_str(), user_id);
 }
 
 // ext4enc:TODO this can't be the only place keys are read from /dev/urandom
@@ -562,13 +582,11 @@
     return key;
 }
 
-static int e4crypt_set_user_policy(const char *mount_path, const char *user_handle,
-                            const char *path, bool create_if_absent)
-{
-    SLOGD("e4crypt_set_user_policy for %s", user_handle);
-    auto user_key = e4crypt_get_key(
-        get_key_path(mount_path, user_handle),
-        create_if_absent);
+static int e4crypt_set_user_policy(const char *mount_path, userid_t user_id,
+        const char *path, bool create_if_absent) {
+    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);
     if (user_key.empty()) {
         return -1;
     }
@@ -579,24 +597,6 @@
     return do_policy_set(path, raw_ref.c_str(), raw_ref.size());
 }
 
-int e4crypt_create_new_user_dir(const char *user_handle, const char *path) {
-    SLOGD("e4crypt_create_new_user_dir(\"%s\", \"%s\")", user_handle, path);
-    if (mkdir(path, S_IRWXU | S_IRWXG | S_IXOTH) < 0) {
-        return -1;
-    }
-    if (chmod(path, S_IRWXU | S_IRWXG | S_IXOTH) < 0) {
-        return -1;
-    }
-    if (chown(path, AID_SYSTEM, AID_SYSTEM) < 0) {
-        return -1;
-    }
-    if (e4crypt_crypto_complete(DATA_MNT_POINT) == 0) {
-        // ext4enc:TODO handle errors from this.
-        e4crypt_set_user_policy(DATA_MNT_POINT, user_handle, path, true);
-    }
-    return 0;
-}
-
 static bool is_numeric(const char *name) {
     for (const char *p = name; *p != '\0'; p++) {
         if (!isdigit(*p))
@@ -626,10 +626,10 @@
         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", result->d_name,
-                user_dir.c_str(), false)) {
+        if (e4crypt_set_user_policy("/data", user_id, user_dir.c_str(), 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());
@@ -638,9 +638,20 @@
     return 0;
 }
 
-int e4crypt_delete_user_key(const char *user_handle) {
-    SLOGD("e4crypt_delete_user_key(\"%s\")", user_handle);
-    auto key_path = get_key_path(DATA_MNT_POINT, user_handle);
+int e4crypt_create_user_key(userid_t user_id) {
+    SLOGD("e4crypt_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).empty()) {
+        return -1;
+    } else {
+        return 0;
+    }
+}
+
+int e4crypt_destroy_user_key(userid_t user_id) {
+    SLOGD("e4crypt_destroy_user_key(%d)", 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);
     auto ext4_key = fill_key(key);
     auto ref = keyname(generate_key_ref(ext4_key.raw, ext4_key.size));
@@ -669,3 +680,66 @@
     // ext4enc:TODO reap the zombie
     return 0;
 }
+
+int e4crypt_unlock_user_key(userid_t user_id, const char* token) {
+    if (property_get_bool("vold.emulate_fbe", false)) {
+        // When in emulation mode, we just use chmod
+        if (chmod(android::vold::BuildDataSystemCePath(user_id).c_str(), 0771) ||
+                chmod(android::vold::BuildDataUserPath(nullptr, user_id).c_str(), 0771)) {
+            PLOG(ERROR) << "Failed to unlock user " << user_id;
+            return -1;
+        }
+    } else {
+        auto user_key = e4crypt_get_key(get_key_path(DATA_MNT_POINT, user_id), false);
+        if (user_key.empty()) {
+            return -1;
+        }
+        auto raw_ref = e4crypt_install_key(user_key);
+        if (raw_ref.empty()) {
+            return -1;
+        }
+    }
+    return 0;
+}
+
+int e4crypt_lock_user_key(userid_t user_id) {
+    if (property_get_bool("vold.emulate_fbe", false)) {
+        // When in emulation mode, we just use chmod
+        if (chmod(android::vold::BuildDataSystemCePath(user_id).c_str(), 0000) ||
+                chmod(android::vold::BuildDataUserPath(nullptr, user_id).c_str(), 0000)) {
+            PLOG(ERROR) << "Failed to lock user " << user_id;
+            return -1;
+        }
+    } else {
+        // TODO: remove from kernel keyring
+    }
+    return 0;
+}
+
+int e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id) {
+    std::string system_ce_path(android::vold::BuildDataSystemCePath(user_id));
+    std::string user_ce_path(android::vold::BuildDataUserPath(volume_uuid, user_id));
+    std::string user_de_path(android::vold::BuildDataUserDePath(volume_uuid, user_id));
+
+    if (fs_prepare_dir(system_ce_path.c_str(), 0700, AID_SYSTEM, AID_SYSTEM)) {
+        PLOG(ERROR) << "Failed to prepare " << system_ce_path;
+        return -1;
+    }
+    if (fs_prepare_dir(user_ce_path.c_str(), 0771, AID_SYSTEM, AID_SYSTEM)) {
+        PLOG(ERROR) << "Failed to prepare " << user_ce_path;
+        return -1;
+    }
+    if (fs_prepare_dir(user_de_path.c_str(), 0771, AID_SYSTEM, AID_SYSTEM)) {
+        PLOG(ERROR) << "Failed to prepare " << user_de_path;
+        return -1;
+    }
+
+    if (e4crypt_crypto_complete(DATA_MNT_POINT) == 0) {
+        if (e4crypt_set_user_policy(DATA_MNT_POINT, user_id, system_ce_path.c_str(), true)
+                || e4crypt_set_user_policy(DATA_MNT_POINT, user_id, user_ce_path.c_str(), true)) {
+            return -1;
+        }
+    }
+
+    return 0;
+}
diff --git a/Ext4Crypt.h b/Ext4Crypt.h
index f5c2871..43b229c 100644
--- a/Ext4Crypt.h
+++ b/Ext4Crypt.h
@@ -1,6 +1,24 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
 #include <stddef.h>
 #include <sys/cdefs.h>
 
+#include <cutils/multiuser.h>
+
 __BEGIN_DECLS
 
 // General functions
@@ -19,7 +37,13 @@
 int e4crypt_set_field(const char* path, const char* fieldname,
                       const char* value);
 int e4crypt_set_user_crypto_policies(const char *path);
-int e4crypt_create_new_user_dir(const char *user_handle, const char *path);
-int e4crypt_delete_user_key(const char *user_handle);
+
+int e4crypt_create_user_key(userid_t user_id);
+int e4crypt_destroy_user_key(userid_t user_id);
+
+int e4crypt_unlock_user_key(userid_t user_id, const char* token);
+int e4crypt_lock_user_key(userid_t user_id);
+
+int e4crypt_prepare_user_storage(const char* volume_uuid, userid_t user_id);
 
 __END_DECLS
diff --git a/Utils.cpp b/Utils.cpp
index d4618af..8f72b44 100644
--- a/Utils.cpp
+++ b/Utils.cpp
@@ -547,10 +547,55 @@
     return res;
 }
 
+static bool isValidFilename(const std::string& name) {
+    if (name.empty() || (name == ".") || (name == "..")
+            || (name.find('/') != std::string::npos)) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
 std::string BuildKeyPath(const std::string& partGuid) {
     return StringPrintf("%s/expand_%s.key", kKeyPath, partGuid.c_str());
 }
 
+std::string BuildDataSystemCePath(userid_t userId) {
+    // TODO: unify with installd path generation logic
+    std::string data(BuildDataPath(nullptr));
+    return StringPrintf("%s/system_ce/%u", data.c_str(), userId);
+}
+
+std::string BuildDataPath(const char* volumeUuid) {
+    // TODO: unify with installd path generation logic
+    if (volumeUuid == nullptr) {
+        return "/data";
+    } else {
+        CHECK(isValidFilename(volumeUuid));
+        return StringPrintf("/mnt/expand/%s", volumeUuid);
+    }
+}
+
+std::string BuildDataUserPath(const char* volumeUuid, userid_t userId) {
+    // TODO: unify with installd path generation logic
+    std::string data(BuildDataPath(volumeUuid));
+    if (volumeUuid == nullptr) {
+        if (userId == 0) {
+            return StringPrintf("%s/data", data.c_str());
+        } else {
+            return StringPrintf("%s/user/%u", data.c_str(), userId);
+        }
+    } else {
+        return StringPrintf("%s/user/%u", data.c_str(), userId);
+    }
+}
+
+std::string BuildDataUserDePath(const char* volumeUuid, userid_t userId) {
+    // TODO: unify with installd path generation logic
+    std::string data(BuildDataPath(volumeUuid));
+    return StringPrintf("%s/user_de/%u", data.c_str(), userId);
+}
+
 dev_t GetDevice(const std::string& path) {
     struct stat sb;
     if (stat(path.c_str(), &sb)) {
diff --git a/Utils.h b/Utils.h
index 228727a..c88325a 100644
--- a/Utils.h
+++ b/Utils.h
@@ -18,6 +18,7 @@
 #define ANDROID_VOLD_UTILS_H
 
 #include <utils/Errors.h>
+#include <cutils/multiuser.h>
 #include <selinux/selinux.h>
 
 #include <vector>
@@ -93,6 +94,12 @@
 
 std::string BuildKeyPath(const std::string& partGuid);
 
+std::string BuildDataSystemCePath(userid_t userid);
+
+std::string BuildDataPath(const char* volumeUuid);
+std::string BuildDataUserPath(const char* volumeUuid, userid_t userid);
+std::string BuildDataUserDePath(const char* volumeUuid, userid_t userid);
+
 dev_t GetDevice(const std::string& path);
 
 std::string DefaultFstabPath();