From: Lukasz Pawelczyk Date: Mon, 15 May 2017 15:34:40 +0000 (+0200) Subject: Implementation of ExtensionEncryption with CryptsetupEngine X-Git-Tag: submit/tizen/20170724.061427~2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e0ead75e1527aa73bf349ecaa306ba6fc4295930;p=platform%2Fcore%2Fsecurity%2Fode.git Implementation of ExtensionEncryption with CryptsetupEngine New approach, let storaged do its work Change-Id: I565bfed56322bbbb65b877b05a4ae4fe332954c8 --- diff --git a/rmi/extension-encryption.h b/rmi/extension-encryption.h index cdcb2ab..7fdac84 100644 --- a/rmi/extension-encryption.h +++ b/rmi/extension-encryption.h @@ -18,6 +18,9 @@ #define __EXTENSION_ENCRYPTION_H__ #include +#include + +#include #include "context.h" @@ -52,7 +55,71 @@ public: int getState(); private: + enum Operation { + ADDED = 0, + CHANGED, + REMOVED, + + OPERATION_MAX + }; + + enum Device { + MMC = 0, + MAP, + + DEVICE_MAX + }; + + enum Request { + NONE = 0, + MOUNT, + UMOUNT + }; + + struct DevInfo { + std::string mntPath; + std::string fsType; + std::string sysPath; + bool mounted; + int storagedId; + + DevInfo() : mounted(false), storagedId(-1) {} + ~DevInfo() {} + + void clear() { + mntPath.clear(); + fsType.clear(); + sysPath.clear(); + mounted = false; + storagedId = -1; + } + }; + + void logStoragedEvent(Operation op, Device d); + void handleDevice(Operation op, + const std::vector &intparams, + const std::vector &strparams); + void parseVariant(Operation op, dbus::Variant parameters); + void queryStoraged(); + void subscribeToStoraged(); + void unsubscribeFromStoraged(); + + bool storagedMount(std::unique_lock &lock); + bool storagedUnmount(std::unique_lock &lock); + + int getStatePriv() const; + bool isInserted() const; + bool isOpened() const; + bool isMounted() const; + ODEControlContext& context; + + // currently inserted card information, empty if no card: + DevInfo info[DEVICE_MAX]; + + dbus::Connection::SubscriptionId subId[OPERATION_MAX]; + + Request currentReq; }; } // namespace ode diff --git a/server/extension-encryption.cpp b/server/extension-encryption.cpp index e430d5e..9c7f1a3 100644 --- a/server/extension-encryption.cpp +++ b/server/extension-encryption.cpp @@ -13,64 +13,698 @@ * See the License for the specific language governing permissions and * limitations under the License */ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 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 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("Close process - " + std::to_string(pid)); + ::kill(pid, SIGKILL); + } +} + +bool findKillAndUmount(const std::string &devPath) +{ + std::string realMntPath = findMntPath(devPath); + if (!realMntPath.empty()) { + INFO("Closing all applications using an SD card..."); + killDependedApplications(realMntPath); + + int ret = ::umount(realMntPath.c_str()); + if (ret != 0) { + ERROR("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 guardLock(apiGuard); + std::unique_lock stateLock(stateGuard); + + if (getStatePriv() != State::Encrypted) { + ERROR("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("Wrong password passed."); + return -2; + } + + if (isMounted()) { + INFO("Already mounted"); + return 0; + } + + KeyManager::data mountKey = keyManager.getMasterKey(pwData); + + INFO("Mount extension storage..."); + + if (!isOpened()) { + // Storaged will mount MAP automatically when it appears, let's prepare + currentReq = Request::MOUNT; + + try { + INFO("Open the MAP of an extension storage..."); + engine->open(CryptsetupEngine::DeviceType::LUKS, EXTENSION_NAME, mountKey); + } catch (runtime::Exception &e) { + ERROR("Open failed: " + std::string(e.what())); + return -3; + } + + INFO("Wait for the storaged to mount the MAP automatically..."); + if (!storagedCv.wait_for(stateLock, std::chrono::seconds(1), [this] { + return currentReq == Request::NONE; + })) { + ERROR("Storaged timed out mounting the MAP."); + return -3; + } + } else { + INFO("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 guardLock(apiGuard); + std::unique_lock stateLock(stateGuard); + + if (getStatePriv() != State::Encrypted) { + ERROR("Cannot umount, card not inserted or corrupted."); + return -1; + } + + if (!isMounted() && !isOpened()) { + INFO("Already umounted."); + return 0; + } + + INFO("Umount extension storage..."); + + if (isMounted()) { + INFO("Close all applications using extension storage..."); + killDependedApplications(info[Device::MAP].mntPath); + + INFO("Ask storaged to umount extension storage..."); + if (!storagedUnmount(stateLock)) { + return -3; + } + } + + if (isOpened()) { + INFO("Close the MAP of an extension storage..."); + try { + CryptsetupEngine::close(EXTENSION_NAME); + } catch (runtime::Exception &e) { + ERROR("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 guardLock(apiGuard); + std::unique_lock stateLock(stateGuard); + + if (!isInserted()) { + ERROR("There is no card inserted."); + status = -1; + stateLock.unlock(); + workerCv.notify_one(); + return; + } + + if (isMounted() || isOpened()) { + ERROR("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("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("Creating LUKS..."); + engine->format(CryptsetupEngine::DeviceType::LUKS, masterKey); + + INFO("Opening LUKS..."); + std::string mappingPath = engine->open(CryptsetupEngine::DeviceType::LUKS, + EXTENSION_NAME, + masterKey); + + INFO("Creating EXT4..."); + Ext4Tool::mkfs(mappingPath); + + INFO("Closing up the operation..."); + CryptsetupEngine::close(EXTENSION_NAME); + sync(); + + INFO("Formatting completed"); + } catch (runtime::Exception &e) { + ERROR("Formatting thread failed: " + std::string(e.what())); + } + }; + + std::thread asyncWork(formatWorker); + asyncWork.detach(); + + std::unique_lock stateLock(stateGuard); + if(!workerCv.wait_for(stateLock, std::chrono::seconds(1), [&status] { + return status != 0; + })) { + ERROR("Timed out waiting for format status."); + return -4; + } + + if (status < 0) + return status; + + return 0; } int ExtensionEncryption::isPasswordInitialized() { - return -1; + std::lock_guard guardLock(apiGuard); + + if (engine->isKeyMetaSet()) { + return 1; + } + + return 0; } int ExtensionEncryption::initPassword(const std::string& password) { - return -1; + std::lock_guard guardLock(apiGuard); + + KeyManager::data pwData(password.begin(), password.end()); + KeyManager keyManager; + + keyManager.initPassword(pwData); + engine->setKeyMeta(keyManager.serialize()); + return 0; } int ExtensionEncryption::cleanPassword(const std::string& password) { - return -1; + std::lock_guard guardLock(apiGuard); + + KeyManager::data pwData(password.begin(), password.end()); + KeyManager keyManager(engine->getKeyMeta()); + + if (!keyManager.verifyPassword(pwData)) { + ERROR("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 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("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 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 guardLock(apiGuard); + std::unique_lock stateLock(stateGuard); + + return getStatePriv(); +} + +void ExtensionEncryption::logStoragedEvent(Operation op, Device d) +{ + DEBUG("Storaged event:"); + + switch(op) { + case Operation::ADDED: + DEBUG(" Operation: ADDED"); + break; + case Operation::CHANGED: + DEBUG(" Operation: CHANGED"); + break; + case Operation::REMOVED: + DEBUG(" Operation: REMOVED"); + break; + default: + break; + } + + switch(d) { + case Device::MMC: + DEBUG(" Device: MMC"); + break; + case Device::MAP: + DEBUG(" Device: MAP"); + break; + default: + break; + } + + DEBUG(" Mnt Path: " + info[d].mntPath); + DEBUG(" Fs Type: " + info[d].fsType); + DEBUG(" Sys path: " + info[d].sysPath); + DEBUG(" Mounted: " + std::to_string(info[d].mounted)); + DEBUG(" ID: " + std::to_string(info[d].storagedId)); +} + +void ExtensionEncryption::handleDevice(Operation op, + const std::vector &intparams, + const std::vector &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("Storaged event: neither extension MMC nor extension mapping, ignoring"); + return; + } + + std::unique_lock 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("MMC card removed while still mounted, umounting."); + if (!findKillAndUmount(EXTENSION_MAP_PATH)) { + return; + } + } + + if (isOpened()) { + INFO("MMC card removed while LUKS is still opened, closing."); + try { + CryptsetupEngine::close(EXTENSION_NAME); + } catch (runtime::Exception &e) { + ERROR("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 intparams(6); + std::vector 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("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("Failed to query storaged: " + std::string(e.what())); + } + + std::vector intparams(6); + std::vector 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 &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("Failed to call mount in storaged: " + std::string(e.what())); + currentReq = Request::NONE; + return false; + } + + if (ret != 0) { + ERROR("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("Storaged timed out mounting the MAP."); + currentReq = Request::NONE; + return false; + } + + return true; +} + +bool ExtensionEncryption::storagedUnmount(std::unique_lock &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("Failed to call unmount in storaged: " + std::string(e.what())); + currentReq = Request::NONE; + return false; + } + + if (ret != 0) { + ERROR("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("Storaged timed out unmounting the MAP."); + currentReq = Request::NONE; + return false; + } + + return true; +} + +int ExtensionEncryption::getStatePriv() const +{ + if (!isInserted()) { + ERROR("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 diff --git a/server/external-encryption.cpp b/server/external-encryption.cpp index 350ef2b..70a4aa5 100644 --- a/server/external-encryption.cpp +++ b/server/external-encryption.cpp @@ -67,7 +67,7 @@ void externalCallback(dbus::Variant parameters) char* strparams[7]; parameters.get("(issssssisibii)", - &intparams[0], // block type: 0 - scsi, 1 : mmc + &intparams[0], // block type: 0 - scsi, 1 : mmc, 2 : mapper &strparams[0], // devnode &strparams[1], // syspath &strparams[2], // usage @@ -81,6 +81,14 @@ void externalCallback(dbus::Variant parameters) &intparams[4], // flags: 1 - unmounted 2 - broken filesystem 4 - no filesystem 8 - not supported 16 - readonly &intparams[5]); // strage id + // TODO: this implementation probably would do better if it was on par with + // ExtensionEncryption in terms of how it detects and reacts to card events + if (intparams[0] != 1 || (std::string(strparams[3]) != "vfat" && + std::string(strparams[3]) != "ext4")) { + DEBUG("Storaged says it's not a regular SD card. Ignoring."); + return; + } + if(intparams[2] == 0) { INFO("Unmounted"); } else { diff --git a/server/server.cpp b/server/server.cpp index 4dada85..5d6ab6b 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -45,6 +45,8 @@ Server::Server() audit::Logger::setBackend(new audit::DlogLogSink()); audit::Logger::setTag("ODE"); + INFO("ODE server starting."); + service.reset(new rmi::Service(ODE_MANAGER_ADDRESS)); service->setPrivilegeChecker(std::bind(&Server::checkPeerPrivilege, this, _1, _2));