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