/*
* 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;
}