1 /*
2  * Author: Copyright (C) Andrzej Surowiec 2012
3  *                      Parts Rudolf Boeddeker  Date: 2013-08-13
4  * Copyright (c) 2012-2018 Nitrokey UG
5  *
6  * This file is part of Nitrokey App.
7  *
8  * Nitrokey App is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * Nitrokey App is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Nitrokey App. If not, see <http://www.gnu.org/licenses/>.
20  *
21  * SPDX-License-Identifier: GPL-3.0
22  */
23 
24 #include <libnitrokey/NitrokeyManager.h>
25 
26 #include "mainwindow.h"
27 #include "aboutdialog.h"
28 #include "pindialog.h"
29 #include "stick20debugdialog.h"
30 #include "ui_mainwindow.h"
31 
32 #include "nitrokey-applet.h"
33 #include "securitydialog.h"
34 #include "stick20changepassworddialog.h"
35 #include "stick20hiddenvolumedialog.h"
36 #include "stick20lockfirmwaredialog.h"
37 #include "stick20responsedialog.h"
38 #include "libada.h"
39 
40 #include <QDateTime>
41 #include <QDialog>
42 #include <QMenu>
43 #include <QThread>
44 #include <QTimer>
45 #include <QtWidgets>
46 
47 #include "src/core/SecureString.h"
48 
49 #include <QString>
50 #include "src/core/ScopedGuard.h"
51 #include "src/core/ThreadWorker.h"
52 #include "hotpslot.h"
53 
54 #include <QFuture>
55 #include <QtConcurrent/QtConcurrent>
56 #include <QSvgWidget>
57 
58 #include <random>
59 
60 using nm = nitrokey::NitrokeyManager;
61 const auto Communication_error_message = QT_TRANSLATE_NOOP("MainWindow", "Communication error. Please reinsert the device.");
62 
63 
64 const auto Invalid_secret_key_string_details_1 = QT_TRANSLATE_NOOP("MainWindow", "The secret string you have entered is invalid. Please reenter it.");
65 const auto Invalid_secret_key_string_details_2 = /*"\n" +*/ QT_TRANSLATE_NOOP("MainWindow", "Details: ");
66 
67 const auto Warning_ev_not_secure_initialize = QT_TRANSLATE_NOOP("MainWindow", "Warning: Encrypted volume is not secure,\nSelect \"Initialize "
68                                                            "device\" option from context menu.");
69 
70 
71 const auto Reset_nitrokeys_time = QT_TRANSLATE_NOOP("MainWindow", "Reset Nitrokey's time?");
72 const auto Warning_devices_clock_not_desynchronized =
73     QT_TRANSLATE_NOOP("MainWindow", "WARNING!\n\nThe time of your computer and Nitrokey are out of "
74                          "sync. Your computer may be configured with a wrong time or "
75                          "your Nitrokey may have been attacked. If an attacker or "
76                          "malware could have used your Nitrokey you should reset the "
77                          "secrets of your configured One Time Passwords. If your "
78                          "computer's time is wrong, please configure it correctly and "
79                          "reset the time of your Nitrokey.\n\nReset Nitrokey's time?");
80 
81 const auto Tray_location_msg = /*"\n" +*/ QT_TRANSLATE_NOOP("MainWindow", "You can find application’s tray icon in system tray in "
82                                       "the right down corner of your screen (Windows) or in the upper right (Linux, MacOS).");
83 
load_settings_page()84 void MainWindow::load_settings_page(){
85     QSettings settings;
86     const auto first_run_key = "main/first_run";
87     auto first_run = settings.value(first_run_key, true).toBool();
88     ui->cb_first_run_message->setChecked(first_run);
89 
90     const auto lang_key = "main/language";
91     auto lang_selected = settings.value(lang_key, "-----").toString();
92 
93     ui->combo_languages->clear();
94     QDir langDir(":/i18n/");
95     auto list = langDir.entryList();
96     for (auto &&translationFile : list) {
97       auto lang = translationFile.remove("nitrokey_").remove(".qm");
98       ui->combo_languages->addItem(lang, lang);
99     }
100     ui->combo_languages->addItem(tr("current:") +" "+ lang_selected, lang_selected);
101     ui->combo_languages->setCurrentText(tr("current:") +" "+ lang_selected);
102 
103     ui->edit_debug_file_path->setText(settings.value("debug/file", "").toString());
104     ui->spin_debug_verbosity->setValue(settings.value("debug/level", 2).toInt());
105     auto settings_debug_enabled = settings.value("debug/enabled", false).toBool();
106     ui->cb_debug_enabled->setChecked(settings_debug_enabled);
107     emit ui->cb_debug_enabled->toggled(settings_debug_enabled);
108     ui->spin_PWS_time->setValue(settings.value("clipboard/PWS_time", 60).toInt());
109     ui->spin_OTP_time->setValue(settings.value("clipboard/OTP_time", 120).toInt());
110     ui->cb_device_connection_message->setChecked(settings.value("main/connection_message", true).toBool());
111     ui->cb_show_main_window_on_connection->setChecked(settings.value("main/show_main_on_connection", true).toBool());
112     ui->cb_hide_main_window_on_connection->setChecked(settings.value("main/close_main_on_connection", false).toBool());
113     ui->cb_hide_main_window_on_close->setChecked(settings.value("main/hide_on_close", true).toBool());
114     ui->cb_show_window_on_start->setChecked(settings.value("main/show_on_start", true).toBool());
115 
116   ui->cb_check_symlink->setChecked(settings.value("storage/check_symlink", false).toBool());
117 #ifndef Q_OS_LINUX
118     ui->cb_check_symlink->setEnabled(false);
119 #endif
120 }
121 
keyPressEvent(QKeyEvent * keyevent)122 void MainWindow::keyPressEvent(QKeyEvent *keyevent)
123 {
124     if (keyevent->key()==Qt::Key_Escape){
125         QSettings settings;
126         if (settings.value("main/hide_on_close", true).toBool()){
127 #ifdef Q_OS_MAC
128             showMinimized();
129 #else
130             hide();
131 #endif
132         } else
133             QApplication::quit();
134     } else {
135         QMainWindow::keyPressEvent(keyevent);
136     }
137 }
138 
MainWindow(QWidget * parent)139 MainWindow::MainWindow(QWidget *parent):
140     QMainWindow(parent),
141     ui(new Ui::MainWindow),
142     clipboard(this),
143     auth_admin(nullptr, Authentication::Type::ADMIN),
144     auth_user(nullptr, Authentication::Type::USER),
145     storage(this, &auth_admin, &auth_user),
146     tray(this, false, false, &storage),
147     HOTP_SlotCount(HOTP_SLOT_COUNT),
148     TOTP_SlotCount(TOTP_SLOT_COUNT)
149 {
150   debug = new DebugDialog(this);
151   connect(this, SIGNAL(DebugData(QString)), debug, SLOT(on_DebugData(QString)));
152 
153   progress_window = std::make_shared<Stick20ResponseDialog>();
154   //TODO move from functors to signals
155   storage.set_start_progress_window( [this](QString msg){ emit ShortOperationBegins(msg); });
156   storage.set_end_progress_window([this](){ emit ShortOperationEnds(); });
157   storage.set_show_message( [this](QString msg){ tray.showTrayMessage(msg); });
158 
159   //TODO make connections in objects instead of accumulating them here
160 //  connect(&storage, SIGNAL(storageStatusChanged()), &tray, SLOT(regenerateMenu()));
161   connect(&storage, SIGNAL(storageStatusUpdated()), &tray, SLOT(regenerateMenu()));
162   connect(&storage, SIGNAL(FactoryReset()), &tray, SLOT(regenerateMenu()));
163   connect(&storage, SIGNAL(FactoryReset()), libada::i().get(), SLOT(on_FactoryReset()));
164   connect(&storage, SIGNAL(longOperationStarted()), this, SLOT(on_KeepDeviceOnline()));
165   connect(&storage, SIGNAL(longOperationStarted()), this, SLOT(on_longOperationStart()));
166 
167   connect(this, SIGNAL(FactoryReset()), &tray, SLOT(regenerateMenu()));
168   connect(this, SIGNAL(FactoryReset()), libada::i().get(), SLOT(on_FactoryReset()));
169 
170   connect(this, SIGNAL(PWS_unlocked()), &tray, SLOT(regenerateMenu()));
171   connect(this, SIGNAL(PWS_unlocked()), this, SLOT(SetupPasswordSafeConfig()));
172   connect(this, SIGNAL(DeviceLocked()), this, SLOT(SetupPasswordSafeConfig()));
173   connect(&storage, SIGNAL(storageStatusChanged()), this, SLOT(SetupPasswordSafeConfig()));
174   connect(&storage, SIGNAL(storageStatusChanged()), this, SLOT(storage_check_symlink()));
175   connect(this, SIGNAL(PWS_slot_saved(int)), &tray, SLOT(regenerateMenu()));
176   connect(this, SIGNAL(PWS_slot_saved(int)), libada::i().get(), SLOT(on_PWS_save(int)));
177 
178   connect(this, SIGNAL(OTP_slot_write(int, bool)), libada::i().get(), SLOT(on_OTP_save(int, bool)));
179   connect(libada::i().get(), SIGNAL(regenerateMenu()), &tray, SLOT(regenerateMenu()));
180   connect(this, SIGNAL(DeviceLocked()), &tray, SLOT(regenerateMenu()));
181   connect(this, SIGNAL(DeviceLocked()), &storage, SLOT(on_StorageStatusChanged()));
182   connect(this, SIGNAL(DeviceConnected()), &storage, SLOT(on_StorageStatusChanged()));
183   connect(&tray, SIGNAL(progress(int)), this, SLOT(updateProgressBar(int)));
184   connect(this, SIGNAL(ShortOperationBegins(QString)), progress_window.get(), SLOT(on_ShortOperationBegins(QString)));
185   connect(this, SIGNAL(ShortOperationEnds()), progress_window.get(), SLOT(on_ShortOperationEnds()));
186   connect(this, SIGNAL(OperationInProgress(int)), &tray, SLOT(updateOperationInProgressBar(int)));
187   connect(this, SIGNAL(OperationInProgress(int)), progress_window.get(), SLOT(updateOperationInProgressBar(int)));
188   connect(this, SIGNAL(DeviceDisconnected()), this, SLOT(on_DeviceDisconnected()));
189   connect(this, SIGNAL(DeviceDisconnected()), libada::i().get(), SLOT(on_DeviceDisconnect()));
190   connect(this, SIGNAL(DeviceConnected()), this, SLOT(on_DeviceConnected()));
191   connect(this, SIGNAL(DeviceConnected()), &tray, SLOT(regenerateMenu()));
192   connect(this, SIGNAL(DeviceDisconnected()), &tray, SLOT(regenerateMenu()));
193   connect(this, SIGNAL(DeviceConnected()), &auth_admin, SLOT(clearTemporaryPasswordForced()));
194   connect(this, SIGNAL(DeviceConnected()), &auth_user, SLOT(clearTemporaryPasswordForced()));
195   connect(this, SIGNAL(DeviceLocked()), &auth_admin, SLOT(clearTemporaryPasswordForced()));
196   connect(this, SIGNAL(DeviceLocked()), &auth_user, SLOT(clearTemporaryPasswordForced()));
197   connect(this, SIGNAL(DeviceConnected()), this, SLOT(ready()));
198 
199   connect(this, SIGNAL(DeviceConnected()), this, SLOT(manageStartPage()));
200   connect(&storage, SIGNAL(storageStatusUpdated()), this, SLOT(manageStartPage()));
201   connect(&storage, SIGNAL(storageStatusChanged()), this, SLOT(manageStartPage()));
202   connect(this, SIGNAL(PWS_unlocked()), this, SLOT(manageStartPage()));
203   connect(this, SIGNAL(DeviceLocked()), this, SLOT(manageStartPage()));
204 
205   ui->setupUi(this);
206   ui->tabWidget->setCurrentIndex(0); // Set first tab active
207   PWS_set_controls_enabled(false);
208   ui->PWS_ButtonCreatePW->setText(QString(tr("Generate random password ")));
209   ui->PWS_progressBar->hide();
210   ui->statusBar->showMessage(tr("Nitrokey disconnected"));
211   ui->l_supportedLength->hide();
212 
213   QTimer *timer = new QTimer(this);
214   connect(timer, SIGNAL(timeout()), this, SLOT(checkConnection()));
215   timer->start(2000);
216   QTimer::singleShot(500, this, SLOT(checkConnection()));
217 
218   keepDeviceOnlineTimer = new QTimer(this);
219   connect(keepDeviceOnlineTimer, SIGNAL(timeout()), this, SLOT(on_KeepDeviceOnline()));
220   keepDeviceOnlineTimer->start(30*1000);
221   QTimer::singleShot(10*1000, this, SLOT(on_KeepDeviceOnline()));
222 
223 
224   connect(ui->secretEdit, SIGNAL(textEdited(QString)), this, SLOT(checkTextEdited()));
225 
226   ui->deleteUserPasswordCheckBox->setEnabled(false);
227   ui->deleteUserPasswordCheckBox->setChecked(false);
228 
229   ui->PWS_EditPassword->setValidator(
230       new utf8FieldLengthValidator(PWS_PASSWORD_LENGTH, ui->PWS_EditPassword));
231   ui->PWS_EditLoginName->setValidator(
232       new utf8FieldLengthValidator(PWS_LOGINNAME_LENGTH, ui->PWS_EditLoginName));
233   ui->PWS_EditSlotName->setValidator(
234       new utf8FieldLengthValidator(PWS_SLOTNAME_LENGTH, ui->PWS_EditSlotName));
235 
236   this->adjustSize();
237   this->move(QApplication::desktop()->screen()->rect().center() - this->rect().center());
238 
239   tray.setFile_menu(menuBar());
240   ui->progressBar->hide();
241 
242   first_run();
243   load_settings_page();
244 
245   make_UI_enabled(false);
246   startConfiguration(false);
247 
248   ui->statusBar->showMessage(tr("Idle"));
249 
250   // Connect dial page
251   connect(ui->btn_dial_PWS, SIGNAL(clicked()), this, SLOT(PWS_Clicked_EnablePWSAccess()));
252   connect(ui->btn_dial_lock, SIGNAL(clicked()), this, SLOT(startLockDeviceAction()));
253   connect(ui->btn_dial_EV, SIGNAL(clicked()), &storage, SLOT(startStick20EnableCryptedVolume()));
254   connect(ui->btn_dial_help, SIGNAL(clicked()), this, SLOT(startHelpAction()));
255   connect(ui->btn_dial_HV, SIGNAL(clicked()), &storage, SLOT(startStick20EnableHiddenVolume()));
256   connect(ui->btn_dial_quit, SIGNAL(clicked()), qApp, SLOT(quit()));
257 
258 
259 }
260 
261 #include <libnitrokey/stick20_commands.h>
262 
make_UI_enabled(bool enabled)263 void MainWindow::make_UI_enabled(bool enabled) {
264   ui->tab->setEnabled(enabled);
265   ui->tab_2->setEnabled(enabled);
266   ui->tab_3->setEnabled(enabled);
267   if(!enabled){
268     ui->btn_dial_PWS->setEnabled(enabled);
269     ui->btn_dial_lock->setEnabled(enabled);
270     ui->btn_dial_EV->setEnabled(enabled);
271     ui->btn_dial_HV->setEnabled(enabled);
272   }
273 }
274 
manageStartPage()275 void MainWindow::manageStartPage(){
276     if (!libada::i()->isDeviceConnected() || libada::i()->get_status_no_except() !=0 ) {
277       make_UI_enabled(false);
278       return;
279     }
280     make_UI_enabled(true);
281 
282 
283     QFuture<bool> PWS_unlocked = QtConcurrent::run(libada::i().get(), &libada::isPasswordSafeUnlocked);
284     PWS_unlocked.waitForFinished();
285     ui->btn_dial_PWS->setEnabled(!PWS_unlocked.result());
286     ui->btn_dial_lock->setEnabled(PWS_unlocked.result());
287 
288     auto is_storage = libada::i()->isStorageDeviceConnected();
289     ui->btn_dial_EV->setEnabled(false);
290     ui->btn_dial_HV->setEnabled(false);
291 
292     if (is_storage){
293         QFuture<nitrokey::stick20::DeviceConfigurationResponsePacket::ResponsePayload> status
294                 = QtConcurrent::run(nitrokey::NitrokeyManager::instance().get(),
295                                     &nitrokey::NitrokeyManager::get_status_storage);
296         status.waitForFinished();
297 
298         bool initialized = !status.result().StickKeysNotInitiated && status.result().SDFillWithRandomChars_u8;
299         if (!initialized){
300           make_UI_enabled(false);
301         }
302 
303         ui->btn_dial_PWS->setEnabled(initialized && !PWS_unlocked.result());
304         ui->btn_dial_EV->setEnabled(initialized && !status.result().VolumeActiceFlag_st.encrypted);
305         ui->btn_dial_HV->setEnabled(initialized && !status.result().VolumeActiceFlag_st.hidden);
306         ui->btn_dial_lock->setEnabled(status.result().VolumeActiceFlag_st.encrypted
307                                       || status.result().VolumeActiceFlag_st.hidden
308                                       || PWS_unlocked.result()
309                                       );
310         ui->PWS_ButtonEnable->setEnabled(ui->btn_dial_PWS->isEnabled());
311     }
312 }
313 
first_run()314 void MainWindow::first_run(){
315   QSettings settings;
316   const auto first_run_key = "main/first_run";
317   auto first_run = settings.value(first_run_key, true).toBool();
318   if (!first_run) return;
319 
320   auto msg = tr("The Nitrokey App is available as an icon in the tray bar.");
321   tray.showTrayMessage(msg);
322   msg += "\n" + tr(Tray_location_msg);
323   msg += " " + tr("Would you like to show this message again?");
324   bool user_wants_to_be_reminded = csApplet()->yesOrNoBox(msg, true);
325   if(!user_wants_to_be_reminded){
326       settings.setValue(first_run_key, false);
327   }
328 
329   //TODO insert call to First run configuration wizard here
330 }
331 
checkConnection()332 void MainWindow::checkConnection() {
333   using cs = ConnectionState;
334 
335   if (!check_connection_mutex.tryLock(100)){
336     if (debug_mode)
337       qDebug("checkConnection skip");
338     return;
339   }
340 
341   ScopedGuard mutexGuard([this](){
342       check_connection_mutex.unlock();
343   });
344 
345   bool deviceConnected = libada::i()->isDeviceConnected() && !libada::i()->have_communication_issues_occurred();
346 
347   static std::atomic_int connection_trials {0};
348 
349   if(!deviceConnected && nm::instance()->could_current_device_be_enumerated()){
350     connection_trials++;
351     if(connection_trials%5==0){
352       csApplet()->warningBox(
353           tr("Device lock detected, please remove and insert the device again.\nIf problem will occur again please: \n1. Close the application\n2. Reinsert the device\n3. Wait 30 seconds and start application")
354       );
355     }
356   }
357 
358   if (deviceConnected){
359         connection_trials = std::max(0, static_cast<int>(connection_trials )- 1);
360           if(connectionState == cs::disconnected){
361               connectionState = cs::connected;
362               if (debug_mode)
363                 emit ShortOperationBegins("Connecting Device");
364 
365               QTimer::singleShot(3000, [this](){
366                   emit ShortOperationEnds();
367                   nitrokey::NitrokeyManager::instance()->connect();
368 
369               //on connection
370                   emit DeviceConnected();
371               });
372           }
373 
374   } else { //device not connected
375       if(connectionState == cs::connected){
376           connectionState = cs::disconnected;
377       //on disconnection
378       emit DeviceDisconnected();
379     }
380     nitrokey::NitrokeyManager::instance()->connect();
381   }
382 }
383 
initialTimeReset()384 void MainWindow::initialTimeReset() {
385   if (!libada::i()->isDeviceConnected() || long_operation_in_progress) {
386     return;
387   }
388 
389   QFuture<bool> is_time_synchronized = QtConcurrent::run(libada::i().get(), &libada::is_time_synchronized);
390   is_time_synchronized.waitForFinished();
391 
392   if (!is_time_synchronized.result()) {
393     bool answer = csApplet()->detailedYesOrNoBox(tr("Time is out-of-sync") + " - " + tr(Reset_nitrokeys_time), tr(Warning_devices_clock_not_desynchronized),
394       false);
395     if (answer) {
396       auto res = libada::i()->set_current_time();
397       if (res) {
398           tray.showTrayMessage(tr("Time reset!"));
399       }
400     }
401   }
402 }
403 
~MainWindow()404 MainWindow::~MainWindow() {
405   nm::instance()->set_log_function([](std::string /*data*/){});
406   delete ui;
407 }
408 
closeEvent(QCloseEvent * event)409 void MainWindow::closeEvent(QCloseEvent *event) {
410   QSettings settings;
411   if (settings.value("main/hide_on_close", true).toBool()){
412 #ifdef Q_OS_MAC
413      showMinimized();
414 #else
415       hide();
416 #endif
417       event->ignore();
418   } else {
419       QApplication::quit();
420   }
421 }
422 
showEvent(QShowEvent * event)423 void MainWindow::showEvent(QShowEvent *event) {
424   if (suppress_next_show){
425     suppress_next_show = false;
426 #ifdef Q_OS_MAC
427     QTimer::singleShot(0, this, SLOT(showMinimized()));
428 #else
429     QTimer::singleShot(0, this, SLOT(hide()));
430 #endif
431   }
432 }
433 
hideOnStartup()434 void MainWindow::hideOnStartup()
435 {
436   suppress_next_show = true;
437 }
438 
generateComboBoxEntrys()439 void MainWindow::generateComboBoxEntrys() {
440   //FIXME run in separate thread
441   int i;
442 
443   ui->slotComboBox->clear();
444 
445   for (i = 0; i < TOTP_SlotCount; i++) {
446     auto slotName = libada::i()->getTOTPSlotName(i);
447     if (slotName.empty())
448       ui->slotComboBox->addItem(QString(tr("TOTP slot ")).append(QString::number(i + 1, 10)));
449     else
450       ui->slotComboBox->addItem(QString(tr("TOTP slot "))
451                                     .append(QString::number(i + 1, 10))
452                                     .append(" [")
453                                     .append(QString::fromStdString(slotName))
454                                     .append("]"));
455   }
456 
457   ui->slotComboBox->insertSeparator(TOTP_SlotCount + 1);
458 
459   for (i = 0; i < HOTP_SlotCount; i++) {
460     auto slotName = libada::i()->getHOTPSlotName(i);
461     if (slotName.empty())
462       ui->slotComboBox->addItem(QString(tr("HOTP slot ")).append(QString::number(i + 1, 10)));
463     else
464       ui->slotComboBox->addItem(QString(tr("HOTP slot "))
465                                     .append(QString::number(i + 1, 10))
466                                     .append(" [")
467                                     .append(QString::fromStdString(slotName))
468                                     .append("]"));
469   }
470 
471   ui->slotComboBox->setCurrentIndex(0);
472 }
473 
474 
475 
generateHOTPConfig(OTPSlot * slot)476 void MainWindow::generateHOTPConfig(OTPSlot *slot) {
477   slot->type = OTPSlot::OTPType::HOTP;
478 
479   uint8_t selectedSlot = ui->slotComboBox->currentIndex();
480   selectedSlot -= (TOTP_SlotCount + 1);
481 
482   if (selectedSlot < HOTP_SlotCount) {
483     slot->slotNumber = selectedSlot;// + 0x10;
484 
485     generateOTPConfig(slot);
486 
487     memset(slot->counter, 0, 8);
488     // Nitrokey Storage needs counter value in text but Pro in binary [#60]
489       bool conversionSuccess = false;
490       uint64_t counterFromGUI = 0;
491       if (0 != ui->counterEdit->text().toLatin1().length()) {
492         counterFromGUI = ui->counterEdit->text().toLatin1().toULongLong(&conversionSuccess);
493       }
494       if (conversionSuccess) {
495         memcpy(slot->counter, &counterFromGUI, sizeof counterFromGUI);
496       } else {
497           csApplet()->warningBox(tr("Counter value not copied - there was an error in conversion. "
498                                             "Setting counter value to 0. Please retry."));
499       }
500     }
501 }
502 
503 #include <src/GUI/ManageWindow.h>
504 
505 #include <cppcodec/hex_upper.hpp>
506 
507 
generateOTPConfig(OTPSlot * slot)508 void MainWindow::generateOTPConfig(OTPSlot *slot) {
509   using hex = cppcodec::hex_upper;
510 
511   std::string secret_hex;
512   ui->base32RadioButton->toggle();
513   auto cleaned = getOTPSecretCleaned(ui->secretEdit->text());
514   ui->secretEdit->setText(cleaned);
515   auto secretFromGUI = cleaned.toLatin1().toStdString();
516 
517   if(ui->base32RadioButton->isChecked()){
518     auto secret_raw = decodeBase32Secret(secretFromGUI, debug_mode);
519     secret_hex = hex::encode(secret_raw);
520   } else{
521     secret_hex = secretFromGUI;
522   }
523 
524   size_t toCopy = std::min(sizeof(slot->secret)-1, (const size_t &) secret_hex.length());
525   if (!libada::i()->is_secret320_supported() && toCopy > 40){
526     toCopy = 40;
527   }
528   memset(slot->secret, 0, sizeof(slot->secret));
529   std::copy(secret_hex.begin(), secret_hex.begin()+toCopy, slot->secret);
530 
531   //TODO to rewrite
532   QByteArray slotNameFromGUI = QByteArray(this->ui->nameEdit->text().toLatin1());
533   memset(slot->slotName, 0, sizeof(slot->slotName));
534   toCopy = std::min(sizeof(slot->slotName)-1, (const size_t &) slotNameFromGUI.length());
535   memcpy(slot->slotName, slotNameFromGUI.constData(), toCopy);
536 
537   memset(slot->tokenID, 0, sizeof(slot->tokenID));
538   QByteArray ompFromGUI = "";
539   toCopy = std::min(2ul, (const unsigned long &) ompFromGUI.length());
540   memcpy(slot->tokenID, ompFromGUI.constData(), toCopy);
541 
542   QByteArray ttFromGUI = "";
543   toCopy = std::min(2ul, (const unsigned long &) ttFromGUI.length());
544   memcpy(slot->tokenID + 2, ttFromGUI.constData(), toCopy);
545 
546   QByteArray muiFromGUI = "";
547   toCopy = std::min(8ul, (const unsigned long &) muiFromGUI.length());
548   memcpy(slot->tokenID + 4, muiFromGUI.constData(), toCopy);
549 
550 //  slot->tokenID[12] = (uint8_t) (this->ui->keyboardComboBox->currentIndex() & 0xFF);
551 
552   slot->config = 0;
553   if (ui->digits8radioButton->isChecked())
554       slot->config += (1 << 0);
555 
556 }
557 
generateTOTPConfig(OTPSlot * slot)558 void MainWindow::generateTOTPConfig(OTPSlot *slot) {
559   slot->type = OTPSlot::OTPType::TOTP;
560 
561   uint8_t selectedSlot = ui->slotComboBox->currentIndex();
562 
563   // get the TOTP slot number
564   if (selectedSlot < TOTP_SlotCount) {
565     slot->slotNumber = selectedSlot;// + 0x20;
566 
567     generateOTPConfig(slot);
568 
569     uint16_t lastInterval = ui->intervalSpinBox->value();
570 
571     if (lastInterval < 1)
572       lastInterval = 1;
573 
574     slot->interval = lastInterval;
575   }
576 }
577 
generateAllConfigs()578 void MainWindow::generateAllConfigs() {
579   displayCurrentSlotConfig();
580 //  generateMenu(false);
581 }
582 
updateSlotConfig(const nitrokey::ReadSlot::ResponsePayload & p,Ui::MainWindow * ui)583 void updateSlotConfig(const nitrokey::ReadSlot::ResponsePayload &p, Ui::MainWindow* ui)  {
584   if (p.use_8_digits)
585     ui->digits8radioButton->setChecked(true);
586   else
587     ui->digits6radioButton->setChecked(true);
588 }
589 
displayCurrentTotpSlotConfig(uint8_t slotNo)590 void MainWindow::displayCurrentTotpSlotConfig(uint8_t slotNo) {
591   ui->label_5->setText(tr("TOTP length:"));
592   ui->label_6->hide();
593   ui->counterEdit->hide();
594   ui->setToZeroButton->hide();
595   ui->setToRandomButton->hide();
596   ui->labelNotify->hide();
597   ui->intervalLabel->show();
598   ui->intervalSpinBox->show();
599   ui->checkBox->setEnabled(false);
600   ui->secretEdit->clear();
601   ui->secretEdit->setPlaceholderText("********************************");
602 
603   ui->nameEdit->setText(QString::fromStdString(libada::i()->getTOTPSlotName(slotNo)));
604   ui->base32RadioButton->setChecked(true);
605 
606   ui->counterEdit->setText("0");
607   ui->digits6radioButton->setChecked(true);
608 
609   std::string cardSerial = libada::i()->get_serial_number();
610   ui->intervalSpinBox->setValue(30);
611 
612   //TODO move reading to separate thread
613 
614 
615   try{
616     if (libada::i()->isTOTPSlotProgrammed(slotNo)) {
617       //FIXME use separate thread
618       auto p = nm::instance()->get_TOTP_slot_data(slotNo);
619       updateSlotConfig(p, ui);
620       uint64_t interval = p.slot_counter;
621       if (interval < 1) interval = 30;
622       ui->intervalSpinBox->setValue(interval);
623     }
624     ui->secret_key_generated_len->setValue(get_supported_secret_length_hex()/2);
625     ui->secret_key_generated_len->setMaximum(get_supported_secret_length_hex()/2);
626   }
627   catch (DeviceCommunicationException &e){
628     emit DeviceDisconnected();
629     return;
630   }
631 
632 }
633 
634 
displayCurrentHotpSlotConfig(uint8_t slotNo)635 void MainWindow::displayCurrentHotpSlotConfig(uint8_t slotNo) {
636   ui->label_5->setText(tr("HOTP length:"));
637   ui->label_6->show();
638   ui->counterEdit->show();
639   ui->setToZeroButton->show();
640   ui->setToRandomButton->show();
641   ui->labelNotify->hide();
642   ui->intervalLabel->hide();
643   ui->intervalSpinBox->hide();
644   ui->checkBox->setEnabled(false);
645   ui->secretEdit->clear();
646   ui->secretEdit->setPlaceholderText("********************************");
647 
648   ui->nameEdit->setText(QString::fromStdString(libada::i()->getHOTPSlotName(slotNo)));
649 
650   ui->base32RadioButton->setChecked(true);
651   std::string cardSerial = libada::i()->get_serial_number();
652   ui->counterEdit->setText(QString::number(0));
653 
654   try {
655     if (libada::i()->isHOTPSlotProgrammed(slotNo)) {
656       //FIXME use separate thread
657       auto p = nm::instance()->get_HOTP_slot_data(slotNo);
658       updateSlotConfig(p, ui);
659       ui->counterEdit->setText(QString::number(p.slot_counter));
660     }
661     ui->secret_key_generated_len->setValue(get_supported_secret_length_hex()/2);
662     ui->secret_key_generated_len->setMaximum(get_supported_secret_length_hex()/2);
663   }
664   catch (DeviceCommunicationException &e){
665     emit DeviceDisconnected();
666     return;
667   }
668 }
669 
displayCurrentSlotConfig()670 void MainWindow::displayCurrentSlotConfig() {
671   ui->slotComboBox->setEnabled(false);
672   ui->slotComboBox->repaint();
673 
674   uint8_t slotNo = ui->slotComboBox->currentIndex();
675 
676   if (slotNo == 255)
677     return;
678 
679   if (slotNo > TOTP_SlotCount)
680     slotNo -= (TOTP_SlotCount + 1);
681   else
682     slotNo += HOTP_SlotCount;
683 
684   if (slotNo < HOTP_SlotCount)
685     displayCurrentHotpSlotConfig(slotNo);
686   else if ((slotNo >= HOTP_SlotCount) && (slotNo < HOTP_SlotCount + TOTP_SlotCount)) {
687     slotNo -= HOTP_SlotCount;
688     displayCurrentTotpSlotConfig(slotNo);
689   }
690   ui->slotComboBox->setEnabled(true);
691   checkTextEdited();
692 }
693 
displayCurrentGeneralConfig()694 void MainWindow::displayCurrentGeneralConfig() {
695   auto status = libada::i()->get_status();
696 
697 
698   ui->enableUserPasswordCheckBox->setChecked(status.enable_user_password != 0);
699   ui->deleteUserPasswordCheckBox->setChecked(status.delete_user_password != 0);
700 }
startConfigurationMain()701 void MainWindow::startConfigurationMain() {
702     startConfiguration(false);
703     ui->tabWidget->setCurrentIndex(0);
704 }
705 
startConfiguration(bool changeTab)706 void MainWindow::startConfiguration(bool changeTab) {
707     if (long_operation_in_progress) return;
708 
709     displayCurrentSlotConfig();
710     displayCurrentGeneralConfig();
711     SetupPasswordSafeConfig();
712 
713     if (libada::i()->isStorageDeviceConnected()) {
714       ui->counterEdit->setMaxLength(7);
715     }
716 
717     QTimer::singleShot(0, this, SLOT(resizeMin()));
718 
719     if (changeTab){
720       ui->tabWidget->setCurrentIndex(1);
721     }
722     QTimer::singleShot(0, this, [this](){
723       ManageWindow::bringToFocus(this);
724       make_UI_enabled(libada::i()->isDeviceConnected());
725     });
726 }
727 
resizeMin()728 void MainWindow::resizeMin() { resize(minimumSizeHint()); }
729 
730 
startStickDebug()731 void MainWindow::startStickDebug() {
732   debug->show();
733   debug->raise();
734 }
735 
736 #include <QDesktopServices>
737 #include <QUrl>
startHelpAction()738 void MainWindow::startHelpAction() {
739     QString link = "https://www.nitrokey.com/start";
740     QDesktopServices::openUrl(QUrl(link));
741 }
742 
on_longOperationStart()743 void MainWindow::on_longOperationStart(){
744   hide();
745   emit LongOperationStart();
746 }
747 
startAboutDialog()748 void MainWindow::startAboutDialog() {
749   AboutDialog dialog(this);
750   dialog.exec();
751 }
752 
753 
startStick20ActionChangeUpdatePIN()754 void MainWindow::startStick20ActionChangeUpdatePIN() {
755   DialogChangePassword dialog(this, PasswordKind::UPDATE);
756   dialog.InitData();
757   dialog.exec();
758 }
759 
startStick20ActionChangeUserPIN()760 void MainWindow::startStick20ActionChangeUserPIN() {
761   DialogChangePassword dialog(this, PasswordKind::USER);
762   connect(&dialog, SIGNAL(UserPinLocked()), &tray, SLOT(regenerateMenu()));
763   dialog.InitData();
764   dialog.exec();
765 }
766 
startStick20ActionChangeAdminPIN()767 void MainWindow::startStick20ActionChangeAdminPIN() {
768   DialogChangePassword dialog(this, PasswordKind::ADMIN);
769   dialog.InitData();
770   dialog.exec();
771 }
772 
startResetUserPassword()773 void MainWindow::startResetUserPassword() {
774   DialogChangePassword dialog(this, PasswordKind::RESET_USER);
775   dialog.InitData();
776   dialog.exec();
777 }
778 
779 
780 
storage_check_symlink()781 void MainWindow::storage_check_symlink(){
782 //TODO make better partition detection method
783 #ifdef Q_OS_LINUX
784     QSettings settings;
785     bool should_remind = settings.value("storage/check_symlink", true).toBool();
786     if (should_remind && !QFileInfo("/dev/nitrospace").isSymLink()) {
787         bool user_wants_reminding = csApplet()->yesOrNoBox(
788                                     tr("Warning: Application could not detect any partition on the Encrypted Volume. "
789                                        "Please use graphical GParted or terminal fdisk/parted tools for this.")+
790                                        " " + tr("Would you like to be reminded again?")
791                                     , true);
792         if (!user_wants_reminding){
793             settings.setValue("storage/check_symlink", false);
794         }
795     }
796 #endif
797 }
798 
799 //int MainWindow::stick20SendCommand(uint8_t stick20Command, uint8_t *password) {
800 //  csApplet()->warningBox(tr("There was an error during communicating with device. Please try again."));
801 //  csApplet()->yesOrNoBox(tr("This command fills the encrypted volumes with random data "
802 //                                "and will destroy all encrypted volumes!\n"
803 //                                "It requires more than 1 hour for 32GB. Do you want to continue?"), false);
804 //  csApplet()->warningBox(tr("Either the password is not correct or the command execution resulted "
805 //                                "in an error. Please try again."));
806 //  return (true);
807 //}
808 
on_writeButton_clicked()809 void MainWindow::on_writeButton_clicked() {
810   uint8_t slotNo = (uint8_t) ui->slotComboBox->currentIndex();
811   const auto isHOTP = slotNo > TOTP_SlotCount;
812   slotNo = isHOTP? slotNo - TOTP_SlotCount -1:slotNo;
813 
814 
815   if (ui->nameEdit->text().isEmpty()) {
816     csApplet()->warningBox(tr("Please enter a slotname."));
817     return;
818   }
819 
820   if (!libada::i()->isDeviceConnected()) {
821     csApplet()->warningBox(tr("Nitrokey is not connected!"));
822     return;
823   }
824 
825   OTPSlot otp;
826   try{
827     if (isHOTP) { // HOTP slot
828       generateHOTPConfig(&otp);
829     } else {
830       generateTOTPConfig(&otp);
831     }
832   }
833   catch(const cppcodec::parse_error &e){
834     csApplet()->warningBox(tr(Invalid_secret_key_string_details_1) + "\n" + tr(Invalid_secret_key_string_details_2) + e.what());
835     return;
836   }
837 
838   if (!this->ui->secretEdit->text().isEmpty() && !validate_raw_secret(otp.secret)) {
839     csApplet()->warningBox(tr(Invalid_secret_key_string_details_1) + "\n" + tr(Invalid_secret_key_string_details_2) + tr("secret is not passing validation."));
840     return;
841   }
842 
843   if(auth_admin.authenticate()){
844     try{
845       libada::i()->writeToOTPSlot(otp, auth_admin.getTempPassword().constData());
846       csApplet()->messageBox(tr("Configuration successfully written."));
847       emit OTP_slot_write(slotNo, isHOTP);
848     }
849     catch (const CommandFailedException&){
850       csApplet()->warningBox(tr("Error writing configuration!"));
851     }
852     catch (const InvalidHexString&){
853       csApplet()->warningBox(tr("Provided secret hex string is invalid. Please check input and try again."));
854     }
855   }
856 
857 //    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
858 //    QApplication::restoreOverrideCursor();
859 
860     generateAllConfigs();
861 }
862 
validate_raw_secret(const char * secret) const863 bool MainWindow::validate_raw_secret(const char *secret) const {
864   if(libada::i()->is_nkpro_07_rtm1() && secret[0] == 0){
865       csApplet()->warningBox(tr("Nitrokey Pro v0.7 does not support secrets starting from null byte. Please change the secret."));
866     return false;
867   }
868   //check if the secret consist only from null bytes
869   //(this value is reserved - it would be ignored by device)
870   for (unsigned int i=0; i < SECRET_LENGTH; i++){
871     if (secret[i] != 0){
872       return true;
873     }
874   }
875   csApplet()->warningBox(tr("Your secret is invalid. Please change the secret."));
876   return false;
877 }
878 
on_slotComboBox_currentIndexChanged(int)879 void MainWindow::on_slotComboBox_currentIndexChanged(int) {
880   displayCurrentSlotConfig();
881 }
882 
on_hexRadioButton_toggled(bool checked)883 void MainWindow::on_hexRadioButton_toggled(bool checked) {
884   if (!checked) {
885     return;
886   }
887 
888   QString secret_input = getOTPSecretCleaned(ui->secretEdit->text());
889   ui->secretEdit->setText(secret_input);
890 
891   auto secret = secret_input.toLatin1().toStdString();
892   if (secret.size() != 0) {
893     try{
894       auto secret_raw = decodeBase32Secret(secret, debug_mode);
895       auto secret_hex = QString::fromStdString(cppcodec::hex_upper::encode(secret_raw));
896       ui->secretEdit->setText(secret_hex);
897       clipboard.copyOTP(secret_hex);
898       showNotificationLabel();
899     }
900     catch (const cppcodec::parse_error &e) {
901       ui->secretEdit->setText("");
902       csApplet()->warningBox(tr(Invalid_secret_key_string_details_1) + "\n" + tr(Invalid_secret_key_string_details_2) + e.what());
903     }
904   }
905 }
906 
getOTPSecretCleaned(QString secret_input)907 QString MainWindow::getOTPSecretCleaned(QString secret_input) {
908   secret_input = secret_input.remove('-').remove(' ').trimmed();
909   constexpr auto base32_block_size = 8u;
910   int secret_length = std::min(get_supported_secret_length_base32(),
911                                roundToNextMultiple(secret_input.length(), base32_block_size));
912   secret_input = secret_input.leftJustified(secret_length, '=');
913   return secret_input;
914 }
915 
roundToNextMultiple(const int number,const int multipleOf) const916 unsigned int MainWindow::roundToNextMultiple(const int number, const int multipleOf) const {
917   return static_cast<unsigned int>(
918       number + ((number % multipleOf == 0) ? 0 : multipleOf - number % multipleOf));
919 }
920 
get_supported_secret_length_hex() const921 unsigned int MainWindow::get_supported_secret_length_hex() const {
922   auto local_secret_length = SECRET_LENGTH_HEX;
923   if (!libada::i()->is_secret320_supported()){
924     local_secret_length /= 2;
925   }
926   return local_secret_length;
927 }
928 
929 #include <cppcodec/base32_default_rfc4648.hpp>
930 
on_base32RadioButton_toggled(bool checked)931 void MainWindow::on_base32RadioButton_toggled(bool checked) {
932   if (!checked) {
933     return;
934   }
935 
936   auto secret_hex = ui->secretEdit->text().toStdString();
937   if (secret_hex.size() != 0) {
938     try{
939       auto secret_raw = cppcodec::hex_upper::decode(secret_hex);
940       auto secret_base32 = QString::fromStdString(base32::encode(secret_raw));
941       ui->secretEdit->setText(secret_base32);
942       clipboard.copyOTP(secret_base32);
943       showNotificationLabel();
944     }
945     catch (const cppcodec::parse_error &e) {
946       ui->secretEdit->setText("");
947       csApplet()->warningBox(tr("The secret string you have entered is invalid. Please reenter it.")
948                              +" "+tr("Details: ") + e.what());
949     }
950   }
951 }
952 
on_setToZeroButton_clicked()953 void MainWindow::on_setToZeroButton_clicked() { ui->counterEdit->setText("0"); }
954 
on_setToRandomButton_clicked()955 void MainWindow::on_setToRandomButton_clicked() {
956   quint64 counter;
957   counter = qrand();
958   if (libada::i()->isStorageDeviceConnected()) {
959     const int maxDigits = 7;
960     counter = counter % ((quint64)pow(10, maxDigits));
961   }
962   ui->counterEdit->setText(QString(QByteArray::number(counter, 10)));
963 }
964 
965 
on_enableUserPasswordCheckBox_toggled(bool checked)966 void MainWindow::on_enableUserPasswordCheckBox_toggled(bool checked) {
967   ui->deleteUserPasswordCheckBox->setEnabled(checked);
968   if(checked){
969     //TODO run status request in separate thread or cache result
970     uint8_t delete_user_password = libada::i()->get_status().delete_user_password;
971     ui->deleteUserPasswordCheckBox->setChecked(delete_user_password);
972   }
973 }
974 
on_writeGeneralConfigButton_clicked()975 void MainWindow::on_writeGeneralConfigButton_clicked() {
976   if (!libada::i()->isDeviceConnected()) {
977       csApplet()->warningBox(tr("Nitrokey is not connected!"));
978   }
979     if(!auth_admin.authenticate()){
980       csApplet()->warningBox(tr("Wrong PIN. Please try again."));
981       return;
982     }
983   try{
984     auto password_byte_array = auth_admin.getTempPassword();
985     nm::instance()->write_config(
986         0,0,0,
987         ui->enableUserPasswordCheckBox->isChecked(),
988         ui->deleteUserPasswordCheckBox->isChecked() &&
989         ui->enableUserPasswordCheckBox->isChecked(),
990         password_byte_array.constData()
991     );
992     csApplet()->messageBox(tr("Configuration successfully written."));
993   }
994   catch (CommandFailedException &e){
995     csApplet()->warningBox(tr("Error writing configuration!"));
996   }
997 
998     generateAllConfigs();
999 }
1000 
getHOTPDialog(int slot)1001 void MainWindow::getHOTPDialog(int slot) {
1002   try{
1003     auto OTPcode = getNextCode(0x10 + slot);
1004     if (OTPcode.empty()) return;
1005     clipboard.copyOTP(QString::fromStdString(OTPcode));
1006 
1007 
1008     if (libada::i()->getHOTPSlotName(slot).empty())
1009       tray.showTrayMessage(QString(tr("HOTP slot ")).append(QString::number(slot + 1, 10)),
1010                       tr("One-time password has been copied to clipboard."), INFORMATION,
1011                       TRAY_MSG_TIMEOUT);
1012     else
1013       tray.showTrayMessage(QString(tr("HOTP slot "))
1014                           .append(QString::number(slot + 1, 10))
1015                           .append(" [")
1016                           .append(QString::fromStdString(libada::i()->getHOTPSlotName(slot)))
1017                           .append("]"),
1018                       tr("One-time password has been copied to clipboard."), INFORMATION,
1019                       TRAY_MSG_TIMEOUT);
1020   }
1021   catch(DeviceCommunicationException &e){
1022     tray.showTrayMessage(tr(Communication_error_message));
1023   }
1024 }
1025 
getTOTPDialog(int slot)1026 void MainWindow::getTOTPDialog(int slot) {
1027   try{
1028     auto OTPcode = getNextCode(0x20 + slot);
1029       if (OTPcode.empty()) return;
1030     clipboard.copyOTP(QString::fromStdString(OTPcode));
1031 
1032     if (libada::i()->getTOTPSlotName(slot).empty())
1033       tray.showTrayMessage(QString(tr("TOTP slot ")).append(QString::number(slot + 1, 10)),
1034                            tr("One-time password has been copied to clipboard."), INFORMATION,
1035                            TRAY_MSG_TIMEOUT);
1036     else
1037       tray.showTrayMessage(QString(tr("TOTP slot "))
1038                                .append(QString::number(slot + 1, 10))
1039                                .append(" [")
1040                                .append(QString::fromStdString(libada::i()->getTOTPSlotName(slot)))
1041                                .append("]"),
1042                            tr("One-time password has been copied to clipboard."), INFORMATION,
1043                            TRAY_MSG_TIMEOUT);
1044   }
1045   catch(DeviceCommunicationException &e){
1046     tray.showTrayMessage(tr(Communication_error_message));
1047   }
1048 
1049 }
1050 
on_eraseButton_clicked()1051 void MainWindow::on_eraseButton_clicked() {
1052   bool answer = csApplet()->yesOrNoBox(tr("WARNING: Are you sure you want to erase the slot?"), false);
1053   if (!answer) {
1054     return;
1055   }
1056 
1057   uint8_t slotNo = ui->slotComboBox->currentIndex();
1058 
1059   const auto isHOTP = slotNo > TOTP_SlotCount;
1060   if (isHOTP) {
1061     slotNo -= (TOTP_SlotCount + 1);
1062   }
1063 
1064     if (!auth_admin.authenticate()){
1065         csApplet()->messageBox(tr("Command execution failed. Please try again."));
1066         return;
1067     };
1068   auto password_array = auth_admin.getTempPassword();
1069   if (isHOTP) {
1070     libada::i()->eraseHOTPSlot(slotNo, password_array.constData());
1071   } else {
1072     libada::i()->eraseTOTPSlot(slotNo, password_array.constData());
1073   }
1074   emit OTP_slot_write(slotNo, isHOTP);
1075     csApplet()->messageBox(tr("Slot has been erased successfully."));
1076 
1077 //  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1078 //  QApplication::restoreOverrideCursor();
1079   generateAllConfigs();
1080 }
1081 
get_random()1082 quint32 get_random(){
1083 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
1084   return QRandomGenerator::global()->generate();
1085 #else
1086   static std::random_device dev;
1087   static std::mt19937 rng(dev());
1088   static std::uniform_int_distribution<std::mt19937::result_type> dist(0, UINT32_MAX);
1089   return dist(rng);
1090 #endif
1091 }
1092 
on_randomSecretButton_clicked()1093 void MainWindow::on_randomSecretButton_clicked() {
1094 
1095   int local_secret_length = std::min( (int) get_supported_secret_length_hex()/2, ui->secret_key_generated_len->value());
1096   local_secret_length = std::max(local_secret_length, 1);
1097   uint8_t secret[local_secret_length];
1098 
1099   int i = 0;
1100   while (i < local_secret_length) {
1101     secret[i] = get_random() & 0xFF;
1102       i++;
1103   }
1104 
1105   QByteArray secretArray = QByteArray((char*)secret, sizeof(secret));
1106 
1107 //  ui->base32RadioButton->setChecked(false);
1108   ui->hexRadioButton->setChecked(true);
1109   ui->secretEdit->setText(secretArray.toHex());
1110   ui->checkBox->setEnabled(true);
1111   ui->checkBox->setChecked(true);
1112   clipboard.copyOTP(secretArray);
1113   showNotificationLabel();
1114   this->checkTextEdited();
1115 }
1116 
showNotificationLabel()1117 void MainWindow::showNotificationLabel() {
1118   static qint64 lastTimeNotificationShown = 0;
1119   lastTimeNotificationShown = QDateTime::currentMSecsSinceEpoch();
1120   constexpr auto delayBeforeHiding = 6000;
1121   constexpr auto timer_precision_off = 1000;
1122   ui->labelNotify->show();
1123   QTimer::singleShot(delayBeforeHiding, [this](){
1124     if (QDateTime::currentMSecsSinceEpoch() >
1125         lastTimeNotificationShown + delayBeforeHiding - timer_precision_off){
1126       ui->labelNotify->hide();
1127     }
1128   });
1129 }
1130 
get_supported_secret_length_base32() const1131 unsigned int MainWindow::get_supported_secret_length_base32() const {
1132   auto local_secret_length = SECRET_LENGTH_BASE32;
1133   if (!libada::i()->is_secret320_supported()){
1134     local_secret_length /= 2;
1135   }
1136   return local_secret_length;
1137 }
1138 
on_checkBox_toggled(bool checked)1139 void MainWindow::on_checkBox_toggled(bool checked) {
1140     ui->secretEdit->setEchoMode(checked ? QLineEdit::PasswordEchoOnEdit : QLineEdit::Normal);
1141 }
1142 
checkTextEdited()1143 void MainWindow::checkTextEdited() {
1144   if (!ui->checkBox->isEnabled()) {
1145     ui->checkBox->setEnabled(true);
1146     ui->checkBox->setChecked(false);
1147   }
1148   auto secret_key = ui->secretEdit->text();
1149   bool valid = secret_key.isEmpty();
1150   bool correct_length = true;
1151 
1152   if (!valid){
1153     bool base32 = ui->base32RadioButton->isChecked();
1154     if (base32) {
1155       auto secret = getOTPSecretCleaned(secret_key).toStdString();
1156       if (secret.empty()) return;
1157       try {
1158         correct_length = secret.length() <= get_supported_secret_length_base32();
1159         auto secret_raw = decodeBase32Secret(secret, debug_mode);
1160         valid = validate_raw_secret(reinterpret_cast<const char *>(secret_raw.data()));
1161         valid = valid && correct_length;
1162       }
1163       catch (...) {}
1164     } else { // hex
1165       const auto l = static_cast<unsigned int>(secret_key.length());
1166       correct_length = l <= get_supported_secret_length_hex();
1167       valid = l % 2 == 0;
1168       valid = valid && correct_length;
1169       if (valid){
1170         try{
1171           cppcodec::hex_upper::decode(secret_key.toStdString());
1172         }
1173         catch (const cppcodec::parse_error &e){
1174           if (debug_mode)
1175             qDebug() << e.what();
1176           valid = false;
1177         }
1178       }
1179     }
1180   }
1181 
1182   ui->l_supportedLength->setVisible(!correct_length);
1183   ui->secretEdit->setStyleSheet(valid ? "background: white;" : "background: #FFDDDD;");
1184   ui->base32RadioButton->setEnabled(valid);
1185   ui->hexRadioButton->setEnabled(valid);
1186   ui->writeButton->setEnabled(valid);
1187   ui->btn_copyToClipboard->setEnabled(valid && !secret_key.isEmpty());
1188 }
1189 
SetupPasswordSafeConfig(void)1190 void MainWindow::SetupPasswordSafeConfig(void) {
1191   int i;
1192   QString Slotname;
1193   ui->PWS_ComboBoxSelectSlot->clear();
1194   PWS_set_controls_enabled(PWS_Access);
1195 
1196   try{
1197     libada::i()->get_status(); //WORKAROUND for crashing Storage v0.45
1198     PWS_Access = libada::i()->isPasswordSafeUnlocked();
1199     PWS_set_controls_enabled(PWS_Access);
1200 
1201     // Get active password slots
1202     if (PWS_Access) {
1203       // Setup combobox
1204       ui->PWS_ComboBoxSelectSlot->clear();
1205       ui->PWS_ComboBoxSelectSlot->addItem(QString(tr("<Select Password Safe slot>")));
1206       for (i = 0; i < PWS_SLOT_COUNT; i++) {
1207         if (libada::i()->getPWSSlotStatus(i)) {
1208           ui->PWS_ComboBoxSelectSlot->addItem(
1209               QString(tr("Slot "))
1210                   .append(QString::number(i + 1, 10))
1211                   .append(QString(" [")
1212                               .append(QString::fromStdString(libada::i()->getPWSSlotName(i)))
1213                               .append(QString("]"))));
1214         } else {
1215           ui->PWS_ComboBoxSelectSlot->addItem(
1216               QString(tr("Slot ")).append(QString::number(i + 1, 10)));
1217         }
1218       }
1219     } else {
1220       ui->PWS_ComboBoxSelectSlot->addItem(QString(tr("Unlock password safe")));
1221     }
1222 
1223   }
1224   catch (LongOperationInProgressException &e){
1225     long_operation_in_progress = true;
1226     return;
1227   }
1228 
1229   ui->PWS_ComboBoxSelectSlot->setEnabled(PWS_Access);
1230   ui->PWS_ButtonEnable->setVisible(!PWS_Access);
1231   ui->PWS_Lock->setVisible(PWS_Access);
1232 
1233   ui->PWS_EditSlotName->setMaxLength(PWS_SLOTNAME_LENGTH);
1234   ui->PWS_EditPassword->setMaxLength(PWS_PASSWORD_LENGTH);
1235   ui->PWS_EditLoginName->setMaxLength(PWS_LOGINNAME_LENGTH);
1236 
1237   ui->PWS_CheckBoxHideSecret->setChecked(true);
1238   ui->PWS_EditPassword->setEchoMode(QLineEdit::Password);
1239 }
1240 
on_PWS_ButtonClearSlot_clicked()1241 void MainWindow::on_PWS_ButtonClearSlot_clicked() {
1242   const auto item_number = ui->PWS_ComboBoxSelectSlot->currentIndex();
1243   const int slot_number = item_number - 1;
1244   if (slot_number<0){
1245     return;
1246   }
1247 
1248   bool answer = csApplet()->yesOrNoBox(tr("WARNING: Are you sure you want to erase the slot?"), false);
1249   if (!answer){
1250       return;
1251   }
1252 
1253   if (!libada::i()->getPWSSlotStatus(slot_number)) // Is slot active?
1254   {
1255     csApplet()->messageBox(tr("Slot is erased already."));
1256     return;
1257   }
1258 
1259   try{
1260     libada::i()->erasePWSSlot(slot_number);
1261 
1262     ui->PWS_EditSlotName->setText("");
1263     ui->PWS_EditPassword->setText("");
1264     ui->PWS_EditLoginName->setText("");
1265     ui->PWS_ComboBoxSelectSlot->setItemText(
1266         item_number, tr("Slot ").append(QString::number(slot_number + 1)));
1267     csApplet()->messageBox(tr("Slot has been erased successfully."));
1268     emit PWS_slot_saved(slot_number);
1269   }
1270   catch (CommandFailedException &e){
1271     csApplet()->warningBox(tr("Can't clear slot."));
1272   }
1273 }
1274 
clear_free_cstr(char * _cstr)1275 void clear_free_cstr(char *_cstr){
1276   auto max_string_length = 30ul;
1277   volatile char* cstr = _cstr;
1278   for (size_t i = 0; i < max_string_length; ++i) {
1279     if (cstr[i] == 0) break;
1280     cstr[i] = ' ';
1281   }
1282   free((void *) _cstr);
1283 }
1284 
1285 #include "src/core/ThreadWorker.h"
on_PWS_ComboBoxSelectSlot_currentIndexChanged(int index)1286 void MainWindow::on_PWS_ComboBoxSelectSlot_currentIndexChanged(int index) {
1287   auto dummy_slot = index <= 0;
1288 
1289   PWS_set_controls_enabled(!dummy_slot);
1290 
1291   if (dummy_slot) return; //do not update for dummy slot
1292   index--;
1293 
1294   if (!PWS_Access) {
1295     return;
1296   }
1297   ui->PWS_ComboBoxSelectSlot->setEnabled(false);
1298 
1299   ui->PWS_progressBar->show();
1300   connect(this, SIGNAL(PWS_progress(int)), ui->PWS_progressBar, SLOT(setValue(int)));
1301 
1302   new ThreadWorker(
1303     [index, this]() -> Data {
1304       Data data;
1305         data["slot_filled"] = libada::i()->getPWSSlotStatus(index);
1306         emit PWS_progress(100*1/4);
1307         if (data["slot_filled"].toBool()) {
1308           data["name"] = QString::fromStdString(libada::i()->getPWSSlotName(index));
1309           emit PWS_progress(100*2/4);
1310           //FIXME use secure way
1311           auto pass_cstr = nm::instance()->get_password_safe_slot_password(index);
1312           data["pass"] = QString::fromStdString(pass_cstr);
1313           clear_free_cstr(const_cast<char*>(pass_cstr));
1314           emit PWS_progress(100*3/4);
1315           auto login_cstr = nm::instance()->get_password_safe_slot_login(index);
1316           data["login"] = QString::fromStdString(login_cstr);
1317           clear_free_cstr(const_cast<char*>(login_cstr));
1318         }
1319         emit PWS_progress(100*4/4);
1320         return data;
1321     },
1322     [this](Data data){
1323       if (data["slot_filled"].toBool()) {
1324         ui->PWS_EditSlotName->setText(data["name"].toString());
1325         ui->PWS_EditPassword->setText(data["pass"].toString());
1326         ui->PWS_EditLoginName->setText(data["login"].toString());
1327       }
1328       ui->PWS_ComboBoxSelectSlot->setEnabled(true);
1329         QTimer::singleShot(2000, [this](){
1330             ui->PWS_progressBar->hide();
1331         });
1332 
1333     }, this);
1334 
1335 }
1336 
PWS_set_controls_enabled(bool enabled) const1337 void MainWindow::PWS_set_controls_enabled(bool enabled) const {
1338   ui->PWS_EditSlotName->setText("");
1339   ui->PWS_EditLoginName->setText("");
1340   ui->PWS_EditPassword->setText("");
1341   ui->PWS_EditSlotName->setEnabled(enabled);
1342   ui->PWS_EditLoginName->setEnabled(enabled);
1343   ui->PWS_EditPassword->setEnabled(enabled);
1344   ui->PWS_ButtonSaveSlot->setEnabled(enabled);
1345   ui->PWS_ButtonClearSlot->setEnabled(enabled);
1346   ui->PWS_CheckBoxHideSecret->setEnabled(enabled);
1347   ui->PWS_ButtonCreatePW->setEnabled(enabled);
1348 
1349 }
1350 
on_PWS_CheckBoxHideSecret_toggled(bool checked)1351 void MainWindow::on_PWS_CheckBoxHideSecret_toggled(bool checked) {
1352     ui->PWS_EditPassword->setEchoMode(checked ? QLineEdit::Password : QLineEdit::Normal);
1353 }
1354 
on_PWS_ButtonSaveSlot_clicked()1355 void MainWindow::on_PWS_ButtonSaveSlot_clicked() {
1356   const auto item_number = ui->PWS_ComboBoxSelectSlot->currentIndex();
1357   int slot_number = item_number - 1;
1358   if(slot_number<0) return;
1359 
1360   if(ui->PWS_EditSlotName->text().isEmpty()){
1361     csApplet()->warningBox(tr("Please enter a slotname."));
1362     return;
1363   }
1364   if(ui->PWS_EditPassword->text().isEmpty()){
1365     csApplet()->warningBox(tr("Please enter a password."));
1366     return;
1367   }
1368 
1369   try{
1370     nm::instance()->write_password_safe_slot(slot_number,
1371        ui->PWS_EditSlotName->text().toUtf8().constData(),
1372        ui->PWS_EditLoginName->text().toUtf8().constData(),
1373        ui->PWS_EditPassword->text().toUtf8().constData());
1374     emit PWS_slot_saved(slot_number);
1375     auto item_name = tr("Slot ")
1376         .append(QString::number(item_number))
1377         .append(QString(" [")
1378                     .append(ui->PWS_EditSlotName->text())
1379                     .append(QString("]")));
1380     ui->PWS_ComboBoxSelectSlot->setItemText(
1381         item_number, item_name);
1382     csApplet()->messageBox(tr("Slot successfully written."));
1383   }
1384   catch (CommandFailedException &e){
1385     csApplet()->messageBox(tr("Can't save slot. %1").arg(e.last_command_status));
1386   }
1387   catch (DeviceCommunicationException &e){
1388     csApplet()->messageBox(tr("Can't save slot. %1").arg(tr(Communication_error_message)));
1389   }
1390 }
1391 
1392 
on_PWS_ButtonClose_pressed()1393 void MainWindow::on_PWS_ButtonClose_pressed() { hide(); }
1394 
PWS_Clicked_EnablePWSAccess()1395 void MainWindow::PWS_Clicked_EnablePWSAccess() {
1396   do{
1397     try{
1398       auto user_password = auth_user.getPassword();
1399       if(user_password.empty()) return;
1400       nm::instance()->enable_password_safe(user_password.c_str());
1401       tray.showTrayMessage(tr("Password safe unlocked"));
1402       PWS_Access = true;
1403       emit PWS_unlocked();
1404       return;
1405     }
1406     catch (DeviceCommunicationException &e){
1407       csApplet()->warningBox(tr(Communication_error_message));
1408     }
1409     catch (CommandFailedException &e){
1410       //TODO emit pw safe not available when not available
1411   //    cryptostick->passwordSafeAvailable = FALSE;
1412   //    UnlockPasswordSafeAction->setEnabled(false);
1413   //    csApplet()->warningBox(tr("Password safe is not supported by this device."));
1414   //    ui->tabWidget->setTabEnabled(3, 0);
1415 
1416       if(e.reason_wrong_password()){
1417         //show message if wrong password
1418         csApplet()->warningBox(tr("Wrong user password."));
1419       } else if (e.reason_AES_not_initialized()){
1420         //generate keys if not generated
1421         try{
1422           csApplet()->warningBox(tr("AES keys not initialized. Please provide Admin PIN."));
1423           nm::instance()->build_aes_key(auth_admin.getPassword().c_str());
1424           csApplet()->messageBox(tr("Keys generated. Please unlock Password Safe again."));
1425         } catch (CommandFailedException &e){
1426           if (e.reason_wrong_password())
1427             csApplet()->warningBox(tr("Wrong admin password."));
1428           else {
1429             csApplet()->warningBox(tr("Can't unlock password safe."));
1430           }
1431         }
1432       } else {
1433         //otherwise
1434         csApplet()->warningBox(tr("Can't unlock password safe."));
1435       }
1436     }
1437   } while (true);
1438 }
1439 
PWS_ExceClickedSlot(int Slot)1440 void MainWindow::PWS_ExceClickedSlot(int Slot) {
1441   try {
1442     auto slot_password = nm::instance()->get_password_safe_slot_password((uint8_t) Slot);
1443     clipboard.copyPWS(slot_password);
1444     clear_free_cstr(const_cast<char*>(slot_password));
1445     QString password_safe_slot_info =
1446         QString(tr("Password safe [%1]").arg(QString::fromStdString(libada::i()->getPWSSlotName(Slot))));
1447     QString title = QString("Password has been copied to clipboard");
1448     tray.showTrayMessage(title, password_safe_slot_info);
1449   }
1450   catch(DeviceCommunicationException &e){
1451     tray.showTrayMessage(tr(Communication_error_message));
1452   }
1453 }
1454 
1455 #include "GUI/Authentication.h"
getNextCode(uint8_t slotNumber)1456 std::string MainWindow::getNextCode(uint8_t slotNumber) {
1457     const auto status = nm::instance()->get_status();
1458     QByteArray tempPassword;
1459 
1460     if(status.enable_user_password){
1461         if(!auth_user.authenticate()){
1462           csApplet()->messageBox(tr("User not authenticated"));
1463           return "";
1464         }
1465         tempPassword = auth_user.getTempPassword();
1466     }
1467   bool isTOTP = slotNumber >= 0x20;
1468   auto temp_password_byte_array = tempPassword;
1469   if (isTOTP){
1470     //run only once before first TOTP request
1471     static bool time_synchronized = libada::i()->is_time_synchronized();
1472     if (!time_synchronized) {
1473        bool user_wants_time_reset =
1474            csApplet()->detailedYesOrNoBox(tr("Time is out-of-sync") + " - " + tr(Reset_nitrokeys_time),
1475                                           tr(Warning_devices_clock_not_desynchronized), false);
1476       if (user_wants_time_reset){
1477           if(libada::i()->set_current_time()){
1478             tray.showTrayMessage(tr("Time reset!"));
1479             time_synchronized = true;
1480           }
1481       }
1482     }
1483     return libada::i()->getTOTPCode(slotNumber - 0x20, (const char *) temp_password_byte_array.constData());
1484   } else {
1485     return libada::i()->getHOTPCode(slotNumber - 0x10, (const char *) temp_password_byte_array.constData());
1486   }
1487   return 0;
1488 }
1489 
1490 
1491 #define PWS_RANDOM_PASSWORD_CHAR_SPACE                                                             \
1492   "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"$%&/"                          \
1493   "()=?[]{}~*+#_'-`,.;:><^|@\\"
1494 
on_PWS_ButtonCreatePW_clicked()1495 void MainWindow::on_PWS_ButtonCreatePW_clicked() {
1496   //FIXME generate in separate class
1497   quint32 n;
1498   const QString PasswordCharSpace = PWS_RANDOM_PASSWORD_CHAR_SPACE;
1499   QString generated_password(20, 0);
1500 
1501   for (int i = 0; i < PWS_CreatePWSize; i++) {
1502     n = get_random();
1503     n = n % PasswordCharSpace.length();
1504     generated_password[i] = PasswordCharSpace[n];
1505   }
1506   ui->PWS_EditPassword->setText(generated_password.toLocal8Bit());
1507 }
1508 
on_PWS_ButtonEnable_clicked()1509 void MainWindow::on_PWS_ButtonEnable_clicked() { PWS_Clicked_EnablePWSAccess(); }
1510 
on_counterEdit_editingFinished()1511 void MainWindow::on_counterEdit_editingFinished() {
1512   bool conversionSuccess = false;
1513   ui->counterEdit->text().toLatin1().toULongLong(&conversionSuccess);
1514   if (!libada::i()->isStorageDeviceConnected()) {
1515     quint64 counterMaxValue = ULLONG_MAX;
1516     if (!conversionSuccess) {
1517       ui->counterEdit->setText(QString("%1").arg(0));
1518         csApplet()->warningBox(tr("Counter must be a value between 0 and %1").arg(counterMaxValue));
1519     }
1520   } else { // for nitrokey storage
1521     if (!conversionSuccess || ui->counterEdit->text().toLatin1().length() > 7) {
1522       ui->counterEdit->setText(QString("%1").arg(0));
1523         csApplet()->warningBox(tr("For Nitrokey Storage counter must be a value between 0 and 9999999"));
1524     }
1525   }
1526 }
1527 
factoryResetAction()1528 int MainWindow::factoryResetAction() {
1529   while (true){
1530     const std::string &password = auth_admin.getPassword();
1531     if (password.empty())
1532       return 0;
1533     try{
1534       nm::instance()->factory_reset(password.c_str());
1535     }
1536     catch (InvalidCRCReceived &e) {
1537       //FIXME WORKAROUND to remove on later firmwares
1538       //We are expecting this exception due to bug in Storage stick firmware, v0.45, with CRC set to "0" in response
1539     }
1540     catch (CommandFailedException &e){
1541       if(!e.reason_wrong_password())
1542         throw;
1543       csApplet()->messageBox(tr("Wrong Pin. Please try again."));
1544       continue;
1545     }
1546     csApplet()->messageBox(tr("Factory reset was successful."));
1547     emit FactoryReset();
1548     return 1;
1549   }
1550   return 0;
1551 }
1552 
on_radioButton_2_toggled(bool checked)1553 void MainWindow::on_radioButton_2_toggled(bool checked) {
1554   if (checked)
1555     ui->slotComboBox->setCurrentIndex(0);
1556 }
1557 
on_radioButton_toggled(bool checked)1558 void MainWindow::on_radioButton_toggled(bool checked) {
1559   if (checked)
1560     ui->slotComboBox->setCurrentIndex(TOTP_SlotCount + 1);
1561 }
1562 
setCounter(int size,const QString & arg1,QLabel * counter)1563 void setCounter(int size, const QString &arg1, QLabel *counter) {
1564   int chars_left = size - arg1.toUtf8().size();
1565   QString t = QString::number(chars_left);
1566   counter->setText(t);
1567 }
1568 
on_PWS_EditSlotName_textChanged(const QString & arg1)1569 void MainWindow::on_PWS_EditSlotName_textChanged(const QString &arg1) {
1570   setCounter(PWS_SLOTNAME_LENGTH, arg1, ui->l_c_name);
1571 }
1572 
on_PWS_EditLoginName_textChanged(const QString & arg1)1573 void MainWindow::on_PWS_EditLoginName_textChanged(const QString &arg1) {
1574   setCounter(PWS_LOGINNAME_LENGTH, arg1, ui->l_c_login);
1575 }
1576 
on_PWS_EditPassword_textChanged(const QString & arg1)1577 void MainWindow::on_PWS_EditPassword_textChanged(const QString &arg1) {
1578   setCounter(PWS_PASSWORD_LENGTH, arg1, ui->l_c_password);
1579 }
1580 
on_enableUserPasswordCheckBox_clicked(bool checked)1581 void MainWindow::on_enableUserPasswordCheckBox_clicked(bool checked) {
1582   if (checked && libada::i()->is_nkpro_07_rtm1()) {
1583     bool answer = csApplet()->yesOrNoBox(tr("To handle this functionality "
1584                                                     "application will keep your user PIN in memory. "
1585                                                     "Do you want to continue?"), false);
1586     ui->enableUserPasswordCheckBox->setChecked(answer);
1587     //TODO save choice in APP's configuration
1588   }
1589 }
1590 
startLockDeviceAction(bool ask_for_confirmation)1591 void MainWindow::startLockDeviceAction(bool ask_for_confirmation) {
1592   if(libada::i()->isStorageDeviceConnected()){
1593     PWS_Access = false;
1594     storage.startLockDeviceAction(ask_for_confirmation);
1595     return;
1596   }
1597   emit ShortOperationBegins(tr("Locking device"));
1598   PWS_Access = false;
1599   nm::instance()->lock_device();
1600   tray.showTrayMessage("Nitrokey App", tr("Device has been locked"), INFORMATION, TRAY_MSG_TIMEOUT);
1601   emit DeviceLocked();
1602   emit ShortOperationEnds();
1603 }
1604 
updateProgressBar(int i)1605 void MainWindow::updateProgressBar(int i) {
1606   ui->progressBar->setValue(i);
1607   if(i == 100){
1608     QTimer::singleShot(1000, [&](){
1609       ui->progressBar->hide();
1610     });
1611   }
1612 }
1613 
on_DeviceDisconnected()1614 void MainWindow::on_DeviceDisconnected() {
1615   if (debug_mode)
1616     qDebug("on_DeviceDisconnected");
1617 
1618   emit ShortOperationEnds();
1619 
1620   QSettings settings;
1621   if(settings.value("main/connection_message", true).toBool()){
1622     tray.showTrayMessage(tr("Nitrokey disconnected"));
1623   }
1624   ui->statusBar->showMessage(tr("Nitrokey disconnected"));
1625 
1626   if(this->isVisible() && settings.value("main/close_main_on_connection", false).toBool()){
1627     this->hide();
1628   }
1629 
1630   make_UI_enabled(false);
1631 }
1632 
on_DeviceConnected()1633 void MainWindow::on_DeviceConnected() {
1634   if (debug_mode)
1635     qDebug("on_DeviceConnected");
1636 
1637   if (debug_mode)
1638     emit ShortOperationBegins(tr("Connecting device"));
1639 
1640   ui->statusBar->showMessage(tr("Device connected. Waiting for initialization..."));
1641 
1642   auto result = QtConcurrent::run(libada::i().get(), &libada::get_status_no_except);
1643   result.waitForFinished();
1644 
1645   if (result.result() == 2){
1646     long_operation_in_progress = true;
1647   }
1648   if (result.result() !=0){
1649       return;
1650   }
1651 
1652   initialTimeReset();
1653 
1654   new ThreadWorker(
1655     []() -> Data {
1656       Data data;
1657       data["error"] = false;
1658       try{
1659         auto storageDeviceConnected = libada::i()->isStorageDeviceConnected();
1660         data["storage_connected"] = storageDeviceConnected;
1661         if (storageDeviceConnected){
1662           auto s = nm::instance()->get_status_storage();
1663           data["initiated"] = !s.StickKeysNotInitiated;
1664           data["initiated_ask"] = !false; //FIXME select proper variable s.NewSDCardFound_u8
1665           data["erased"] = !s.NewSDCardFound_u8;
1666           data["erased_ask"] = !s.SDFillWithRandomChars_u8;
1667           data["old_firmware"] = s.versionInfo.major == 0 && s.versionInfo.minor <= 49;
1668         }
1669         data["PWS_Access"] = libada::i()->isPasswordSafeUnlocked();
1670       }
1671       catch(DeviceCommunicationException &e){
1672         data["error"] = true;
1673       }
1674       return data;
1675     },
1676     [this](Data data) {
1677       emit ShortOperationEnds();
1678       PWS_Access = data["PWS_Access"].toBool();
1679       if(data["error"].toBool()) return;
1680       if(!data["storage_connected"].toBool()) return;
1681 
1682       if (!data["initiated"].toBool()) {
1683         if (data["initiated_ask"].toBool()){
1684           csApplet()->warningBox(tr(Warning_ev_not_secure_initialize) + " " + "\n" + tr(Tray_location_msg));
1685           ui->statusBar->showMessage(tr(Warning_ev_not_secure_initialize));
1686           make_UI_enabled(false);
1687         }
1688       }
1689       if (data["initiated"].toBool() && !data["erased"].toBool()) {
1690         if (data["erased_ask"].toBool())
1691           csApplet()->warningBox(tr("Warning: Encrypted volume is not secure,\nSelect \"Initialize "
1692                                         "storage with random data\"") + ". " + "\n" + tr(Tray_location_msg));
1693       }
1694 
1695 #if defined(Q_OS_MAC) || defined(Q_OS_DARWIN)
1696       if(data["old_firmware"].toBool()){
1697         csApplet()->warningBox(tr(
1698                                   "WARNING: This Storage firmware version is old. Application may be unresponsive and unlocking encrypted volume may not work. Please update the firmware to the latest version."
1699                                    " "
1700                                    "Guide should be available at: <br/><a href='https://www.nitrokey.com/en/doc/firmware-update-storage'>www.nitrokey.com/en/doc/firmware-update-storage</a>."
1701                                   ));
1702       }
1703 #endif
1704       }, this);
1705 
1706   QSettings settings;
1707   if (settings.value("main/show_main_on_connection", true).toBool()){
1708     startConfiguration(false);
1709   }
1710 
1711   auto connected_device_model = libada::i()->isStorageDeviceConnected() ?
1712                                   tr("Nitrokey Storage connected") :
1713                                   tr("Nitrokey Pro connected");
1714   if(settings.value("main/connection_message", true).toBool()){
1715     tray.showTrayMessage(tr("Nitrokey connected"), connected_device_model);
1716   }
1717   ui->statusBar->showMessage(connected_device_model);
1718 }
1719 
on_KeepDeviceOnline()1720 void MainWindow::on_KeepDeviceOnline() {
1721 
1722   if (!check_connection_mutex.tryLock(100)){
1723     if (debug_mode)
1724       qDebug("on_KeepDeviceOnline skip");
1725       return;
1726   }
1727   ScopedGuard mutexGuard([this](){
1728       check_connection_mutex.unlock();
1729   });
1730 
1731   try{
1732     nm::instance()->get_status();
1733     //if long operation in progress jump to catch,
1734     // clear the flag otherwise
1735     if (long_operation_in_progress) {
1736       long_operation_in_progress = false;
1737       keepDeviceOnlineTimer->setInterval(30*1000);
1738       emit OperationInProgress(100);
1739       startLockDeviceAction(false);
1740     }
1741   }
1742   catch (DeviceCommunicationException &e){
1743     if(connectionState != ConnectionState::disconnected){
1744       emit DeviceDisconnected();
1745     }
1746   }
1747   catch (LongOperationInProgressException &e){
1748     if(!long_operation_in_progress){
1749       long_operation_in_progress = true;
1750       keepDeviceOnlineTimer->setInterval(10*1000);
1751     }
1752     emit OperationInProgress(e.progress_bar_value);
1753   }
1754 }
1755 
show_progress_window()1756 void MainWindow::show_progress_window() {
1757   progress_window->show();
1758   progress_window->setFocus();
1759   progress_window->raise();
1760   QApplication::setActiveWindow(progress_window.get());
1761 }
1762 
set_commands_delay(int delay_in_ms)1763 void MainWindow::set_commands_delay(int delay_in_ms) {
1764   nm::instance()->set_default_commands_delay(delay_in_ms);
1765 }
1766 
enable_admin_commands()1767 void MainWindow::enable_admin_commands() {
1768   tray.setAdmin_mode(true);
1769 }
1770 
set_debug_file(QString log_file_name)1771 void MainWindow::set_debug_file(QString log_file_name) {
1772   nm::instance()->set_log_function( [log_file_name, debug_mode=this->debug_mode](std::string data){
1773       static std::shared_ptr<QFile> log_file;
1774       if(!log_file){
1775         log_file = std::make_shared<QFile>(log_file_name);
1776         if (!log_file->open(QIODevice::WriteOnly | QIODevice::Text)){
1777           if (debug_mode)
1778             qDebug() << "Could not open " << log_file_name;
1779           log_file = nullptr;
1780         }
1781       }
1782       if(log_file) {
1783         log_file->write(data.c_str());
1784         log_file->flush();
1785       }
1786     }
1787   );
1788   LOGD(QSysInfo::prettyProductName().toStdString());
1789 }
1790 
set_debug_window()1791 void MainWindow::set_debug_window() {
1792   tray.setDebug_mode(true);
1793   nm::instance()->set_log_function( [this](std::string data) {
1794       emit DebugData(QString::fromStdString(data));
1795   });
1796   LOGD(QSysInfo::prettyProductName().toStdString());
1797 }
1798 
set_debug_mode()1799 void MainWindow::set_debug_mode() {
1800   tray.setDebug_mode(true);
1801   debug_mode = true;
1802 }
1803 
set_debug_level(int debug_level)1804 void MainWindow::set_debug_level(int debug_level) {
1805   nm::instance()->set_loglevel(debug_level);
1806 }
1807 
on_btn_writeSettings_clicked()1808 void MainWindow::on_btn_writeSettings_clicked()
1809 {
1810     QSettings settings;
1811 
1812     // see if restart is required
1813     bool restart_required = false;
1814     if (settings.value("main/language").toString() != ui->combo_languages->currentData().toString()
1815             || settings.value("debug/enabled").toBool() != ui->cb_debug_enabled->isChecked()
1816             || settings.value("debug/file").toString() != ui->edit_debug_file_path->text()
1817             ){
1818         restart_required = true;
1819     }
1820 
1821     // save the settings
1822     settings.setValue("main/first_run", ui->cb_first_run_message->isChecked());
1823     settings.setValue("main/language", ui->combo_languages->currentData());
1824     settings.setValue("debug/file", ui->edit_debug_file_path->text());
1825     settings.setValue("debug/level", ui->spin_debug_verbosity->text().toInt());
1826     settings.setValue("debug/enabled", ui->cb_debug_enabled->isChecked());
1827     settings.setValue("clipboard/PWS_time", ui->spin_PWS_time->value());
1828     settings.setValue("clipboard/OTP_time", ui->spin_OTP_time->value());
1829     settings.setValue("main/connection_message", ui->cb_device_connection_message->isChecked());
1830     settings.setValue("main/show_main_on_connection", ui->cb_show_main_window_on_connection->isChecked());
1831     settings.setValue("main/close_main_on_connection", ui->cb_hide_main_window_on_connection->isChecked());
1832     settings.setValue("main/hide_on_close", ui->cb_hide_main_window_on_close->isChecked());
1833     settings.setValue("main/show_on_start", ui->cb_show_window_on_start->isChecked());
1834 
1835     settings.setValue("storage/check_symlink", ui->cb_check_symlink->isChecked());
1836 
1837     // inform user and quit if asked
1838     if (!restart_required){
1839         csApplet()->messageBox(tr("Configuration successfully written."));
1840     } else {
1841         auto user_wants_quit = csApplet()->yesOrNoBox(
1842                 tr("Configuration successfully written.") + " "+
1843                 tr("Please run the application again to apply new settings.") + " "+
1844                 tr("Would you like to quit now?"), true);
1845         if (user_wants_quit)
1846         {
1847             QApplication::exit();
1848         }
1849     }
1850   load_settings_page();
1851 }
1852 
on_btn_select_debug_file_path_clicked()1853 void MainWindow::on_btn_select_debug_file_path_clicked()
1854 {
1855     auto filename = QFileDialog::getSaveFileName(this, tr("Debug file location (will be overwritten)"));
1856     ui->edit_debug_file_path->setText(filename);
1857 }
1858 
on_PWS_Lock_clicked()1859 void MainWindow::on_PWS_Lock_clicked()
1860 {
1861   startLockDeviceAction(true);
1862 }
1863 
on_btn_copyToClipboard_clicked()1864 void MainWindow::on_btn_copyToClipboard_clicked()
1865 {
1866     clipboard.copyOTP(ui->secretEdit->text());
1867     showNotificationLabel();
1868 }
1869 
ready()1870 void MainWindow::ready() {
1871 }
1872 
on_btn_select_debug_console_clicked()1873 void MainWindow::on_btn_select_debug_console_clicked()
1874 {
1875     ui->edit_debug_file_path->setText("console");
1876 }
1877