2
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
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.
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.
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/>.
18
#include "DatabaseEdit.h"
21
#include "cli/DatabaseCreate.h"
22
#include "keys/ChallengeResponseKey.h"
23
#include "keys/FileKey.h"
24
#include "keys/PasswordKey.h"
26
#include <QCommandLineParser>
29
const QCommandLineOption DatabaseEdit::UnsetPasswordOption =
30
QCommandLineOption(QStringList() << "unset-password", QObject::tr("Unset the password for the database."));
31
const QCommandLineOption DatabaseEdit::UnsetKeyFileOption =
32
QCommandLineOption(QStringList() << "unset-key-file", QObject::tr("Unset the key file for the database."));
34
DatabaseEdit::DatabaseEdit()
36
name = QString("db-edit");
37
description = QObject::tr("Edit a database.");
38
options.append(DatabaseCreate::SetKeyFileOption);
39
options.append(DatabaseCreate::SetPasswordOption);
40
options.append(DatabaseEdit::UnsetKeyFileOption);
41
options.append(DatabaseEdit::UnsetPasswordOption);
44
int DatabaseEdit::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
46
auto& out = Utils::STDOUT;
47
auto& err = Utils::STDERR;
49
const QStringList args = parser->positionalArguments();
50
bool databaseWasChanged = false;
52
if (parser->isSet(DatabaseCreate::SetPasswordOption) && parser->isSet(DatabaseEdit::UnsetPasswordOption)) {
53
err << QObject::tr("Cannot use %1 and %2 at the same time.")
54
.arg(DatabaseCreate::SetPasswordOption.names().at(0))
55
.arg(DatabaseEdit::UnsetPasswordOption.names().at(0))
60
if (parser->isSet(DatabaseCreate::SetKeyFileOption) && parser->isSet(DatabaseEdit::UnsetKeyFileOption)) {
61
err << QObject::tr("Cannot use %1 and %2 at the same time.")
62
.arg(DatabaseCreate::SetKeyFileOption.names().at(0))
63
.arg(DatabaseEdit::UnsetKeyFileOption.names().at(0))
69
(parser->isSet(DatabaseCreate::SetPasswordOption) || parser->isSet(DatabaseCreate::SetKeyFileOption)
70
|| parser->isSet(DatabaseEdit::UnsetPasswordOption) || parser->isSet(DatabaseEdit::UnsetKeyFileOption));
73
auto newDatabaseKey = getNewDatabaseKey(database,
74
parser->isSet(DatabaseCreate::SetPasswordOption),
75
parser->isSet(DatabaseEdit::UnsetPasswordOption),
76
parser->value(DatabaseCreate::SetKeyFileOption),
77
parser->isSet(DatabaseEdit::UnsetKeyFileOption));
78
if (newDatabaseKey.isNull()) {
79
err << QObject::tr("Could not change the database key.") << endl;
82
database->setKey(newDatabaseKey);
83
databaseWasChanged = true;
86
if (!databaseWasChanged) {
87
out << QObject::tr("Database was not modified.") << endl;
92
if (!database->save(Database::Atomic, {}, &errorMessage)) {
93
err << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl;
97
out << QObject::tr("Successfully edited the database.") << endl;
101
QSharedPointer<CompositeKey> DatabaseEdit::getNewDatabaseKey(QSharedPointer<Database> database,
104
QString newFileKeyPath,
107
auto& err = Utils::STDERR;
108
auto newDatabaseKey = QSharedPointer<CompositeKey>::create();
109
bool updateKeyFile = !newFileKeyPath.isEmpty();
111
auto currentPasswordKey = database->key()->getKey(PasswordKey::UUID);
112
auto currentFileKey = database->key()->getKey(FileKey::UUID);
113
auto currentChallengeResponseKey = database->key()->getChallengeResponseKey(ChallengeResponseKey::UUID);
115
if (removePassword && currentPasswordKey.isNull()) {
116
err << QObject::tr("Cannot remove password: The database does not have a password.") << endl;
120
if (removeKeyFile && currentFileKey.isNull()) {
121
err << QObject::tr("Cannot remove file key: The database does not have a file key.") << endl;
125
if (updatePassword) {
126
QSharedPointer<PasswordKey> newPasswordKey = Utils::getConfirmedPassword();
127
if (newPasswordKey.isNull()) {
128
err << QObject::tr("Failed to set database password.") << endl;
131
newDatabaseKey->addKey(newPasswordKey);
132
} else if (!removePassword && !currentPasswordKey.isNull()) {
133
newDatabaseKey->addKey(currentPasswordKey);
137
QSharedPointer<FileKey> newFileKey = QSharedPointer<FileKey>::create();
138
QString errorMessage;
139
if (!Utils::loadFileKey(newFileKeyPath, newFileKey)) {
140
err << QObject::tr("Loading the new key file failed: %1").arg(errorMessage) << endl;
143
newDatabaseKey->addKey(newFileKey);
144
} else if (!removeKeyFile && !currentFileKey.isNull()) {
145
newDatabaseKey->addKey(currentFileKey);
148
// This is a sanity check to make sure that this function is not used if
149
// new key types are introduced. Otherwise, those key types would be
150
// silently removed from the database.
151
for (const QSharedPointer<Key>& key : database->key()->keys()) {
152
if (key->uuid() != PasswordKey::UUID && key->uuid() != FileKey::UUID) {
153
err << QObject::tr("Found unexpected Key type %1").arg(key->uuid().toString()) << endl;
157
for (const QSharedPointer<ChallengeResponseKey>& key : database->key()->challengeResponseKeys()) {
158
if (key->uuid() != ChallengeResponseKey::UUID) {
159
err << QObject::tr("Found unexpected Key type %1").arg(key->uuid().toString()) << endl;
164
if (!currentChallengeResponseKey.isNull()) {
165
newDatabaseKey->addChallengeResponseKey(currentChallengeResponseKey);
168
if (newDatabaseKey->keys().isEmpty() && newDatabaseKey->challengeResponseKeys().isEmpty()) {
169
err << QObject::tr("Cannot remove all the keys from a database.") << endl;
173
return newDatabaseKey;