1 #include <utility>
2
3 /*
4 * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 or (at your option)
9 * version 3 of the License.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "KdbxReader.h"
21 #include "core/Database.h"
22 #include "core/Endian.h"
23
24 #include <QBuffer>
25
26 #define UUID_LENGTH 16
27
28 /**
29 * Read KDBX magic header numbers from a device.
30 *
31 * @param device input device
32 * @param sig1 KDBX signature 1
33 * @param sig2 KDBX signature 2
34 * @param version KDBX version
35 * @return true if magic numbers were read successfully
36 */
readMagicNumbers(QIODevice * device,quint32 & sig1,quint32 & sig2,quint32 & version)37 bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version)
38 {
39 bool ok;
40 sig1 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
41 if (!ok) {
42 return false;
43 }
44
45 sig2 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
46 if (!ok) {
47 return false;
48 }
49
50 version = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
51
52 return ok;
53 }
54
55 /**
56 * Read KDBX stream from device.
57 * The device will automatically be reset to 0 before reading.
58 *
59 * @param device input device
60 * @param key database encryption composite key
61 * @param db database to read into
62 * @return true on success
63 */
readDatabase(QIODevice * device,QSharedPointer<const CompositeKey> key,Database * db)64 bool KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db)
65 {
66 device->seek(0);
67
68 m_db = db;
69 m_masterSeed.clear();
70 m_encryptionIV.clear();
71 m_streamStartBytes.clear();
72 m_protectedStreamKey.clear();
73
74 StoreDataStream headerStream(device);
75 headerStream.open(QIODevice::ReadOnly);
76
77 // read KDBX magic numbers
78 quint32 sig1, sig2;
79 if (!readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion)) {
80 return false;
81 }
82 m_kdbxSignature = qMakePair(sig1, sig2);
83
84 // mask out minor version
85 m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
86
87 // read header fields
88 while (readHeaderField(headerStream, m_db) && !hasError()) {
89 }
90
91 headerStream.close();
92
93 if (hasError()) {
94 return false;
95 }
96
97 // read payload
98 return readDatabaseImpl(device, headerStream.storedData(), std::move(key), db);
99 }
100
hasError() const101 bool KdbxReader::hasError() const
102 {
103 return m_error;
104 }
105
errorString() const106 QString KdbxReader::errorString() const
107 {
108 return m_errorStr;
109 }
110
protectedStreamAlgo() const111 KeePass2::ProtectedStreamAlgo KdbxReader::protectedStreamAlgo() const
112 {
113 return m_irsAlgo;
114 }
115
116 /**
117 * @param data stream cipher UUID as bytes
118 */
setCipher(const QByteArray & data)119 void KdbxReader::setCipher(const QByteArray& data)
120 {
121 if (data.size() != UUID_LENGTH) {
122 raiseError(tr("Invalid cipher uuid length: %1 (length=%2)").arg(QString(data)).arg(data.size()));
123 return;
124 }
125
126 QUuid uuid = QUuid::fromRfc4122(data);
127 if (uuid.isNull()) {
128 raiseError(tr("Unable to parse UUID: %1").arg(QString(data)));
129 return;
130 }
131
132 if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) {
133 raiseError(tr("Unsupported cipher"));
134 return;
135 }
136 m_db->setCipher(uuid);
137 }
138
139 /**
140 * @param data compression flags as bytes
141 */
setCompressionFlags(const QByteArray & data)142 void KdbxReader::setCompressionFlags(const QByteArray& data)
143 {
144 if (data.size() != 4) {
145 raiseError(tr("Invalid compression flags length"));
146 return;
147 }
148 auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
149
150 if (id > Database::CompressionAlgorithmMax) {
151 raiseError(tr("Unsupported compression algorithm"));
152 return;
153 }
154 m_db->setCompressionAlgorithm(static_cast<Database::CompressionAlgorithm>(id));
155 }
156
157 /**
158 * @param data master seed as bytes
159 */
setMasterSeed(const QByteArray & data)160 void KdbxReader::setMasterSeed(const QByteArray& data)
161 {
162 if (data.size() != 32) {
163 raiseError(tr("Invalid master seed size"));
164 return;
165 }
166 m_masterSeed = data;
167 }
168
169 /**
170 * @param data KDF seed as bytes
171 */
setTransformSeed(const QByteArray & data)172 void KdbxReader::setTransformSeed(const QByteArray& data)
173 {
174 if (data.size() != 32) {
175 raiseError(tr("Invalid transform seed size"));
176 return;
177 }
178
179 auto kdf = m_db->kdf();
180 if (!kdf.isNull()) {
181 kdf->setSeed(data);
182 }
183 }
184
185 /**
186 * @param data KDF transform rounds as bytes
187 */
setTransformRounds(const QByteArray & data)188 void KdbxReader::setTransformRounds(const QByteArray& data)
189 {
190 if (data.size() != 8) {
191 raiseError(tr("Invalid transform rounds size"));
192 return;
193 }
194
195 auto rounds = Endian::bytesToSizedInt<quint64>(data, KeePass2::BYTEORDER);
196 auto kdf = m_db->kdf();
197 if (!kdf.isNull()) {
198 kdf->setRounds(static_cast<int>(rounds));
199 }
200 }
201
202 /**
203 * @param data cipher stream IV as bytes
204 */
setEncryptionIV(const QByteArray & data)205 void KdbxReader::setEncryptionIV(const QByteArray& data)
206 {
207 m_encryptionIV = data;
208 }
209
210 /**
211 * @param data key for random (inner) stream as bytes
212 */
setProtectedStreamKey(const QByteArray & data)213 void KdbxReader::setProtectedStreamKey(const QByteArray& data)
214 {
215 m_protectedStreamKey = data;
216 }
217
218 /**
219 * @param data start bytes for cipher stream
220 */
setStreamStartBytes(const QByteArray & data)221 void KdbxReader::setStreamStartBytes(const QByteArray& data)
222 {
223 if (data.size() != 32) {
224 raiseError(tr("Invalid start bytes size"));
225 return;
226 }
227 m_streamStartBytes = data;
228 }
229
230 /**
231 * @param data id of inner cipher stream algorithm
232 */
setInnerRandomStreamID(const QByteArray & data)233 void KdbxReader::setInnerRandomStreamID(const QByteArray& data)
234 {
235 if (data.size() != 4) {
236 raiseError(tr("Invalid random stream id size"));
237 return;
238 }
239 auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
240 KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
241 if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo
242 || irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) {
243 raiseError(tr("Invalid inner random stream cipher"));
244 return;
245 }
246 m_irsAlgo = irsAlgo;
247 }
248
249 /**
250 * Raise an error. Use in case of an unexpected read error.
251 *
252 * @param errorMessage error message
253 */
raiseError(const QString & errorMessage)254 void KdbxReader::raiseError(const QString& errorMessage)
255 {
256 m_error = true;
257 m_errorStr = errorMessage;
258 }
259