1 /*
2  *  Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
3  *  Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 2 or (at your option)
8  *  version 3 of the License.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifndef KEEPASSX_DATABASE_H
20 #define KEEPASSX_DATABASE_H
21 
22 #include <QDateTime>
23 #include <QHash>
24 #include <QMutex>
25 #include <QPointer>
26 #include <QScopedPointer>
27 #include <QTimer>
28 
29 #include "config-keepassx.h"
30 #include "crypto/kdf/AesKdf.h"
31 #include "crypto/kdf/Kdf.h"
32 #include "format/KeePass2.h"
33 #include "keys/CompositeKey.h"
34 #include "keys/PasswordKey.h"
35 
36 class Entry;
37 enum class EntryReferenceType;
38 class FileWatcher;
39 class Group;
40 class Metadata;
41 class QIODevice;
42 
43 struct DeletedObject
44 {
45     QUuid uuid;
46     QDateTime deletionTime;
47     bool operator==(const DeletedObject& other) const
48     {
49         return uuid == other.uuid && deletionTime == other.deletionTime;
50     }
51 };
52 
53 Q_DECLARE_TYPEINFO(DeletedObject, Q_MOVABLE_TYPE);
54 
55 class Database : public QObject
56 {
57     Q_OBJECT
58 
59 public:
60     enum CompressionAlgorithm
61     {
62         CompressionNone = 0,
63         CompressionGZip = 1
64     };
65     static const quint32 CompressionAlgorithmMax = CompressionGZip;
66 
67     Database();
68     explicit Database(const QString& filePath);
69     ~Database() override;
70 
71     bool open(QSharedPointer<const CompositeKey> key, QString* error = nullptr, bool readOnly = false);
72     bool open(const QString& filePath,
73               QSharedPointer<const CompositeKey> key,
74               QString* error = nullptr,
75               bool readOnly = false);
76     bool save(QString* error = nullptr, bool atomic = true, bool backup = false);
77     bool saveAs(const QString& filePath, QString* error = nullptr, bool atomic = true, bool backup = false);
78     bool extract(QByteArray&, QString* error = nullptr);
79     bool import(const QString& xmlExportPath, QString* error = nullptr);
80 
81     void releaseData();
82 
83     bool isInitialized() const;
84     bool isModified() const;
85     bool hasNonDataChanges() const;
86     void setEmitModified(bool value);
87     bool isReadOnly() const;
88     void setReadOnly(bool readOnly);
89     bool isSaving();
90 
91     QUuid uuid() const;
92     QString filePath() const;
93     QString canonicalFilePath() const;
94     void setFilePath(const QString& filePath);
95 
96     Metadata* metadata();
97     const Metadata* metadata() const;
98     Group* rootGroup();
99     const Group* rootGroup() const;
100     void setRootGroup(Group* group);
101     QVariantMap& publicCustomData();
102     const QVariantMap& publicCustomData() const;
103     void setPublicCustomData(const QVariantMap& customData);
104 
105     void recycleGroup(Group* group);
106     void recycleEntry(Entry* entry);
107     void emptyRecycleBin();
108     QList<DeletedObject> deletedObjects();
109     const QList<DeletedObject>& deletedObjects() const;
110     void addDeletedObject(const DeletedObject& delObj);
111     void addDeletedObject(const QUuid& uuid);
112     bool containsDeletedObject(const QUuid& uuid) const;
113     bool containsDeletedObject(const DeletedObject& uuid) const;
114     void setDeletedObjects(const QList<DeletedObject>& delObjs);
115 
116     QList<QString> commonUsernames();
117 
118     QSharedPointer<const CompositeKey> key() const;
119     bool setKey(const QSharedPointer<const CompositeKey>& key,
120                 bool updateChangedTime = true,
121                 bool updateTransformSalt = false,
122                 bool transformKey = true);
123     QString keyError();
124     QByteArray challengeResponseKey() const;
125     bool challengeMasterSeed(const QByteArray& masterSeed);
126     const QUuid& cipher() const;
127     void setCipher(const QUuid& cipher);
128     Database::CompressionAlgorithm compressionAlgorithm() const;
129     void setCompressionAlgorithm(Database::CompressionAlgorithm algo);
130 
131     QSharedPointer<Kdf> kdf() const;
132     void setKdf(QSharedPointer<Kdf> kdf);
133     bool changeKdf(const QSharedPointer<Kdf>& kdf);
134     QByteArray transformedDatabaseKey() const;
135 
136     static Database* databaseByUuid(const QUuid& uuid);
137 
138 public slots:
139     void markAsModified();
140     void markAsClean();
141     void updateCommonUsernames(int topN = 10);
142     void markNonDataChange();
143 
144 signals:
145     void filePathChanged(const QString& oldPath, const QString& newPath);
146     void groupDataChanged(Group* group);
147     void groupAboutToAdd(Group* group, int index);
148     void groupAdded();
149     void groupAboutToRemove(Group* group);
150     void groupRemoved();
151     void groupAboutToMove(Group* group, Group* toGroup, int index);
152     void groupMoved();
153     void databaseOpened();
154     void databaseModified();
155     void databaseSaved();
156     void databaseDiscarded();
157     void databaseFileChanged();
158 
159 private:
160     struct DatabaseData
161     {
162         QString filePath;
163         bool isReadOnly = false;
164         QUuid cipher = KeePass2::CIPHER_AES256;
165         CompressionAlgorithm compressionAlgorithm = CompressionGZip;
166 
167         QScopedPointer<PasswordKey> masterSeed;
168         QScopedPointer<PasswordKey> transformedDatabaseKey;
169         QScopedPointer<PasswordKey> challengeResponseKey;
170 
171         QSharedPointer<const CompositeKey> key;
172         QSharedPointer<Kdf> kdf = QSharedPointer<AesKdf>::create(true);
173 
174         QVariantMap publicCustomData;
175 
DatabaseDataDatabaseData176         DatabaseData()
177             : masterSeed(new PasswordKey())
178             , transformedDatabaseKey(new PasswordKey())
179             , challengeResponseKey(new PasswordKey())
180         {
181             kdf->randomizeSeed();
182         }
183 
clearDatabaseData184         void clear()
185         {
186             filePath.clear();
187 
188             masterSeed.reset();
189             transformedDatabaseKey.reset();
190             challengeResponseKey.reset();
191 
192             key.reset();
193             kdf.reset();
194 
195             publicCustomData.clear();
196         }
197     };
198 
199     void createRecycleBin();
200 
201     bool writeDatabase(QIODevice* device, QString* error = nullptr);
202     bool backupDatabase(const QString& filePath);
203     bool restoreDatabase(const QString& filePath);
204     bool performSave(const QString& filePath, QString* error, bool atomic, bool backup);
205 
206     QPointer<Metadata> const m_metadata;
207     DatabaseData m_data;
208     QPointer<Group> m_rootGroup;
209     QList<DeletedObject> m_deletedObjects;
210     QTimer m_modifiedTimer;
211     QMutex m_saveMutex;
212     QPointer<FileWatcher> m_fileWatcher;
213     bool m_modified = false;
214     bool m_emitModified;
215     bool m_hasNonDataChange = false;
216     QString m_keyError;
217 
218     QList<QString> m_commonUsernames;
219 
220     QUuid m_uuid;
221     static QHash<QUuid, QPointer<Database>> s_uuidMap;
222 };
223 
224 #endif // KEEPASSX_DATABASE_H
225