/* * Copyright (c) 2017-2018 Nitrokey UG * * This file is part of Nitrokey App. * * Nitrokey App is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * Nitrokey App is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Nitrokey App. If not, see . * * SPDX-License-Identifier: GPL-3.0 */ #include "StorageActions.h" #include "src/ui/pindialog.h" #include "src/utils/bool_values.h" #include #ifdef Q_OS_LINUX #include "systemutils.h" #include // for unmounting on linux #endif // Q_OS_LINUX #if defined(Q_OS_LINUX) || defined(Q_OS_MAC) #include //for sync syscall #endif // Q_OS_LINUX || Q_OS_MAC #include #include #include #define LOCAL_PASSWORD_SIZE 40 #include #include #include void unmountEncryptedVolumes() { return; #if defined(Q_OS_LINUX) std::string endev = systemutils::getEncryptedDevice(); if (endev.size() < 1) return; std::string mntdir = systemutils::getMntPoint(endev); qDebug() << "Unmounting " << mntdir.c_str(); // TODO polling with MNT_EXPIRE? test which will suit better // int err = umount2("/dev/nitrospace", MNT_DETACH); int err = umount(mntdir.c_str()); if (err != 0) { qDebug() << "Unmount error: " << strerror(errno); } #endif // Q_OS_LINUX } void local_sync() { // TODO TEST unmount during/after big data transfer fflush(NULL); // for windows, not necessarly needed or working #if defined(Q_OS_LINUX) || defined(Q_OS_MAC) sync(); #endif // Q_OS_LINUX || Q_OS_MAC // manual says sync blocks until it's done, but they // are not guaranteeing will this save data integrity anyway, // additional sleep might help OwnSleep::sleep(1); // unmount does sync on its own additionally (if successful) unmountEncryptedVolumes(); } void StorageActions::startStick20EnableCryptedVolume() { bool answer; if (TRUE == HiddenVolumeActive) { answer = csApplet()->yesOrNoBox(tr("This activity locks your hidden volume. Do you want to " "proceed?\nTo avoid data loss, please unmount the partitions before " "proceeding."), false); if (!answer) return; } PinDialog dialog(PinType::USER_PIN, nullptr); const auto user_wants_to_proceed = QDialog::Accepted == dialog.exec(); if (user_wants_to_proceed) { startProgressFunc(tr("Enabling encrypted volume")); //FIXME use existing translation const auto s = dialog.getPassword(); new ThreadWorker( [s]() -> Data { // FIXME make s shared_ptr to delete after use //or secure string Data data; data["error"] = 0; auto m = nitrokey::NitrokeyManager::instance(); auto l = libada::i(); try{ local_sync(); bool enable_storage_v045_workaround = l->getMinorFirmwareVersion() <= 45; m->unlock_encrypted_volume(s.c_str()); if (enable_storage_v045_workaround) OwnSleep::sleep(5); //workaround for https://github.com/Nitrokey/nitrokey-storage-firmware/issues/31 data["success"] = true; } catch (CommandFailedException &e){ data["error"] = e.last_command_status; if (e.reason_wrong_password()){ data["wrong_password"] = true; } } catch (DeviceCommunicationException &e){ data["error"] = -1; data["comm_error"] = true; } return data; }, [this](Data data){ if(data["success"].toBool()){ CryptedVolumeActive = true; emit storageStatusChanged(); show_message_function(tr("Encrypted volume enabled")); } else if (data["wrong_password"].toBool()){ csApplet()->warningBox(tr("Could not enable encrypted volume.") + " " //FIXME use existing translation + tr("Wrong password.")); } else { csApplet()->warningBox(tr("Could not enable encrypted volume.") + " " + tr("Status code: %1").arg(data["error"].toInt())); //FIXME use existing translation } end_progress_function(); }, this); } } void StorageActions::startStick20DisableCryptedVolume() { if (TRUE == CryptedVolumeActive) { const bool user_wants_to_proceed = csApplet()->yesOrNoBox(tr("This activity locks your encrypted volume. Do you want to " "proceed?\nTo avoid data loss, please unmount the partitions before " "proceeding."), false); if (!user_wants_to_proceed) return; startProgressFunc(tr("Disabling encrypted volume")); //FIXME use existing translation new ThreadWorker( []() -> Data { Data data; try{ local_sync(); auto m = nitrokey::NitrokeyManager::instance(); m->lock_encrypted_volume(); data["success"] = true; } catch (CommandFailedException &e){ data["error"] = e.last_command_status; } catch (DeviceCommunicationException &e){ data["error"] = -1; data["comm_error"] = true; } return data; }, [this](Data data){ if(data["success"].toBool()) { CryptedVolumeActive = false; emit storageStatusChanged(); show_message_function(tr("Encrypted volume disabled")); // for v0.50 and below ask for reinsertion to complete the lock procedure if (libada::i()->getMinorFirmwareVersion() <= 50){ csApplet()->messageBox( tr("To complete the lock procedure, please remove and reconnect the Nitrokey.")); } } else { csApplet()->warningBox(tr("Could not lock encrypted volume.") + " " + tr("Status code: %1").arg(data["error"].toInt())); //FIXME use existing translation } end_progress_function(); }, this); } } void StorageActions::startStick20EnableHiddenVolume() { if (!CryptedVolumeActive) { csApplet()->warningBox(tr("Please enable the encrypted volume first.")); return; } const bool user_wants_to_proceed = csApplet()->yesOrNoBox(tr("This activity locks your encrypted volume. Do you want to " "proceed?\nTo avoid data loss, please unmount the partitions before " "proceeding."), true); if (!user_wants_to_proceed) return; PinDialog dialog(PinType::HIDDEN_VOLUME, nullptr); const auto user_gives_password = dialog.exec() == QDialog::Accepted ; if (!user_gives_password) { return; } startProgressFunc(tr("Enabling hidden volume")); //FIXME use existing translation auto s = dialog.getPassword(); new ThreadWorker( [s]() -> Data { //FIXME transport throuugh shared_ptr or secure string Data data; auto m = nitrokey::NitrokeyManager::instance(); auto l = libada::i(); try { local_sync(); bool enable_storage_v045_workaround = l->getMinorFirmwareVersion() <= 45; m->unlock_hidden_volume(s.c_str()); if (enable_storage_v045_workaround) OwnSleep::sleep(5); //workaround for https://github.com/Nitrokey/nitrokey-storage-firmware/issues/31 data["success"] = true; } catch (CommandFailedException &e){ data["error"] = e.last_command_status; if (e.reason_wrong_password()){ data["wrong_password"] = true; } } catch (DeviceCommunicationException &e){ data["error"] = -1; data["comm_error"] = true; } return data; }, [this](Data data){ if(data["success"].toBool()){ HiddenVolumeActive = true; emit storageStatusChanged(); show_message_function(tr("Hidden volume enabled"));//FIXME use existing translation } else if (data["wrong_password"].toBool()){ csApplet()->warningBox(tr("Could not enable hidden volume.") + " " //FIXME use existing translation + tr("Wrong password.")); } else { csApplet()->warningBox(tr("Could not enable hidden volume.") + " " + tr("Status code: %1").arg(data["error"].toInt())); //FIXME use existing translation } end_progress_function(); }, this); } void StorageActions::startStick20DisableHiddenVolume() { const bool user_wants_to_proceed = csApplet()->yesOrNoBox(tr("This activity locks your hidden volume. Do you want to proceed?\nTo " "avoid data loss, please unmount the partitions before proceeding."), true); if (!user_wants_to_proceed) return; startProgressFunc(tr("Disabling hidden volume")); //FIXME use existing translation new ThreadWorker( []() -> Data { Data data; try{ local_sync(); auto m = nitrokey::NitrokeyManager::instance(); m->lock_hidden_volume(); data["success"] = true; } catch (CommandFailedException &e){ data["error"] = e.last_command_status; } catch (DeviceCommunicationException &e){ data["error"] = -1; data["comm_error"] = true; } return data; }, [this](Data data){ if(data["success"].toBool()) { HiddenVolumeActive = false; emit storageStatusChanged(); show_message_function(tr("Hidden volume disabled")); //FIXME use existing translation } else { csApplet()->warningBox(tr("Could not lock hidden volume.") + " " + tr("Status code: %1").arg(data["error"].toInt())); //FIXME use existing translation } end_progress_function(); }, this); } void StorageActions::startLockDeviceAction(bool ask_for_confirmation) { bool user_wants_to_proceed; if ((ask_for_confirmation) && ((TRUE == CryptedVolumeActive) || (TRUE == HiddenVolumeActive))) { user_wants_to_proceed = csApplet()->yesOrNoBox(tr("This activity locks your encrypted volume. Do you want to " "proceed?\nTo avoid data loss, please unmount the partitions before " "proceeding."), true); if (!user_wants_to_proceed) { return; } } startProgressFunc(tr("Locking device")); //FIXME use existing translation new ThreadWorker( []() -> Data { Data data; try { local_sync(); auto m = nitrokey::NitrokeyManager::instance(); m->lock_device(); data["success"] = true; } catch (CommandFailedException &e){ data["error"] = e.last_command_status; } catch (DeviceCommunicationException &e){ data["error"] = -1; data["comm_error"] = true; } return data; }, [this](Data data){ if(data["success"].toBool()) { HiddenVolumeActive = false; CryptedVolumeActive = false; emit storageStatusChanged(); show_message_function(tr("Device locked")); //FIXME use existing translation // for v0.50 and below ask for reinsertion to complete the lock procedure if (libada::i()->getMinorFirmwareVersion() <= 50){ csApplet()->messageBox( tr("To complete the lock procedure, please remove and reconnect the Nitrokey.")); } } else { csApplet()->warningBox(tr("Could not lock device.") + " " + tr("Status code: %1").arg(data["error"].toInt())); //FIXME use existing translation } end_progress_function(); }, this); } #include "stick20updatedialog.h" void StorageActions::startStick20EnableFirmwareUpdate() { UpdateDialog dialogUpdate(nullptr); bool user_wants_to_proceed = dialogUpdate.exec() == QDialog::Accepted; if (!user_wants_to_proceed) { return; } auto successMessage = tr("Device set in update mode"); auto failureMessage = tr("Device could not be set in update mode."); bool success = false; // try with default password // ask for password when fails runAndHandleErrorsInUI(successMessage, "", [&success](){ //FIXME use secure string local_sync(); auto m = nitrokey::NitrokeyManager::instance(); m->enable_firmware_update("12345678"); success = true; },[](){}); if(success) return; PinDialog dialog(PinType::FIRMWARE_PIN); user_wants_to_proceed = QDialog::Accepted == dialog.exec(); if (!user_wants_to_proceed) { return; } auto s = dialog.getPassword(); runAndHandleErrorsInUI(successMessage, failureMessage, [s](){ //FIXME use secure string local_sync(); auto m = nitrokey::NitrokeyManager::instance(); m->enable_firmware_update(s.c_str()); },[](){}); } void StorageActions::startStick20ExportFirmwareToFile() { PinDialog dialog(PinType::ADMIN_PIN); bool user_provided_PIN = QDialog::Accepted == dialog.exec(); if (!user_provided_PIN) { return; } auto s = dialog.getPassword(); //FIXME use secure string //FIXME use existing translation runAndHandleErrorsInUI(tr("Firmware exported"), tr("Could not export firmware."), [s](){ auto m = nitrokey::NitrokeyManager::instance(); m->export_firmware(s.c_str()); }, [](){}); } void StorageActions::startStick20DestroyCryptedVolume(int fillSDWithRandomChars) { bool user_entered_PIN; bool answer; answer = (fillSDWithRandomChars == 1) || csApplet()->yesOrNoBox(tr("WARNING: Generating new AES keys will destroy the encrypted volumes, " "hidden volumes, and password safe! Continue?"), false); if (answer) { PinDialog dialog(PinType::ADMIN_PIN); user_entered_PIN = QDialog::Accepted == dialog.exec(); if (!user_entered_PIN) { return; } auto s = dialog.getPassword(); startProgressFunc(tr("Generating new AES keys")); //FIXME use existing translation new ThreadWorker( [s]() -> Data { //FIXME use secure string Data data; try{ auto m = nitrokey::NitrokeyManager::instance(); m->lock_device(); //lock device to reset its state m->build_aes_key(s.c_str()); data["success"] = true; } catch (CommandFailedException &e){ data["error"] = e.last_command_status; if (e.reason_wrong_password()){ data["wrong_password"] = true; } } catch (DeviceCommunicationException &e){ data["error"] = -1; data["comm_error"] = true; } return data; }, [this, fillSDWithRandomChars, s](Data data){ // FIXME use secure string if(data["success"].toBool()) { emit FactoryReset(); show_message_function(tr("New AES keys generated")); //FIXME use existing translation if (fillSDWithRandomChars != 0) { _execute_SD_clearing(s); } } else if (data["wrong_password"].toBool()){ csApplet()->warningBox(tr("Keys could not be generated.") + " " //FIXME use existing translation + tr("Wrong password.")); } else { csApplet()->warningBox(tr("Keys could not be generated.") + " " + tr("Status code: %1").arg(data["error"].toInt())); //FIXME use existing translation } end_progress_function(); }, this); } } void StorageActions::_execute_SD_clearing(const std::string &s) { //does not need long operation indicator new ThreadWorker( [s]() -> Data { //FIXME use secure string Data data; data["success"] = false; try{ auto m = nitrokey::NitrokeyManager::instance(); m->fill_SD_card_with_random_data(s.c_str()); } catch (LongOperationInProgressException &l){ //expected data["success"] = true; } catch (CommandFailedException &e){ data["error"] = e.last_command_status; if (e.reason_wrong_password()){ data["wrong_password"] = true; } } catch (DeviceCommunicationException &e){ data["error"] = -1; data["comm_error"] = true; } return data; }, [this](Data data){ if(data["success"].toBool()) { QTimer::singleShot(1000, [this](){ emit storageStatusUpdated(); emit longOperationStarted(); emit storageStatusChanged(); }); } else if (data["wrong_password"].toBool()){ csApplet()->warningBox(tr("Could not clear SD card.") + " " //FIXME use existing translation + tr("Wrong password.")); } else { csApplet()->warningBox(tr("Could not clear SD card.") + " " + tr("Status code: %1").arg(data["error"].toInt())); //FIXME use existing translation } }, this); } void StorageActions::startStick20FillSDCardWithRandomChars() { PinDialog dialog(PinType::ADMIN_PIN); bool user_provided_PIN = QDialog::Accepted == dialog.exec(); if (user_provided_PIN) { auto s = dialog.getPassword(); _execute_SD_clearing(s); } } void StorageActions::runAndHandleErrorsInUI(QString successMessage, QString operationFailureMessage, std::function codeToRunInDeviceThread, std::function onSuccessInGuiThread) { // startProgressFunc(successMessage); //TODO enable after moving blocking code to separate thread try{ //TODO run in separate thread codeToRunInDeviceThread(); onSuccessInGuiThread(); show_message_function(successMessage); } catch (CommandFailedException &e){ if (!operationFailureMessage.isEmpty()){ if (e.reason_wrong_password()){ csApplet()->warningBox(operationFailureMessage + " " + tr("Wrong password.")); } else { csApplet()->warningBox(operationFailureMessage + " " + tr("Status code: %1").arg(e.last_command_status)); } } } catch (DeviceCommunicationException &e){ if (!operationFailureMessage.isEmpty()){ csApplet()->warningBox(operationFailureMessage + " " + tr("Communication issue.")); } } // end_progress_function(); } void StorageActions::startStick20ClearNewSdCardFound() { PinDialog dialog(PinType::ADMIN_PIN); bool user_provided_pin = QDialog::Accepted == dialog.exec(); if (!user_provided_pin) { return; } auto s = dialog.getPassword(); auto operationFailureMessage = tr("Flag cannot be cleared."); //FIXME use existing translation auto operationSuccessMessage = tr("Flag cleared."); runAndHandleErrorsInUI(QString(), operationFailureMessage, [s]() { //FIXME use secure string auto m = nitrokey::NitrokeyManager::instance(); m->clear_new_sd_card_warning(s.c_str()); }, [this]() { emit storageStatusUpdated(); }); } void StorageActions::startStick20SetReadOnlyUncryptedVolume() { using nm = nitrokey::NitrokeyManager; auto type = PinType::ADMIN_PIN; if(nm::instance()->set_unencrypted_volume_rorw_pin_type_user()){ type = PinType::USER_PIN; } PinDialog dialog(type); bool user_provided_pin = QDialog::Accepted == dialog.exec(); if (!user_provided_pin) { return; } auto operationFailureMessage = tr("Cannot set unencrypted volume read-only"); //FIXME use existing translation auto operationSuccessMessage = tr("Unencrypted volume set read-only"); //FIXME use existing translation auto s = dialog.getPassword(); runAndHandleErrorsInUI(operationSuccessMessage, operationFailureMessage, [s, type]() { auto m = nitrokey::NitrokeyManager::instance(); //FIXME use secure string switch(type){ case USER_PIN: m->set_unencrypted_read_only(s.c_str()); break; case ADMIN_PIN: m->set_unencrypted_read_only_admin(s.c_str()); break; default: break; } }, [this]() { emit storageStatusChanged(); }); } void StorageActions::startStick20SetReadWriteUncryptedVolume() { using nm = nitrokey::NitrokeyManager; auto type = PinType::ADMIN_PIN; if(nm::instance()->set_unencrypted_volume_rorw_pin_type_user()){ type = PinType::USER_PIN; } PinDialog dialog(type); bool user_provided_pin = QDialog::Accepted == dialog.exec(); if (!user_provided_pin) { return; } auto operationFailureMessage = tr("Cannot set unencrypted volume read-write"); //FIXME use existing translation auto operationSuccessMessage = tr("Unencrypted volume set read-write"); //FIXME use existing translation auto s = dialog.getPassword(); runAndHandleErrorsInUI(operationSuccessMessage, operationFailureMessage, [s, type]() { auto m = nitrokey::NitrokeyManager::instance(); //FIXME use secure string switch(type){ case USER_PIN: m->set_unencrypted_read_write(s.c_str()); break; case ADMIN_PIN: m->set_unencrypted_read_write_admin(s.c_str()); break; default: break; } }, [this]() { emit storageStatusChanged(); }); } void StorageActions::startStick20SetReadOnlyEncryptedVolume() { PinDialog dialog(PinType::ADMIN_PIN); bool user_provided_pin = QDialog::Accepted == dialog.exec(); if (!user_provided_pin) { return; } auto operationFailureMessage = tr("Cannot set encrypted volume read-only"); //FIXME use existing translation auto operationSuccessMessage = tr("Encrypted volume set read-only"); //FIXME use existing translation auto s = dialog.getPassword(); runAndHandleErrorsInUI(operationSuccessMessage, operationFailureMessage, [s]() { auto m = nitrokey::NitrokeyManager::instance(); m->set_encrypted_volume_read_only(s.c_str()); //FIXME use secure string }, [this]() { emit storageStatusChanged(); }); } void StorageActions::startStick20SetReadWriteEncryptedVolume() { PinDialog dialog(PinType::ADMIN_PIN); bool user_provided_pin = QDialog::Accepted == dialog.exec(); if (!user_provided_pin) { return; } auto operationFailureMessage = tr("Cannot set encrypted volume read-write"); //FIXME use existing translation auto operationSuccessMessage = tr("Encrypted volume set read-write"); //FIXME use existing translation auto s = dialog.getPassword(); runAndHandleErrorsInUI(operationSuccessMessage, operationFailureMessage, [s]() { auto m = nitrokey::NitrokeyManager::instance(); m->set_encrypted_volume_read_write(s.c_str()); //FIXME use secure string }, [this]() { emit storageStatusChanged(); }); } void StorageActions::startStick20LockStickHardware() { csApplet()->messageBox(tr("Functionality not implemented in current version")); //FIXME use existing translation return; stick20LockFirmwareDialog firmwareDialog(nullptr); bool user_wants_to_proceed = QDialog::Accepted == firmwareDialog.exec(); if (user_wants_to_proceed) { PinDialog pinDialog(PinType::ADMIN_PIN); bool user_provided_PIN = pinDialog.exec() == QDialog::Accepted; if (!user_provided_PIN) { return; } const auto pass = pinDialog.getPassword(); // TODO stick20SendCommand(STICK20_CMD_SEND_LOCK_STICK_HARDWARE, password); } } void StorageActions::startStick20SetupHiddenVolume() { stick20HiddenVolumeDialog HVDialog(nullptr); if (FALSE == CryptedVolumeActive) { csApplet()->warningBox(tr("Please enable the encrypted volume first.")); return; } auto operationSuccessMessage = tr("Hidden volume created"); auto operationFailureMessage = tr("Hidden volume could not be created."); //FIXME use existing translation bool user_wants_to_proceed = HVDialog.exec() == QDialog::Accepted; if (!user_wants_to_proceed) { return; } const auto d = HVDialog.HV_Setup_st; //FIXME clear securely struct auto p = std::string( reinterpret_cast< char const* >(d.HiddenVolumePassword_au8)); runAndHandleErrorsInUI(operationSuccessMessage, operationFailureMessage, [d, p](){ //FIXME use secure string auto m = nitrokey::NitrokeyManager::instance(); m->create_hidden_volume(d.SlotNr_u8, d.StartBlockPercent_u8, d.EndBlockPercent_u8, p.c_str()); }, [](){}); } StorageActions::StorageActions(QObject *parent, Authentication *auth_admin, Authentication *auth_user) : QObject( parent), auth_admin(auth_admin), auth_user(auth_user) { connect(this, SIGNAL(storageStatusChanged()), this, SLOT(on_StorageStatusChanged())); } #include void StorageActions::on_StorageStatusChanged() { if (!libada::i()->isStorageDeviceConnected()) return; new ThreadWorker( []() -> Data { bool interrupt = QThread::currentThread()->isInterruptionRequested(); static bool first_run = true; int times = first_run? 5 : 3; first_run = false; for (int i = 0; i < times && !interrupt; ++i) { QThread::currentThread()->msleep(500); interrupt = QThread::currentThread()->isInterruptionRequested(); } Data data; if(interrupt) { return data; } auto m = nitrokey::NitrokeyManager::instance(); auto s = m->get_status_storage(); data["encrypted_active"] = s.VolumeActiceFlag_st.encrypted; data["hidden_active"] = s.VolumeActiceFlag_st.hidden; return data; }, [this](Data data){ CryptedVolumeActive = data["encrypted_active"].toBool(); HiddenVolumeActive = data["hidden_active"].toBool(); emit storageStatusUpdated(); }, this, "update storage status"); } void StorageActions::set_start_progress_window(std::function _start_progress_function) { startProgressFunc = _start_progress_function; } void StorageActions::set_end_progress_window(std::function _end_progress_function) { end_progress_function = _end_progress_function; } void StorageActions::set_show_message(std::function _show_message) { show_message_function = _show_message; }