keepassxc
291 строка · 10.7 Кб
1/*
2* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
3*
4* This program is free software: you can redistribute it and/or modify
5* it under the terms of the GNU General Public License as published by
6* the Free Software Foundation, either version 2 or (at your option)
7* version 3 of the License.
8*
9* This program is distributed in the hope that it will be useful,
10* but WITHOUT ANY WARRANTY; without even the implied warranty of
11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12* GNU General Public License for more details.
13*
14* You should have received a copy of the GNU General Public License
15* along with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "DatabaseSettingsWidgetDatabaseKey.h"
19
20#include "core/Config.h"
21#include "core/Database.h"
22#include "core/PasswordHealth.h"
23#include "gui/MessageBox.h"
24#include "gui/databasekey/KeyFileEditWidget.h"
25#include "gui/databasekey/PasswordEditWidget.h"
26#include "gui/databasekey/YubiKeyEditWidget.h"
27#include "keys/ChallengeResponseKey.h"
28#include "keys/FileKey.h"
29#include "keys/PasswordKey.h"
30#include "quickunlock/QuickUnlockInterface.h"
31
32#include <QLayout>
33#include <QPushButton>
34
35DatabaseSettingsWidgetDatabaseKey::DatabaseSettingsWidgetDatabaseKey(QWidget* parent)
36: DatabaseSettingsWidget(parent)
37, m_additionalKeyOptionsToggle(new QPushButton(tr("Add additional protection…"), this))
38, m_additionalKeyOptions(new QWidget(this))
39, m_passwordEditWidget(new PasswordEditWidget(this))
40, m_keyFileEditWidget(new KeyFileEditWidget(this))
41#ifdef WITH_XC_YUBIKEY
42, m_yubiKeyEditWidget(new YubiKeyEditWidget(this))
43#endif
44{
45auto* vbox = new QVBoxLayout(this);
46vbox->setSizeConstraint(QLayout::SetMinimumSize);
47vbox->setSpacing(20);
48
49// primary password option
50vbox->addWidget(m_passwordEditWidget);
51
52// additional key options
53m_additionalKeyOptionsToggle->setObjectName("additionalKeyOptionsToggle");
54vbox->addWidget(m_additionalKeyOptionsToggle);
55vbox->addWidget(m_additionalKeyOptions);
56vbox->setSizeConstraint(QLayout::SetMinimumSize);
57m_additionalKeyOptions->setLayout(new QVBoxLayout());
58m_additionalKeyOptions->layout()->setMargin(0);
59m_additionalKeyOptions->layout()->setSpacing(20);
60m_additionalKeyOptions->layout()->addWidget(m_keyFileEditWidget);
61#ifdef WITH_XC_YUBIKEY
62m_additionalKeyOptions->layout()->addWidget(m_yubiKeyEditWidget);
63#endif
64m_additionalKeyOptions->setVisible(false);
65
66connect(m_additionalKeyOptionsToggle, SIGNAL(clicked()), SLOT(showAdditionalKeyOptions()));
67
68vbox->addStretch();
69setLayout(vbox);
70}
71
72DatabaseSettingsWidgetDatabaseKey::~DatabaseSettingsWidgetDatabaseKey() = default;
73
74void DatabaseSettingsWidgetDatabaseKey::load(QSharedPointer<Database> db)
75{
76DatabaseSettingsWidget::load(db);
77
78if (!m_db->key() || m_db->key()->keys().isEmpty()) {
79// database has no key, we are about to add a new one
80m_passwordEditWidget->changeVisiblePage(KeyComponentWidget::Page::Edit);
81m_passwordEditWidget->setPasswordVisible(true);
82}
83
84bool hasAdditionalKeys = false;
85for (const auto& key : m_db->key()->keys()) {
86if (key->uuid() == PasswordKey::UUID) {
87m_passwordEditWidget->setComponentAdded(true);
88} else if (key->uuid() == FileKey::UUID) {
89m_keyFileEditWidget->setComponentAdded(true);
90hasAdditionalKeys = true;
91}
92}
93
94#ifdef WITH_XC_YUBIKEY
95for (const auto& key : m_db->key()->challengeResponseKeys()) {
96if (key->uuid() == ChallengeResponseKey::UUID) {
97m_yubiKeyEditWidget->setComponentAdded(true);
98hasAdditionalKeys = true;
99}
100}
101#endif
102
103setAdditionalKeyOptionsVisible(hasAdditionalKeys);
104
105connect(m_passwordEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
106connect(m_keyFileEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
107#ifdef WITH_XC_YUBIKEY
108connect(m_yubiKeyEditWidget->findChild<QPushButton*>("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
109#endif
110}
111
112void DatabaseSettingsWidgetDatabaseKey::initialize()
113{
114bool blocked = blockSignals(true);
115m_passwordEditWidget->setComponentAdded(false);
116m_keyFileEditWidget->setComponentAdded(false);
117#ifdef WITH_XC_YUBIKEY
118m_yubiKeyEditWidget->setComponentAdded(false);
119#endif
120blockSignals(blocked);
121}
122
123void DatabaseSettingsWidgetDatabaseKey::uninitialize()
124{
125}
126
127bool DatabaseSettingsWidgetDatabaseKey::save()
128{
129m_isDirty |= (m_passwordEditWidget->visiblePage() == KeyComponentWidget::Page::Edit);
130m_isDirty |= (m_keyFileEditWidget->visiblePage() == KeyComponentWidget::Page::Edit);
131#ifdef WITH_XC_YUBIKEY
132m_isDirty |= (m_yubiKeyEditWidget->visiblePage() == KeyComponentWidget::Page::Edit);
133#endif
134
135if (m_db->key() && !m_db->key()->keys().isEmpty() && !m_isDirty) {
136// key unchanged
137return true;
138}
139
140auto newKey = QSharedPointer<CompositeKey>::create();
141
142QSharedPointer<Key> oldPasswordKey;
143QSharedPointer<Key> oldFileKey;
144QSharedPointer<ChallengeResponseKey> oldChallengeResponse;
145
146for (const auto& key : m_db->key()->keys()) {
147if (key->uuid() == PasswordKey::UUID) {
148oldPasswordKey = key;
149} else if (key->uuid() == FileKey::UUID) {
150oldFileKey = key;
151}
152}
153
154for (const auto& key : m_db->key()->challengeResponseKeys()) {
155if (key->uuid() == ChallengeResponseKey::UUID) {
156oldChallengeResponse = key;
157}
158}
159
160// Show warning if database password has not been set
161if (m_passwordEditWidget->visiblePage() == KeyComponentWidget::Page::AddNew || m_passwordEditWidget->isEmpty()) {
162QScopedPointer<QMessageBox> msgBox(new QMessageBox(this));
163msgBox->setIcon(QMessageBox::Warning);
164msgBox->setWindowTitle(tr("No password set"));
165msgBox->setText(tr("WARNING! You have not set a password. Using a database without "
166"a password is strongly discouraged!\n\n"
167"Are you sure you want to continue without a password?"));
168auto btn = msgBox->addButton(tr("Continue without password"), QMessageBox::ButtonRole::AcceptRole);
169msgBox->addButton(QMessageBox::Cancel);
170msgBox->setDefaultButton(QMessageBox::Cancel);
171msgBox->layout()->setSizeConstraint(QLayout::SetMinimumSize);
172msgBox->exec();
173if (msgBox->clickedButton() != btn) {
174return false;
175}
176} else if (!addToCompositeKey(m_passwordEditWidget, newKey, oldPasswordKey)) {
177return false;
178}
179
180// Show warning if database password is weak
181if (!m_passwordEditWidget->isEmpty()
182&& m_passwordEditWidget->getPasswordQuality() < PasswordHealth::Quality::Good) {
183auto dialogResult = MessageBox::warning(this,
184tr("Weak password"),
185tr("This is a weak password! For better protection of your secrets, "
186"you should choose a stronger password."),
187MessageBox::ContinueWithWeakPass | MessageBox::Cancel,
188MessageBox::Cancel);
189
190if (dialogResult == MessageBox::Cancel) {
191return false;
192}
193}
194
195// If enforced in the config file, deny users from continuing with a weak password
196auto minQuality =
197static_cast<PasswordHealth::Quality>(config()->get(Config::Security_DatabasePasswordMinimumQuality).toInt());
198if (!m_passwordEditWidget->isEmpty() && m_passwordEditWidget->getPasswordQuality() < minQuality) {
199MessageBox::critical(this,
200tr("Weak password"),
201tr("You must enter a stronger password to protect your database."),
202MessageBox::Ok,
203MessageBox::Ok);
204return false;
205}
206
207if (!addToCompositeKey(m_keyFileEditWidget, newKey, oldFileKey)) {
208return false;
209}
210
211#ifdef WITH_XC_YUBIKEY
212if (!addToCompositeKey(m_yubiKeyEditWidget, newKey, oldChallengeResponse)) {
213return false;
214}
215#endif
216
217if (newKey->keys().isEmpty() && newKey->challengeResponseKeys().isEmpty()) {
218MessageBox::critical(this,
219tr("No encryption key added"),
220tr("You must add at least one encryption key to secure your database!"),
221MessageBox::Ok,
222MessageBox::Ok);
223return false;
224}
225
226m_db->setKey(newKey, true, false, false);
227
228getQuickUnlock()->reset(m_db->publicUuid());
229
230emit editFinished(true);
231if (m_isDirty) {
232m_db->markAsModified();
233}
234
235return true;
236}
237
238void DatabaseSettingsWidgetDatabaseKey::discard()
239{
240emit editFinished(false);
241}
242
243void DatabaseSettingsWidgetDatabaseKey::showAdditionalKeyOptions()
244{
245setAdditionalKeyOptionsVisible(true);
246}
247
248void DatabaseSettingsWidgetDatabaseKey::setAdditionalKeyOptionsVisible(bool show)
249{
250m_additionalKeyOptionsToggle->setVisible(!show);
251m_additionalKeyOptions->setVisible(show);
252}
253
254bool DatabaseSettingsWidgetDatabaseKey::addToCompositeKey(KeyComponentWidget* widget,
255QSharedPointer<CompositeKey>& newKey,
256QSharedPointer<Key>& oldKey)
257{
258if (widget->visiblePage() == KeyComponentWidget::Edit) {
259QString error = tr("Unknown error");
260if (!widget->validate(error) || !widget->addToCompositeKey(newKey)) {
261MessageBox::critical(this, tr("Failed to change database credentials"), error, MessageBox::Ok);
262return false;
263}
264} else if (widget->visiblePage() == KeyComponentWidget::LeaveOrRemove) {
265Q_ASSERT(oldKey);
266newKey->addKey(oldKey);
267}
268return true;
269}
270
271bool DatabaseSettingsWidgetDatabaseKey::addToCompositeKey(KeyComponentWidget* widget,
272QSharedPointer<CompositeKey>& newKey,
273QSharedPointer<ChallengeResponseKey>& oldKey)
274{
275if (widget->visiblePage() == KeyComponentWidget::Edit) {
276QString error = tr("Unknown error");
277if (!widget->validate(error) || !widget->addToCompositeKey(newKey)) {
278MessageBox::critical(this, tr("Failed to change database credentials"), error, MessageBox::Ok);
279return false;
280}
281} else if (widget->visiblePage() == KeyComponentWidget::LeaveOrRemove) {
282Q_ASSERT(oldKey);
283newKey->addChallengeResponseKey(oldKey);
284}
285return true;
286}
287
288void DatabaseSettingsWidgetDatabaseKey::markDirty()
289{
290m_isDirty = true;
291}
292