Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/oox/source/crypto/Standard2007Engine.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 */
10
11
#include <oox/crypto/Standard2007Engine.hxx>
12
13
#include <oox/helper/binaryinputstream.hxx>
14
#include <oox/helper/binaryoutputstream.hxx>
15
#include <rtl/random.h>
16
17
#include <comphelper/crypto/Crypto.hxx>
18
#include <comphelper/hash.hxx>
19
20
namespace oox::crypto {
21
22
/* =========================================================================== */
23
/*  Kudos to Caolan McNamara who provided the core decryption implementations. */
24
/* =========================================================================== */
25
namespace
26
{
27
28
void lclRandomGenerateValues(sal_uInt8* aArray, sal_uInt32 aSize)
29
0
{
30
0
    if (rtl_random_getBytes(nullptr, aArray, aSize) != rtl_Random_E_None)
31
0
    {
32
0
        throw css::uno::RuntimeException(u"rtl_random_getBytes failed"_ustr);
33
0
    }
34
0
}
35
36
constexpr OUString lclCspName = u"Microsoft Enhanced RSA and AES Cryptographic Provider"_ustr;
37
constexpr const sal_uInt32 AES128Size = 16;
38
39
} // end anonymous namespace
40
41
bool Standard2007Engine::generateVerifier()
42
0
{
43
    // only support key of size 128 bit (16 byte)
44
0
    if (mKey.size() != 16)
45
0
        return false;
46
47
0
    std::vector<sal_uInt8> verifier(msfilter::ENCRYPTED_VERIFIER_LENGTH);
48
0
    std::vector<sal_uInt8> encryptedVerifier(msfilter::ENCRYPTED_VERIFIER_LENGTH);
49
50
0
    lclRandomGenerateValues(verifier.data(), verifier.size());
51
52
0
    std::vector<sal_uInt8> iv;
53
0
    comphelper::Encrypt aEncryptorVerifier(mKey, iv, comphelper::CryptoType::AES_128_ECB);
54
0
    if (aEncryptorVerifier.update(encryptedVerifier, verifier) != msfilter::ENCRYPTED_VERIFIER_LENGTH)
55
0
        return false;
56
0
    std::copy(encryptedVerifier.begin(), encryptedVerifier.end(), mInfo.verifier.encryptedVerifier);
57
58
0
    mInfo.verifier.encryptedVerifierHashSize = comphelper::SHA1_HASH_LENGTH;
59
0
    std::vector<sal_uInt8> hash = comphelper::Hash::calculateHash(verifier.data(), verifier.size(), comphelper::HashType::SHA1);
60
0
    hash.resize(comphelper::SHA256_HASH_LENGTH, 0);
61
62
0
    std::vector<sal_uInt8> encryptedHash(comphelper::SHA256_HASH_LENGTH, 0);
63
64
0
    comphelper::Encrypt aEncryptorHash(mKey, iv, comphelper::CryptoType::AES_128_ECB);
65
0
    aEncryptorHash.update(encryptedHash, hash, hash.size());
66
0
    std::copy(encryptedHash.begin(), encryptedHash.end(), mInfo.verifier.encryptedVerifierHash);
67
68
0
    return true;
69
0
}
70
71
bool Standard2007Engine::calculateEncryptionKey(std::u16string_view rPassword)
72
34
{
73
34
    sal_uInt32 saltSize = mInfo.verifier.saltSize;
74
34
    size_t passwordByteLength = rPassword.size() * 2;
75
34
    const sal_uInt8* saltArray = mInfo.verifier.salt;
76
77
    // Prepare initial data -> salt + password (in 16-bit chars)
78
34
    std::vector<sal_uInt8> initialData(saltSize + passwordByteLength);
79
34
    std::copy(saltArray, saltArray + saltSize, initialData.begin());
80
81
34
    auto p = initialData.begin() + saltSize;
82
544
    for (size_t i = 0; i != rPassword.size(); ++i) {
83
510
        auto c = rPassword[i];
84
510
        *p++ = c & 0xFF;
85
510
        *p++ = c >> 8;
86
510
    }
87
88
    // use "hash" vector for result of sha1 hashing
89
    // calculate SHA1 hash of initialData
90
34
    std::vector<sal_uInt8> hash = comphelper::Hash::calculateHash(initialData.data(), initialData.size(), comphelper::HashType::SHA1);
91
92
    // data = iterator (4bytes) + hash
93
34
    std::vector<sal_uInt8> data(comphelper::SHA1_HASH_LENGTH + 4, 0);
94
95
1.70M
    for (sal_Int32 i = 0; i < 50000; ++i)
96
1.70M
    {
97
1.70M
        ByteOrderConverter::writeLittleEndian(data.data(), i);
98
1.70M
        std::copy(hash.begin(), hash.end(), data.begin() + 4);
99
1.70M
        hash = comphelper::Hash::calculateHash(data.data(), data.size(), comphelper::HashType::SHA1);
100
1.70M
    }
101
34
    std::copy(hash.begin(), hash.end(), data.begin() );
102
34
    std::fill(data.begin() + comphelper::SHA1_HASH_LENGTH, data.end(), 0 );
103
104
34
    hash = comphelper::Hash::calculateHash(data.data(), data.size(), comphelper::HashType::SHA1);
105
106
    // derive key
107
34
    std::vector<sal_uInt8> buffer(64, 0x36);
108
714
    for (size_t i = 0; i < hash.size(); ++i)
109
680
        buffer[i] ^= hash[i];
110
111
34
    hash = comphelper::Hash::calculateHash(buffer.data(), buffer.size(), comphelper::HashType::SHA1);
112
34
    if (mKey.size() > hash.size())
113
4
        return false;
114
30
    std::copy(hash.begin(), hash.begin() + mKey.size(), mKey.begin());
115
116
30
    return true;
117
34
}
118
119
bool Standard2007Engine::generateEncryptionKey(std::u16string_view password)
120
37
{
121
37
    mKey.clear();
122
    /*
123
        KeySize (4 bytes): An unsigned integer that specifies the number of bits in the encryption key.
124
        MUST be a multiple of 8. MUST be one of the values in the following table:
125
        Algorithm   Value                               Comment
126
        Any         0x00000000                          Determined by Flags
127
        RC4         0x00000028 – 0x00000080             (inclusive) 8-bit increments.
128
        AES         0x00000080, 0x000000C0, 0x00000100  128, 192 or 256-bit
129
    */
130
37
    if (mInfo.header.keyBits > 8192) // should we strictly enforce the above 256 bit limit ?
131
2
        return false;
132
35
    mKey.resize(mInfo.header.keyBits / 8, 0);
133
35
    if (mKey.empty())
134
1
        return false;
135
136
34
    calculateEncryptionKey(password);
137
138
34
    std::vector<sal_uInt8> encryptedVerifier(msfilter::ENCRYPTED_VERIFIER_LENGTH);
139
34
    std::copy(
140
34
        mInfo.verifier.encryptedVerifier,
141
34
        mInfo.verifier.encryptedVerifier + msfilter::ENCRYPTED_VERIFIER_LENGTH,
142
34
        encryptedVerifier.begin());
143
144
34
    std::vector<sal_uInt8> encryptedHash(comphelper::SHA256_HASH_LENGTH);
145
34
    std::copy(
146
34
        mInfo.verifier.encryptedVerifierHash,
147
34
        mInfo.verifier.encryptedVerifierHash + comphelper::SHA256_HASH_LENGTH,
148
34
        encryptedHash.begin());
149
150
34
    std::vector<sal_uInt8> verifier(encryptedVerifier.size(), 0);
151
34
    comphelper::Decrypt::aes128ecb(verifier, encryptedVerifier, mKey);
152
153
34
    std::vector<sal_uInt8> verifierHash(encryptedHash.size(), 0);
154
34
    comphelper::Decrypt::aes128ecb(verifierHash, encryptedHash, mKey);
155
156
34
    std::vector<sal_uInt8> hash = comphelper::Hash::calculateHash(verifier.data(), verifier.size(), comphelper::HashType::SHA1);
157
158
34
    return std::equal(hash.begin(), hash.end(), verifierHash.begin());
159
35
}
160
161
bool Standard2007Engine::decrypt(BinaryXInputStream& aInputStream,
162
                                 BinaryXOutputStream& aOutputStream)
163
24
{
164
24
    sal_uInt32 totalSize = aInputStream.readuInt32(); // Document unencrypted size - 4 bytes
165
24
    aInputStream.skip(4); // Reserved 4 Bytes
166
167
24
    std::vector<sal_uInt8> iv;
168
24
    comphelper::Decrypt aDecryptor(mKey, iv, comphelper::CryptoType::AES_128_ECB);
169
24
    std::vector<sal_uInt8> inputBuffer (4096);
170
24
    std::vector<sal_uInt8> outputBuffer(4096);
171
24
    sal_uInt32 inputLength;
172
24
    sal_uInt32 outputLength;
173
24
    sal_uInt32 remaining = totalSize;
174
175
76
    while ((inputLength = aInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
176
52
    {
177
52
        outputLength = aDecryptor.update(outputBuffer, inputBuffer, inputLength);
178
52
        sal_uInt32 writeLength = std::min(outputLength, remaining);
179
52
        aOutputStream.writeMemory(outputBuffer.data(), writeLength);
180
52
        remaining -= outputLength;
181
52
    }
182
24
    return true;
183
24
}
184
185
bool Standard2007Engine::checkDataIntegrity()
186
24
{
187
24
    return true;
188
24
}
189
190
bool Standard2007Engine::setupEncryption(OUString const & password)
191
0
{
192
0
    mInfo.header.flags        = msfilter::ENCRYPTINFO_AES | msfilter::ENCRYPTINFO_CRYPTOAPI;
193
0
    mInfo.header.algId        = msfilter::ENCRYPT_ALGO_AES128;
194
0
    mInfo.header.algIdHash    = msfilter::ENCRYPT_HASH_SHA1;
195
0
    mInfo.header.keyBits      = msfilter::ENCRYPT_KEY_SIZE_AES_128;
196
0
    mInfo.header.providedType = msfilter::ENCRYPT_PROVIDER_TYPE_AES;
197
198
0
    lclRandomGenerateValues(mInfo.verifier.salt, mInfo.verifier.saltSize);
199
0
    const sal_Int32 keyLength = mInfo.header.keyBits / 8;
200
201
0
    mKey.clear();
202
0
    mKey.resize(keyLength, 0);
203
204
0
    if (!calculateEncryptionKey(password))
205
0
        return false;
206
207
0
    if (!generateVerifier())
208
0
        return false;
209
210
0
    return true;
211
0
}
212
213
void Standard2007Engine::writeEncryptionInfo(BinaryXOutputStream& rStream)
214
0
{
215
0
    rStream.WriteUInt32(msfilter::VERSION_INFO_2007_FORMAT);
216
217
0
    sal_uInt32 cspNameSize = (lclCspName.getLength() * 2) + 2;
218
219
0
    sal_uInt32 encryptionHeaderSize = static_cast<sal_uInt32>(sizeof(msfilter::EncryptionStandardHeader));
220
221
0
    rStream.WriteUInt32(mInfo.header.flags);
222
0
    sal_uInt32 headerSize = encryptionHeaderSize + cspNameSize;
223
0
    rStream.WriteUInt32(headerSize);
224
225
0
    rStream.WriteUInt32(mInfo.header.flags);
226
0
    rStream.WriteUInt32(mInfo.header.sizeExtra);
227
0
    rStream.WriteUInt32(mInfo.header.algId);
228
0
    rStream.WriteUInt32(mInfo.header.algIdHash);
229
0
    rStream.WriteUInt32(mInfo.header.keyBits);
230
0
    rStream.WriteUInt32(mInfo.header.providedType);
231
0
    rStream.WriteUInt32(mInfo.header.reserved1);
232
0
    rStream.WriteUInt32(mInfo.header.reserved2);
233
0
    rStream.writeUnicodeArray(lclCspName);
234
0
    rStream.WriteUInt16(0);
235
236
0
    rStream.WriteUInt32(mInfo.verifier.saltSize);
237
0
    rStream.writeMemory(&mInfo.verifier.salt, sizeof mInfo.verifier.salt);
238
0
    rStream.writeMemory(&mInfo.verifier.encryptedVerifier, sizeof mInfo.verifier.encryptedVerifier);
239
0
    rStream.WriteUInt32(mInfo.verifier.encryptedVerifierHashSize);
240
0
    rStream.writeMemory(
241
0
        &mInfo.verifier.encryptedVerifierHash, sizeof mInfo.verifier.encryptedVerifierHash);
242
0
}
243
244
void Standard2007Engine::encrypt(const css::uno::Reference<css::io::XInputStream> &  rxInputStream,
245
                                 css::uno::Reference<css::io::XOutputStream> & rxOutputStream,
246
                                 sal_uInt32 nSize)
247
0
{
248
0
    if (mKey.empty())
249
0
        return;
250
251
0
    BinaryXOutputStream aBinaryOutputStream(rxOutputStream, false);
252
0
    BinaryXInputStream aBinaryInputStream(rxInputStream, false);
253
254
0
    aBinaryOutputStream.WriteUInt32(nSize); // size
255
0
    aBinaryOutputStream.WriteUInt32(0U);    // reserved
256
257
0
    std::vector<sal_uInt8> inputBuffer(1024);
258
0
    std::vector<sal_uInt8> outputBuffer(1024);
259
260
0
    sal_uInt32 inputLength;
261
0
    sal_uInt32 outputLength;
262
263
0
    std::vector<sal_uInt8> iv;
264
0
    comphelper::Encrypt aEncryptor(mKey, iv, comphelper::CryptoType::AES_128_ECB);
265
266
0
    while ((inputLength = aBinaryInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
267
0
    {
268
        // increase size to multiple of 16 (size of mKey) if necessary
269
0
        inputLength = inputLength % AES128Size == 0 ?
270
0
                            inputLength : comphelper::roundUp(inputLength, AES128Size);
271
0
        outputLength = aEncryptor.update(outputBuffer, inputBuffer, inputLength);
272
0
        aBinaryOutputStream.writeMemory(outputBuffer.data(), outputLength);
273
0
    }
274
0
}
275
276
bool Standard2007Engine::readEncryptionInfo(css::uno::Reference<css::io::XInputStream> & rxInputStream)
277
250
{
278
250
    BinaryXInputStream aBinaryStream(rxInputStream, false);
279
280
250
    mInfo.header.flags = aBinaryStream.readuInt32();
281
250
    if (getFlag(mInfo.header.flags, msfilter::ENCRYPTINFO_EXTERNAL))
282
23
        return false;
283
284
227
    sal_uInt32 nHeaderSize = aBinaryStream.readuInt32();
285
286
227
    sal_uInt32 actualHeaderSize = sizeof(mInfo.header);
287
288
227
    if (nHeaderSize < actualHeaderSize)
289
65
        return false;
290
291
162
    mInfo.header.flags = aBinaryStream.readuInt32();
292
162
    mInfo.header.sizeExtra = aBinaryStream.readuInt32();
293
162
    mInfo.header.algId = aBinaryStream.readuInt32();
294
162
    mInfo.header.algIdHash = aBinaryStream.readuInt32();
295
162
    mInfo.header.keyBits = aBinaryStream.readuInt32();
296
162
    mInfo.header.providedType = aBinaryStream.readuInt32();
297
162
    mInfo.header.reserved1 = aBinaryStream.readuInt32();
298
162
    mInfo.header.reserved2 = aBinaryStream.readuInt32();
299
300
162
    aBinaryStream.skip(nHeaderSize - actualHeaderSize);
301
302
162
    mInfo.verifier.saltSize = aBinaryStream.readuInt32();
303
162
    aBinaryStream.readArray(mInfo.verifier.salt, SAL_N_ELEMENTS(mInfo.verifier.salt));
304
162
    aBinaryStream.readArray(mInfo.verifier.encryptedVerifier, SAL_N_ELEMENTS(mInfo.verifier.encryptedVerifier));
305
162
    mInfo.verifier.encryptedVerifierHashSize = aBinaryStream.readuInt32();
306
162
    aBinaryStream.readArray(mInfo.verifier.encryptedVerifierHash, SAL_N_ELEMENTS(mInfo.verifier.encryptedVerifierHash));
307
308
162
    if (mInfo.verifier.saltSize != 16)
309
113
        return false;
310
311
    // check flags and algorithm IDs, required are AES128 and SHA-1
312
49
    if (!getFlag(mInfo.header.flags, msfilter::ENCRYPTINFO_CRYPTOAPI))
313
3
        return false;
314
315
46
    if (!getFlag(mInfo.header.flags, msfilter::ENCRYPTINFO_AES))
316
1
        return false;
317
318
    // algorithm ID 0 defaults to AES128 too, if ENCRYPTINFO_AES flag is set
319
45
    if (mInfo.header.algId != 0 && mInfo.header.algId != msfilter::ENCRYPT_ALGO_AES128)
320
2
        return false;
321
322
    // hash algorithm ID 0 defaults to SHA-1 too
323
43
    if (mInfo.header.algIdHash != 0 && mInfo.header.algIdHash != msfilter::ENCRYPT_HASH_SHA1)
324
2
        return false;
325
326
41
    if (mInfo.verifier.encryptedVerifierHashSize != 20)
327
4
        return false;
328
329
37
    return !aBinaryStream.isEof();
330
41
}
331
332
} // namespace oox::crypto
333
334
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */