3
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
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.
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.
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/>.
19
#include "KdbxReader.h"
20
#include "core/Database.h"
21
#include "core/Endian.h"
22
#include "crypto/SymmetricCipher.h"
23
#include "streams/StoreDataStream.h"
28
* Read KDBX magic header numbers from a device.
30
* Passing a null key will only read in the unprotected headers.
32
* @param device input device
33
* @param sig1 KDBX signature 1
34
* @param sig2 KDBX signature 2
35
* @param version KDBX version
36
* @return true if magic numbers were read successfully
38
bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version)
41
sig1 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
46
sig2 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
51
version = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
57
* Read KDBX stream from device.
58
* The device will automatically be reset to 0 before reading.
60
* Passing a null key will only read in the unprotected headers.
62
* @param device input device
63
* @param key database encryption composite key
64
* @param db database to read into
65
* @return true on success
67
bool KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db)
73
m_encryptionIV.clear();
74
m_streamStartBytes.clear();
75
m_protectedStreamKey.clear();
77
StoreDataStream headerStream(device);
78
headerStream.open(QIODevice::ReadOnly);
80
// read KDBX magic numbers
81
quint32 sig1, sig2, version;
82
if (!readMagicNumbers(&headerStream, sig1, sig2, version)) {
85
m_kdbxSignature = qMakePair(sig1, sig2);
86
m_db->setFormatVersion(version);
89
while (readHeaderField(headerStream, m_db) && !hasError()) {
98
// No key provided - don't proceed to load payload
104
return readDatabaseImpl(device, headerStream.storedData(), std::move(key), db);
107
bool KdbxReader::hasError() const
112
QString KdbxReader::errorString() const
117
KeePass2::ProtectedStreamAlgo KdbxReader::protectedStreamAlgo() const
123
* @param data stream cipher UUID as bytes
125
void KdbxReader::setCipher(const QByteArray& data)
127
if (data.size() != UUID_LENGTH) {
128
raiseError(tr("Invalid cipher uuid length: %1 (length=%2)").arg(QString(data)).arg(data.size()));
132
QUuid uuid = QUuid::fromRfc4122(data);
134
raiseError(tr("Unable to parse UUID: %1").arg(QString(data)));
138
if (SymmetricCipher::cipherUuidToMode(uuid) == SymmetricCipher::InvalidMode) {
139
raiseError(tr("Unsupported cipher"));
142
m_db->setCipher(uuid);
146
* @param data compression flags as bytes
148
void KdbxReader::setCompressionFlags(const QByteArray& data)
150
if (data.size() != 4) {
151
raiseError(tr("Invalid compression flags length"));
154
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
156
if (id > Database::CompressionAlgorithmMax) {
157
raiseError(tr("Unsupported compression algorithm"));
160
m_db->setCompressionAlgorithm(static_cast<Database::CompressionAlgorithm>(id));
164
* @param data master seed as bytes
166
void KdbxReader::setMasterSeed(const QByteArray& data)
168
if (data.size() != 32) {
169
raiseError(tr("Invalid master seed size"));
176
* @param data KDF seed as bytes
178
void KdbxReader::setTransformSeed(const QByteArray& data)
180
if (data.size() != 32) {
181
raiseError(tr("Invalid transform seed size"));
185
auto kdf = m_db->kdf();
192
* @param data KDF transform rounds as bytes
194
void KdbxReader::setTransformRounds(const QByteArray& data)
196
if (data.size() != 8) {
197
raiseError(tr("Invalid transform rounds size"));
201
auto rounds = Endian::bytesToSizedInt<quint64>(data, KeePass2::BYTEORDER);
202
auto kdf = m_db->kdf();
204
kdf->setRounds(static_cast<int>(rounds));
209
* @param data cipher stream IV as bytes
211
void KdbxReader::setEncryptionIV(const QByteArray& data)
213
m_encryptionIV = data;
217
* @param data key for random (inner) stream as bytes
219
void KdbxReader::setProtectedStreamKey(const QByteArray& data)
221
m_protectedStreamKey = data;
225
* @param data start bytes for cipher stream
227
void KdbxReader::setStreamStartBytes(const QByteArray& data)
229
if (data.size() != 32) {
230
raiseError(tr("Invalid start bytes size"));
233
m_streamStartBytes = data;
237
* @param data id of inner cipher stream algorithm
239
void KdbxReader::setInnerRandomStreamID(const QByteArray& data)
241
if (data.size() != 4) {
242
raiseError(tr("Invalid random stream id size"));
245
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
246
KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
247
if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo
248
|| irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) {
249
raiseError(tr("Invalid inner random stream cipher"));
256
* Raise an error. Use in case of an unexpected read error.
258
* @param errorMessage error message
260
void KdbxReader::raiseError(const QString& errorMessage)
263
m_errorStr = errorMessage;