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