* See the License for the specific language governing permissions and
* limitations under the License
*/
+#include <mutex>
+
+#include <signal.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <stdio.h>
+#include <mntent.h>
+
+#include <klay/file-user.h>
+#include <klay/filesystem.h>
+#include <klay/dbus/variant.h>
+#include <klay/dbus/connection.h>
+
+#include "logger.h"
+#include "ext4-tool.h"
+#include "engine/encryption/cryptsetup-engine.h"
+#include "key-manager/key-manager.h"
#include "rmi/extension-encryption.h"
+#define EXTENSION_ENGINE CryptsetupEngine
+#define EXTENSION_NAME_DEF "extension"
+
namespace ode {
+namespace {
+
+const size_t DEFAULT_KEY_SIZE = 64;
+
+const char *EXTENSION_DEV_PATH = "/dev/mmcblk1p1";
+const char *EXTENSION_NAME = EXTENSION_NAME_DEF;
+const char *EXTENSION_MAP_PATH = "/dev/mapper/" EXTENSION_NAME_DEF;
+const char *EXTENSION_FS_TYPE = "crypto_LUKS";
+
+const char *PRIVILEGE_PLATFORM = "http://tizen.org/privilege/internal/default/platform";
+
+const char *STORAGED_DBUS_BUSNAME = "org.tizen.system.storage";
+const char *STORAGED_DBUS_OBJECT = "/Org/Tizen/System/Storage/Block/Manager";
+const char *STORAGED_DBUS_INTERFACE = "org.tizen.system.storage.BlockManager";
+
+std::unique_ptr<EXTENSION_ENGINE> engine;
+
+std::mutex apiGuard;
+std::mutex stateGuard;
+std::condition_variable storagedCv;
+
+std::string findMntPath(const std::string &devPath)
+{
+ std::string ret;
+
+ FILE* mtab = setmntent("/etc/mtab", "r");
+ struct mntent* entry = NULL;
+ while ((entry = getmntent(mtab)) != NULL) {
+ if (devPath == entry->mnt_fsname) {
+ ret = entry->mnt_dir;
+ break;
+ }
+ }
+ endmntent(mtab);
+
+ return ret;
+}
+
+void killDependedApplications(const std::string &mntPath)
+{
+ for (pid_t pid : runtime::FileUser::getList(mntPath, true)) {
+ INFO(SINK, "Close process - " + std::to_string(pid));
+ ::kill(pid, SIGKILL);
+ }
+}
+
+bool findKillAndUmount(const std::string &devPath)
+{
+ std::string realMntPath = findMntPath(devPath);
+ if (!realMntPath.empty()) {
+ INFO(SINK, "Closing all applications using an SD card...");
+ killDependedApplications(realMntPath);
+
+ int ret = ::umount(realMntPath.c_str());
+ if (ret != 0) {
+ ERROR(SINK, "The card is still mounted, umount failed with: "
+ + std::to_string(ret));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namsepace
+
ExtensionEncryption::ExtensionEncryption(ODEControlContext &ctx) :
- context(ctx)
+ context(ctx),
+ currentReq(Request::NONE)
{
+ context.expose(this, PRIVILEGE_PLATFORM, (int)(ExtensionEncryption::mount)(std::string));
+ context.expose(this, PRIVILEGE_PLATFORM, (int)(ExtensionEncryption::umount)());
+ context.expose(this, PRIVILEGE_PLATFORM, (int)(ExtensionEncryption::format)(std::string));
+ context.expose(this, "", (int)(ExtensionEncryption::isPasswordInitialized)());
+ context.expose(this, PRIVILEGE_PLATFORM, (int)(ExtensionEncryption::initPassword)(std::string));
+ context.expose(this, PRIVILEGE_PLATFORM, (int)(ExtensionEncryption::cleanPassword)(std::string));
+ context.expose(this, PRIVILEGE_PLATFORM, (int)(ExtensionEncryption::changePassword)(std::string, std::string));
+ context.expose(this, PRIVILEGE_PLATFORM, (int)(ExtensionEncryption::verifyPassword)(std::string));
+ context.expose(this, "", (int)(ExtensionEncryption::getState)());
+
+ context.createNotification("ExtensionEncryption::mount");
+
+ // TODO: think about handling more than one card / more than one engine
+ // it would require global API change to select which card is handled atm
+ engine.reset(new EXTENSION_ENGINE(EXTENSION_DEV_PATH));
+
+ queryStoraged();
+ subscribeToStoraged();
}
ExtensionEncryption::~ExtensionEncryption()
{
+ unsubscribeFromStoraged();
}
int ExtensionEncryption::mount(const std::string& password)
{
- return -1;
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+ std::unique_lock<std::mutex> stateLock(stateGuard);
+
+ if (getStatePriv() != State::Encrypted) {
+ ERROR(SINK, "Cannot mount, card not inserted or corrupted");
+ return -1;
+ }
+
+ KeyManager::data pwData(password.begin(), password.end());
+ KeyManager keyManager(engine->getKeyMeta());
+
+ if (!keyManager.verifyPassword(pwData)) {
+ ERROR(SINK, "Wrong password passed.");
+ return -2;
+ }
+
+ if (isMounted()) {
+ INFO(SINK, "Already mounted");
+ return 0;
+ }
+
+ KeyManager::data mountKey = keyManager.getMasterKey(pwData);
+
+ INFO(SINK, "Mount extension storage...");
+
+ if (!isOpened()) {
+ // Storaged will mount MAP automatically when it appears, let's prepare
+ currentReq = Request::MOUNT;
+
+ try {
+ INFO(SINK, "Open the MAP of an extension storage...");
+ engine->open(CryptsetupEngine::DeviceType::LUKS, EXTENSION_NAME, mountKey);
+ } catch (runtime::Exception &e) {
+ ERROR(SINK, "Open failed: " + std::string(e.what()));
+ return -3;
+ }
+
+ INFO(SINK, "Wait for the storaged to mount the MAP automatically...");
+ if (!storagedCv.wait_for(stateLock, std::chrono::seconds(1), [this] {
+ return currentReq == Request::NONE;
+ })) {
+ ERROR(SINK, "Storaged timed out mounting the MAP.");
+ return -3;
+ }
+ } else {
+ INFO(SINK, "Ask storaged to mount extension storage...");
+ if (!storagedMount(stateLock)) {
+ return -3;
+ }
+ }
+
+ context.notify("ExtensionEncryption::mount");
+ return 0;
}
int ExtensionEncryption::umount()
{
- return -1;
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+ std::unique_lock<std::mutex> stateLock(stateGuard);
+
+ if (getStatePriv() != State::Encrypted) {
+ ERROR(SINK, "Cannot umount, card not inserted or corrupted.");
+ return -1;
+ }
+
+ if (!isMounted() && !isOpened()) {
+ INFO(SINK, "Already umounted.");
+ return 0;
+ }
+
+ INFO(SINK, "Umount extension storage...");
+
+ if (isMounted()) {
+ INFO(SINK, "Close all applications using extension storage...");
+ killDependedApplications(info[Device::MAP].mntPath);
+
+ INFO(SINK, "Ask storaged to umount extension storage...");
+ if (!storagedUnmount(stateLock)) {
+ return -3;
+ }
+ }
+
+ if (isOpened()) {
+ INFO(SINK, "Close the MAP of an extension storage...");
+ try {
+ CryptsetupEngine::close(EXTENSION_NAME);
+ } catch (runtime::Exception &e) {
+ ERROR(SINK, "Close failed: " + std::string(e.what()));
+ return -3;
+ }
+ }
+
+ return 0;
}
int ExtensionEncryption::format(const std::string &password)
{
- return -1;
+ int status = 0;
+ std::condition_variable workerCv;
+
+ auto formatWorker = [&, this]() {
+ try {
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+ std::unique_lock<std::mutex> stateLock(stateGuard);
+
+ if (!isInserted()) {
+ ERROR(SINK, "There is no card inserted.");
+ status = -1;
+ stateLock.unlock();
+ workerCv.notify_one();
+ return;
+ }
+
+ if (isMounted() || isOpened()) {
+ ERROR(SINK, "The card is still mounted and/or opened, umount and close first.");
+ status = -1;
+ stateLock.unlock();
+ workerCv.notify_one();
+ return;
+ }
+
+ KeyManager::data pwData(password.begin(), password.end());
+ KeyManager keyManager(engine->getKeyMeta());
+
+ if (!keyManager.verifyPassword(pwData)) {
+ ERROR(SINK, "Wrong password passed.");
+ status = -2;
+ stateLock.unlock();
+ workerCv.notify_one();
+ return;
+ }
+
+ // no error, let's notify the main thread it can return
+ status = 1;
+ stateLock.unlock();
+ workerCv.notify_one();
+
+ KeyManager::data masterKey = keyManager.getMasterKey(pwData);
+
+ // TODO: this probably needs a rework, some sort of communication
+ // and/or merge with External. The use case for it might be:
+ // 1. The mmc is mounted by something else, somewhere else. Do we care?
+ // 2. We take over the cd card from External.
+ if (!findKillAndUmount(EXTENSION_DEV_PATH))
+ return;
+
+ INFO(SINK, "Creating LUKS...");
+ engine->format(CryptsetupEngine::DeviceType::LUKS, masterKey);
+
+ INFO(SINK, "Opening LUKS...");
+ std::string mappingPath = engine->open(CryptsetupEngine::DeviceType::LUKS,
+ EXTENSION_NAME,
+ masterKey);
+
+ INFO(SINK, "Creating EXT4...");
+ Ext4Tool::mkfs(mappingPath);
+
+ INFO(SINK, "Closing up the operation...");
+ CryptsetupEngine::close(EXTENSION_NAME);
+ sync();
+
+ INFO(SINK, "Formatting completed");
+ } catch (runtime::Exception &e) {
+ ERROR(SINK, "Formatting thread failed: " + std::string(e.what()));
+ }
+ };
+
+ std::thread asyncWork(formatWorker);
+ asyncWork.detach();
+
+ std::unique_lock<std::mutex> stateLock(stateGuard);
+ if(!workerCv.wait_for(stateLock, std::chrono::seconds(1), [&status] {
+ return status != 0;
+ })) {
+ ERROR(SINK, "Timed out waiting for format status.");
+ return -4;
+ }
+
+ if (status < 0)
+ return status;
+
+ return 0;
}
int ExtensionEncryption::isPasswordInitialized()
{
- return -1;
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+
+ if (engine->isKeyMetaSet()) {
+ return 1;
+ }
+
+ return 0;
}
int ExtensionEncryption::initPassword(const std::string& password)
{
- return -1;
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+
+ KeyManager::data pwData(password.begin(), password.end());
+ KeyManager keyManager;
+
+ keyManager.initPassword(pwData, DEFAULT_KEY_SIZE);
+ engine->setKeyMeta(keyManager.serialize());
+ return 0;
}
int ExtensionEncryption::cleanPassword(const std::string& password)
{
- return -1;
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+
+ KeyManager::data pwData(password.begin(), password.end());
+ KeyManager keyManager(engine->getKeyMeta());
+
+ if (!keyManager.verifyPassword(pwData)) {
+ ERROR(SINK, "Wrong password passed.");
+ return -2;
+ }
+
+ engine->clearKeyMeta();
+ return 0;
}
int ExtensionEncryption::changePassword(const std::string &oldPassword,
const std::string &newPassword)
{
- return -1;
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+
+ KeyManager::data oldPwData(oldPassword.begin(), oldPassword.end());
+ KeyManager::data newPwData(newPassword.begin(), newPassword.end());
+ KeyManager keyManager(engine->getKeyMeta());
+
+ if (!keyManager.verifyPassword(oldPwData)) {
+ ERROR(SINK, "Wrong password passed.");
+ return -2;
+ }
+
+ keyManager.changePassword(oldPwData, newPwData);
+ engine->setKeyMeta(keyManager.serialize());
+
+ return 0;
}
int ExtensionEncryption::verifyPassword(const std::string& password)
{
- return -1;
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+
+ KeyManager::data pwData(password.begin(), password.end());
+ KeyManager keyManager(engine->getKeyMeta());
+
+ if (keyManager.verifyPassword(pwData)) {
+ return 1;
+ }
+
+ return 0;
}
int ExtensionEncryption::getState()
{
- return -1;
+ std::lock_guard<std::mutex> guardLock(apiGuard);
+ std::unique_lock<std::mutex> stateLock(stateGuard);
+
+ return getStatePriv();
+}
+
+void ExtensionEncryption::logStoragedEvent(Operation op, Device d)
+{
+ DEBUG(SINK, "Storaged event:");
+
+ switch(op) {
+ case Operation::ADDED:
+ DEBUG(SINK, " Operation: ADDED");
+ break;
+ case Operation::CHANGED:
+ DEBUG(SINK, " Operation: CHANGED");
+ break;
+ case Operation::REMOVED:
+ DEBUG(SINK, " Operation: REMOVED");
+ break;
+ default:
+ break;
+ }
+
+ switch(d) {
+ case Device::MMC:
+ DEBUG(SINK, " Device: MMC");
+ break;
+ case Device::MAP:
+ DEBUG(SINK, " Device: MAP");
+ break;
+ default:
+ break;
+ }
+
+ DEBUG(SINK, " Mnt Path: " + info[d].mntPath);
+ DEBUG(SINK, " Fs Type: " + info[d].fsType);
+ DEBUG(SINK, " Sys path: " + info[d].sysPath);
+ DEBUG(SINK, " Mounted: " + std::to_string(info[d].mounted));
+ DEBUG(SINK, " ID: " + std::to_string(info[d].storagedId));
+}
+
+void ExtensionEncryption::handleDevice(Operation op,
+ const std::vector<int> &intparams,
+ const std::vector<char*> &strparams)
+{
+ Device d;
+
+ if (std::string(strparams[0]) == EXTENSION_DEV_PATH && intparams[0] == 1) {
+ d = Device::MMC;
+ } else if (std::string(strparams[0]) == EXTENSION_MAP_PATH && intparams[0] == 2) {
+ d = Device::MAP;
+ } else {
+ DEBUG(SINK, "Storaged event: neither extension MMC nor extension mapping, ignoring");
+ return;
+ }
+
+ std::unique_lock<std::mutex> stateLock(stateGuard);
+
+ if (op == Operation::REMOVED) {
+ info[d].clear();
+ } else {
+ info[d].mntPath = strparams[6];
+ info[d].fsType = strparams[3];
+ info[d].sysPath = strparams[1];
+ info[d].mounted = intparams[2];
+ info[d].storagedId = intparams[5];
+ }
+
+ logStoragedEvent(op, d);
+
+ // removing/reformatting the SD card does not automatically close the DM crypt mapping
+ if (d == Device::MMC && (op == Operation::REMOVED ||
+ (op == Operation::CHANGED &&
+ info[d].fsType != EXTENSION_FS_TYPE))) {
+ if (isMounted()) {
+ INFO(SINK, "MMC card removed while still mounted, umounting.");
+ if (!findKillAndUmount(EXTENSION_MAP_PATH)) {
+ return;
+ }
+ }
+
+ if (isOpened()) {
+ INFO(SINK, "MMC card removed while LUKS is still opened, closing.");
+ try {
+ CryptsetupEngine::close(EXTENSION_NAME);
+ } catch (runtime::Exception &e) {
+ ERROR(SINK, "Closing failed: " + std::string(e.what()));
+ return;
+ }
+ }
+
+ return;
+ }
+
+ if (d == Device::MMC && op == Operation::ADDED &&
+ getStatePriv() == State::Encrypted) {
+ // TODO: when UI gets written it should be triggered here.
+
+ return;
+ }
+
+ if (d == Device::MAP && op == Operation::CHANGED) {
+ if ((currentReq == Request::MOUNT && info[Device::MAP].mounted) ||
+ (currentReq == Request::UMOUNT && !info[Device::MAP].mounted)) {
+
+ currentReq = Request::NONE;
+ stateLock.unlock();
+ storagedCv.notify_one();
+
+ return;
+ }
+ }
+}
+
+void ExtensionEncryption::parseVariant(Operation op, dbus::Variant parameters)
+{
+ std::vector<int> intparams(6);
+ std::vector<char*> strparams(7);
+
+ parameters.get("(issssssisibii)",
+ &intparams[0], // block type: 0 - scsi, 1 : mmc, 2 : mapper
+ &strparams[0], // devnode
+ &strparams[1], // syspath
+ &strparams[2], // usage
+ &strparams[3], // fs type
+ &strparams[4], // fs version
+ &strparams[5], // fs uuid enc
+ &intparams[1], // readonly: 0 - rw, 1 - ro
+ &strparams[6], // mount point
+ &intparams[2], // state: 0 - unmount, 1 - mount
+ &intparams[3], // primary: 0 - false, 1 - true
+ &intparams[4], // flags: 1 - unmounted 2 - broken filesystem 4 - no filesystem 8 - not supported 16 - readonly
+ &intparams[5]); // storage id
+
+ handleDevice(op, intparams, strparams);
+}
+
+void ExtensionEncryption::queryStoraged()
+{
+ INFO(SINK, "Querying the storaged for devices...");
+
+ dbus::VariantIterator vi;
+
+ try {
+ dbus::Connection::getSystem().methodcall(STORAGED_DBUS_BUSNAME,
+ STORAGED_DBUS_OBJECT,
+ STORAGED_DBUS_INTERFACE,
+ "GetDeviceList",
+ -1,
+ "(a(issssssisibii))",
+ "(s)",
+ "all").get("(a(issssssisibii))", &vi);
+ } catch (runtime::Exception &e) {
+ ERROR(SINK, "Failed to query storaged: " + std::string(e.what()));
+ }
+
+ std::vector<int> intparams(6);
+ std::vector<char*> strparams(7);
+
+ while(vi.get("(issssssisibii)",
+ &intparams[0], // block type: 0 - scsi, 1 : mmc, 2 : mapper
+ &strparams[0], // devnode
+ &strparams[1], // syspath
+ &strparams[2], // usage
+ &strparams[3], // fs type
+ &strparams[4], // fs version
+ &strparams[5], // fs uuid enc
+ &intparams[1], // readonly: 0 - rw, 1 - ro
+ &strparams[6], // mount point
+ &intparams[2], // state: 0 - unmount, 1 - mount
+ &intparams[3], // primary: 0 - false, 1 - true
+ &intparams[4], // flags: 1 - unmounted 2 - broken filesystem 4 - no filesystem 8 - not supported 16 - readonly
+ &intparams[5])) // storage id
+ {
+ handleDevice(Operation::ADDED, intparams, strparams);
+ }
+}
+
+void ExtensionEncryption::subscribeToStoraged()
+{
+ dbus::Connection &systemDBus = dbus::Connection::getSystem();
+
+ auto subscribe = [&systemDBus, this](Operation op, const std::string &method) {
+ dbus::Connection::SignalCallback callback =
+ std::bind(&ExtensionEncryption::parseVariant,
+ this,
+ op,
+ std::placeholders::_1);
+
+ subId[op] = systemDBus.subscribeSignal("",
+ STORAGED_DBUS_OBJECT,
+ STORAGED_DBUS_INTERFACE,
+ method.c_str(),
+ callback);
+ };
+
+ subscribe(Operation::ADDED, "DeviceAdded");
+ subscribe(Operation::CHANGED, "DeviceChanged");
+ subscribe(Operation::REMOVED, "DeviceRemoved");
+}
+
+void ExtensionEncryption::unsubscribeFromStoraged()
+{
+ dbus::Connection &systemDBus = dbus::Connection::getSystem();
+
+ systemDBus.unsubscribeSignal(subId[Operation::ADDED]);
+ systemDBus.unsubscribeSignal(subId[Operation::CHANGED]);
+ systemDBus.unsubscribeSignal(subId[Operation::REMOVED]);
+}
+
+bool ExtensionEncryption::storagedMount(std::unique_lock<std::mutex> &lock)
+{
+ currentReq = Request::MOUNT;
+
+ int ret = -1;
+ try {
+ dbus::Connection::getSystem().methodcall(STORAGED_DBUS_BUSNAME,
+ STORAGED_DBUS_OBJECT,
+ STORAGED_DBUS_INTERFACE,
+ "Mount",
+ -1,
+ "(i)",
+ "(is)",
+ info[Device::MAP].storagedId,
+ info[Device::MAP].mntPath.c_str()).get("(i)", &ret);
+ } catch (runtime::Exception &e) {
+ ERROR(SINK, "Failed to call mount in storaged: " + std::string(e.what()));
+ currentReq = Request::NONE;
+ return false;
+ }
+
+ if (ret != 0) {
+ ERROR(SINK, "Storaged failed to mount: " + std::to_string(ret));
+ currentReq = Request::NONE;
+ return false;
+ }
+
+ if (!storagedCv.wait_for(lock, std::chrono::seconds(1), [this] {
+ return currentReq == Request::NONE;
+ })) {
+ ERROR(SINK, "Storaged timed out mounting the MAP.");
+ currentReq = Request::NONE;
+ return false;
+ }
+
+ return true;
+}
+
+bool ExtensionEncryption::storagedUnmount(std::unique_lock<std::mutex> &lock)
+{
+ currentReq = Request::UMOUNT;
+
+ int ret = -1;
+ try {
+ dbus::Connection::getSystem().methodcall(STORAGED_DBUS_BUSNAME,
+ STORAGED_DBUS_OBJECT,
+ STORAGED_DBUS_INTERFACE,
+ "Unmount",
+ -1,
+ "(i)",
+ "(ii)",
+ info[Device::MAP].storagedId,
+ 0).get("(i)", &ret);
+ } catch (runtime::Exception &e) {
+ ERROR(SINK, "Failed to call unmount in storaged: " + std::string(e.what()));
+ currentReq = Request::NONE;
+ return false;
+ }
+
+ if (ret != 0) {
+ ERROR(SINK, "Storaged failed to unmount: " + std::to_string(ret));
+ currentReq = Request::NONE;
+ return false;
+ }
+
+ if (!storagedCv.wait_for(lock, std::chrono::seconds(1), [this] {
+ return currentReq == Request::NONE;
+ })) {
+ ERROR(SINK, "Storaged timed out unmounting the MAP.");
+ currentReq = Request::NONE;
+ return false;
+ }
+
+ return true;
+}
+
+int ExtensionEncryption::getStatePriv() const
+{
+ if (!isInserted()) {
+ ERROR(SINK, "Cannot check state, card not inserted");
+ return -1;
+ }
+
+ if (info[Device::MMC].fsType == EXTENSION_FS_TYPE)
+ return State::Encrypted;
+
+ return State::Corrupted;
+}
+
+bool ExtensionEncryption::isInserted() const
+{
+ return info[Device::MMC].storagedId >= 0;
+}
+
+bool ExtensionEncryption::isOpened() const
+{
+ return info[Device::MAP].storagedId >= 0;
+}
+
+bool ExtensionEncryption::isMounted() const
+{
+ return info[Device::MAP].mounted;
}
} // namespace ode