keepassxc

Форк
0
/
Kdbx3Writer.cpp 
163 строки · 6.2 Кб
1
/*
2
 *  Copyright (C) 2017 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
#include "Kdbx3Writer.h"
20

21
#include <QBuffer>
22

23
#include "crypto/CryptoHash.h"
24
#include "crypto/Random.h"
25
#include "format/KdbxXmlWriter.h"
26
#include "format/KeePass2RandomStream.h"
27
#include "streams/HashedBlockStream.h"
28
#include "streams/SymmetricCipherStream.h"
29
#include "streams/qtiocompressor.h"
30

31
bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
32
{
33
    m_error = false;
34
    m_errorStr.clear();
35

36
    auto mode = SymmetricCipher::cipherUuidToMode(db->cipher());
37
    int ivSize = SymmetricCipher::defaultIvSize(mode);
38
    if (ivSize < 0) {
39
        raiseError(tr("Invalid symmetric cipher IV size.", "IV = Initialization Vector for symmetric cipher"));
40
        return false;
41
    }
42

43
    QByteArray masterSeed = randomGen()->randomArray(32);
44
    QByteArray encryptionIV = randomGen()->randomArray(ivSize);
45
    QByteArray protectedStreamKey = randomGen()->randomArray(32);
46
    QByteArray startBytes = randomGen()->randomArray(32);
47
    QByteArray endOfHeader = "\r\n\r\n";
48

49
    if (!db->challengeMasterSeed(masterSeed)) {
50
        raiseError(tr("Unable to issue challenge-response: %1").arg(db->keyError()));
51
        return false;
52
    }
53

54
    if (!db->setKey(db->key(), false, true)) {
55
        raiseError(tr("Unable to calculate database key"));
56
        return false;
57
    }
58

59
    // generate transformed database key
60
    CryptoHash hash(CryptoHash::Sha256);
61
    hash.addData(masterSeed);
62
    hash.addData(db->challengeResponseKey());
63
    Q_ASSERT(!db->transformedDatabaseKey().isEmpty());
64
    hash.addData(db->transformedDatabaseKey());
65
    QByteArray finalKey = hash.result();
66

67
    // write header
68
    QBuffer header;
69
    header.open(QIODevice::WriteOnly);
70

71
    writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, db->formatVersion());
72

73
    CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
74
    CHECK_RETURN_FALSE(
75
        writeHeaderField<quint16>(&header,
76
                                  KeePass2::HeaderFieldID::CompressionFlags,
77
                                  Endian::sizedIntToBytes<qint32>(db->compressionAlgorithm(), KeePass2::BYTEORDER)));
78
    auto kdf = db->kdf();
79
    CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
80
    CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::TransformSeed, kdf->seed()));
81
    CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header,
82
                                                 KeePass2::HeaderFieldID::TransformRounds,
83
                                                 Endian::sizedIntToBytes<qint64>(kdf->rounds(), KeePass2::BYTEORDER)));
84
    CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
85
    CHECK_RETURN_FALSE(
86
        writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::ProtectedStreamKey, protectedStreamKey));
87
    CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::StreamStartBytes, startBytes));
88
    CHECK_RETURN_FALSE(writeHeaderField<quint16>(
89
        &header,
90
        KeePass2::HeaderFieldID::InnerRandomStreamID,
91
        Endian::sizedIntToBytes<qint32>(static_cast<qint32>(KeePass2::ProtectedStreamAlgo::Salsa20),
92
                                        KeePass2::BYTEORDER)));
93
    CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader));
94
    header.close();
95

96
    // write header data
97
    CHECK_RETURN_FALSE(writeData(device, header.data()));
98

99
    // hash header
100
    const QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
101

102
    // write cipher stream
103
    SymmetricCipherStream cipherStream(device);
104
    cipherStream.init(mode, SymmetricCipher::Encrypt, finalKey, encryptionIV);
105
    if (!cipherStream.open(QIODevice::WriteOnly)) {
106
        raiseError(cipherStream.errorString());
107
        return false;
108
    }
109
    CHECK_RETURN_FALSE(writeData(&cipherStream, startBytes));
110

111
    HashedBlockStream hashedStream(&cipherStream);
112
    if (!hashedStream.open(QIODevice::WriteOnly)) {
113
        raiseError(hashedStream.errorString());
114
        return false;
115
    }
116

117
    QIODevice* outputDevice = nullptr;
118
    QScopedPointer<QtIOCompressor> ioCompressor;
119

120
    if (db->compressionAlgorithm() == Database::CompressionNone) {
121
        outputDevice = &hashedStream;
122
    } else {
123
        ioCompressor.reset(new QtIOCompressor(&hashedStream));
124
        ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
125
        if (!ioCompressor->open(QIODevice::WriteOnly)) {
126
            raiseError(ioCompressor->errorString());
127
            return false;
128
        }
129
        outputDevice = ioCompressor.data();
130
    }
131

132
    Q_ASSERT(outputDevice);
133

134
    KeePass2RandomStream randomStream;
135
    if (!randomStream.init(SymmetricCipher::Salsa20, protectedStreamKey)) {
136
        raiseError(randomStream.errorString());
137
        return false;
138
    }
139

140
    KdbxXmlWriter xmlWriter(db->formatVersion());
141
    xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
142

143
    // Explicitly close/reset streams so they are flushed and we can detect
144
    // errors. QIODevice::close() resets errorString() etc.
145
    if (ioCompressor) {
146
        ioCompressor->close();
147
    }
148
    if (!hashedStream.reset()) {
149
        raiseError(hashedStream.errorString());
150
        return false;
151
    }
152
    if (!cipherStream.reset()) {
153
        raiseError(cipherStream.errorString());
154
        return false;
155
    }
156

157
    if (xmlWriter.hasError()) {
158
        raiseError(xmlWriter.errorString());
159
        return false;
160
    }
161

162
    return true;
163
}
164

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.