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 #include <config_oox.h>
11 #include <cppunit/plugin/TestPlugIn.h>
12 #include <cppunit/extensions/HelperMacros.h>
13 #include <cppunit/TestFixture.h>
14
15 #include <algorithm>
16 #include <tools/stream.hxx>
17 #include <unotools/streamwrap.hxx>
18
19 #include <oox/crypto/Standard2007Engine.hxx>
20 #include <oox/crypto/AgileEngine.hxx>
21 #include <oox/helper/binaryinputstream.hxx>
22 #include <oox/helper/binaryoutputstream.hxx>
23
24 #if USE_TLS_NSS
25 #include <nss.h>
26 #endif
27
28 using namespace css;
29
30 class CryptoTest : public CppUnit::TestFixture
31 {
32 public:
33 virtual ~CryptoTest() override;
34 void testCryptoHash();
35 void testRoundUp();
36 void testStandard2007();
37 void testAgileEncryptionVerifier();
38 void testAgileEncryptionInfoWritingAndParsing();
39 void testAgileDataIntegrityHmacKey();
40 void testAgileEncryptingAndDecrypting();
41
42 CPPUNIT_TEST_SUITE(CryptoTest);
43 CPPUNIT_TEST(testCryptoHash);
44 CPPUNIT_TEST(testRoundUp);
45 CPPUNIT_TEST(testStandard2007);
46 CPPUNIT_TEST(testAgileEncryptionVerifier);
47 CPPUNIT_TEST(testAgileEncryptionInfoWritingAndParsing);
48 CPPUNIT_TEST(testAgileDataIntegrityHmacKey);
49 CPPUNIT_TEST(testAgileEncryptingAndDecrypting);
50 CPPUNIT_TEST_SUITE_END();
51 };
52
53 namespace
54 {
toString(std::vector<sal_uInt8> const & aInput)55 std::string toString(std::vector<sal_uInt8> const& aInput)
56 {
57 std::stringstream aStream;
58 for (auto const& aValue : aInput)
59 {
60 aStream << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(aValue);
61 }
62
63 return aStream.str();
64 }
65 }
66
~CryptoTest()67 CryptoTest::~CryptoTest()
68 {
69 #if USE_TLS_NSS
70 NSS_Shutdown();
71 #endif
72 }
73
testCryptoHash()74 void CryptoTest::testCryptoHash()
75 {
76 // Check examples from Wikipedia (https://en.wikipedia.org/wiki/HMAC)
77 OString aContentString("The quick brown fox jumps over the lazy dog");
78 std::vector<sal_uInt8> aContent(aContentString.getStr(),
79 aContentString.getStr() + aContentString.getLength());
80 std::vector<sal_uInt8> aKey = { 'k', 'e', 'y' };
81 {
82 oox::crypto::CryptoHash aCryptoHash(aKey, oox::crypto::CryptoHashType::SHA1);
83 aCryptoHash.update(aContent);
84 std::vector<sal_uInt8> aHash = aCryptoHash.finalize();
85 CPPUNIT_ASSERT_EQUAL(std::string("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"),
86 toString(aHash));
87 }
88
89 {
90 oox::crypto::CryptoHash aCryptoHash(aKey, oox::crypto::CryptoHashType::SHA256);
91 aCryptoHash.update(aContent);
92 std::vector<sal_uInt8> aHash = aCryptoHash.finalize();
93 CPPUNIT_ASSERT_EQUAL(
94 std::string("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"),
95 toString(aHash));
96 }
97
98 {
99 oox::crypto::CryptoHash aCryptoHash(aKey, oox::crypto::CryptoHashType::SHA512);
100 aCryptoHash.update(aContent);
101 std::vector<sal_uInt8> aHash = aCryptoHash.finalize();
102 CPPUNIT_ASSERT_EQUAL(
103 std::string("b42af09057bac1e2d41708e48a902e09b5ff7f12ab428a4fe86653c73dd248fb82f948a549"
104 "f7b791a5b41915ee4d1ec3935357e4e2317250d0372afa2ebeeb3a"),
105 toString(aHash));
106 }
107 }
108
testRoundUp()109 void CryptoTest::testRoundUp()
110 {
111 CPPUNIT_ASSERT_EQUAL(16, oox::crypto::roundUp(16, 16));
112 CPPUNIT_ASSERT_EQUAL(32, oox::crypto::roundUp(32, 16));
113 CPPUNIT_ASSERT_EQUAL(64, oox::crypto::roundUp(64, 16));
114
115 CPPUNIT_ASSERT_EQUAL(16, oox::crypto::roundUp(01, 16));
116 CPPUNIT_ASSERT_EQUAL(32, oox::crypto::roundUp(17, 16));
117 CPPUNIT_ASSERT_EQUAL(32, oox::crypto::roundUp(31, 16));
118 }
119
testStandard2007()120 void CryptoTest::testStandard2007()
121 {
122 oox::crypto::Standard2007Engine aEngine;
123 {
124 aEngine.setupEncryption("Password");
125
126 SvMemoryStream aEncryptionInfo;
127 oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
128 new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), false);
129
130 aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
131 aBinaryEncryptionInfoOutputStream.close();
132
133 CPPUNIT_ASSERT_EQUAL(sal_uInt64(224), aEncryptionInfo.GetSize());
134 }
135
136 SvMemoryStream aUnencryptedInput;
137 SvMemoryStream aEncryptedStream;
138
139 OString aTestString = "1234567890ABCDEFG";
140
141 aUnencryptedInput.WriteBytes(aTestString.getStr(), aTestString.getLength() + 1);
142 aUnencryptedInput.Seek(STREAM_SEEK_TO_BEGIN);
143
144 {
145 uno::Reference<io::XInputStream> xInputStream(
146 new utl::OSeekableInputStreamWrapper(aUnencryptedInput));
147 uno::Reference<io::XOutputStream> xOutputStream(
148 new utl::OSeekableOutputStreamWrapper(aEncryptedStream));
149
150 aEngine.encrypt(xInputStream, xOutputStream, aUnencryptedInput.GetSize());
151
152 xOutputStream->flush();
153
154 const sal_uInt8* pData = static_cast<const sal_uInt8*>(aEncryptedStream.GetData());
155 sal_uInt64 nSize = aEncryptedStream.GetSize();
156
157 std::vector<sal_uInt8> aData(nSize);
158 std::copy(pData, pData + nSize, aData.data());
159
160 CPPUNIT_ASSERT_EQUAL(sal_uInt64(40), nSize);
161 }
162
163 aEncryptedStream.Seek(STREAM_SEEK_TO_BEGIN);
164 SvMemoryStream aUnencryptedOutput;
165
166 {
167 oox::BinaryXInputStream aBinaryInputStream(
168 new utl::OSeekableInputStreamWrapper(aEncryptedStream), true);
169 oox::BinaryXOutputStream aBinaryOutputStream(
170 new utl::OSeekableOutputStreamWrapper(aUnencryptedOutput), true);
171
172 aEngine.decrypt(aBinaryInputStream, aBinaryOutputStream);
173 aBinaryOutputStream.close();
174 aBinaryInputStream.close();
175
176 const char* pData = static_cast<const char*>(aUnencryptedOutput.GetData());
177 sal_uInt64 nSize = aUnencryptedOutput.GetSize();
178
179 CPPUNIT_ASSERT_EQUAL(sal_uInt64(18), nSize);
180
181 OString aString(pData);
182
183 CPPUNIT_ASSERT_EQUAL(aTestString, aString);
184 }
185 }
186
testAgileEncryptionVerifier()187 void CryptoTest::testAgileEncryptionVerifier()
188 {
189 oox::crypto::AgileEngine aEngine;
190
191 OUString aPassword("Password");
192
193 aEngine.setupEncryptionParameters({ 100000, 16, 128, 20, 16, OUString("AES"),
194 OUString("ChainingModeCBC"), OUString("SHA1") });
195
196 CPPUNIT_ASSERT_EQUAL(true, aEngine.generateAndEncryptVerifierHash(aPassword));
197 CPPUNIT_ASSERT_EQUAL(false, aEngine.decryptAndCheckVerifierHash("Wrong"));
198 CPPUNIT_ASSERT_EQUAL(true, aEngine.decryptAndCheckVerifierHash(aPassword));
199
200 aEngine.setupEncryptionParameters({ 100000, 16, 256, 64, 16, OUString("AES"),
201 OUString("ChainingModeCBC"), OUString("SHA512") });
202
203 CPPUNIT_ASSERT_EQUAL(true, aEngine.generateAndEncryptVerifierHash(aPassword));
204 CPPUNIT_ASSERT_EQUAL(false, aEngine.decryptAndCheckVerifierHash("Wrong"));
205 CPPUNIT_ASSERT_EQUAL(true, aEngine.decryptAndCheckVerifierHash(aPassword));
206 }
207
testAgileEncryptionInfoWritingAndParsing()208 void CryptoTest::testAgileEncryptionInfoWritingAndParsing()
209 {
210 OUString aPassword("Password");
211 std::vector<sal_uInt8> aKeyDataSalt;
212
213 { // Preset AES128 - SHA1
214 SvMemoryStream aEncryptionInfo;
215 {
216 oox::crypto::AgileEngine aEngine;
217
218 aEngine.setPreset(oox::crypto::AgileEncryptionPreset::AES_128_SHA1);
219 aEngine.setupEncryption(aPassword);
220 aKeyDataSalt = aEngine.getInfo().keyDataSalt;
221
222 oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
223 new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), true);
224
225 aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
226 aBinaryEncryptionInfoOutputStream.close();
227
228 CPPUNIT_ASSERT_EQUAL(sal_uInt64(996), aEncryptionInfo.GetSize());
229 }
230
231 aEncryptionInfo.Seek(STREAM_SEEK_TO_BEGIN);
232
233 {
234 oox::crypto::AgileEngine aEngine;
235
236 uno::Reference<io::XInputStream> xInputStream(
237 new utl::OSeekableInputStreamWrapper(aEncryptionInfo));
238
239 xInputStream->skipBytes(4); // Encryption type -> Agile
240
241 CPPUNIT_ASSERT(aEngine.readEncryptionInfo(xInputStream));
242
243 oox::crypto::AgileEncryptionInfo& rInfo = aEngine.getInfo();
244 CPPUNIT_ASSERT_EQUAL(sal_Int32(100000), rInfo.spinCount);
245 CPPUNIT_ASSERT_EQUAL(sal_Int32(16), rInfo.saltSize);
246 CPPUNIT_ASSERT_EQUAL(sal_Int32(128), rInfo.keyBits);
247 CPPUNIT_ASSERT_EQUAL(sal_Int32(20), rInfo.hashSize);
248 CPPUNIT_ASSERT_EQUAL(sal_Int32(16), rInfo.blockSize);
249 CPPUNIT_ASSERT_EQUAL(OUString("AES"), rInfo.cipherAlgorithm);
250 CPPUNIT_ASSERT_EQUAL(OUString("ChainingModeCBC"), rInfo.cipherChaining);
251 CPPUNIT_ASSERT_EQUAL(OUString("SHA1"), rInfo.hashAlgorithm);
252 CPPUNIT_ASSERT_EQUAL(toString(aKeyDataSalt), toString(rInfo.keyDataSalt));
253
254 CPPUNIT_ASSERT_EQUAL(false, aEngine.decryptAndCheckVerifierHash("Wrong"));
255 CPPUNIT_ASSERT_EQUAL(true, aEngine.decryptAndCheckVerifierHash(aPassword));
256 }
257 }
258
259 { // Preset AES256 - SHA512
260 SvMemoryStream aEncryptionInfo;
261 {
262 oox::crypto::AgileEngine aEngine;
263
264 aEngine.setPreset(oox::crypto::AgileEncryptionPreset::AES_256_SHA512);
265 aEngine.setupEncryption(aPassword);
266 aKeyDataSalt = aEngine.getInfo().keyDataSalt;
267
268 oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
269 new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), true);
270
271 aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
272 aBinaryEncryptionInfoOutputStream.close();
273
274 CPPUNIT_ASSERT_EQUAL(sal_uInt64(1112), aEncryptionInfo.GetSize());
275 }
276
277 aEncryptionInfo.Seek(STREAM_SEEK_TO_BEGIN);
278
279 {
280 oox::crypto::AgileEngine aEngine;
281
282 uno::Reference<io::XInputStream> xInputStream(
283 new utl::OSeekableInputStreamWrapper(aEncryptionInfo));
284
285 xInputStream->skipBytes(4); // Encryption type -> Agile
286
287 CPPUNIT_ASSERT(aEngine.readEncryptionInfo(xInputStream));
288
289 oox::crypto::AgileEncryptionInfo& rInfo = aEngine.getInfo();
290 CPPUNIT_ASSERT_EQUAL(sal_Int32(100000), rInfo.spinCount);
291 CPPUNIT_ASSERT_EQUAL(sal_Int32(16), rInfo.saltSize);
292 CPPUNIT_ASSERT_EQUAL(sal_Int32(256), rInfo.keyBits);
293 CPPUNIT_ASSERT_EQUAL(sal_Int32(64), rInfo.hashSize);
294 CPPUNIT_ASSERT_EQUAL(sal_Int32(16), rInfo.blockSize);
295 CPPUNIT_ASSERT_EQUAL(OUString("AES"), rInfo.cipherAlgorithm);
296 CPPUNIT_ASSERT_EQUAL(OUString("ChainingModeCBC"), rInfo.cipherChaining);
297 CPPUNIT_ASSERT_EQUAL(OUString("SHA512"), rInfo.hashAlgorithm);
298 CPPUNIT_ASSERT_EQUAL(toString(aKeyDataSalt), toString(rInfo.keyDataSalt));
299
300 CPPUNIT_ASSERT_EQUAL(false, aEngine.decryptAndCheckVerifierHash("Wrong"));
301 CPPUNIT_ASSERT_EQUAL(true, aEngine.decryptAndCheckVerifierHash(aPassword));
302 }
303 }
304 }
305
testAgileDataIntegrityHmacKey()306 void CryptoTest::testAgileDataIntegrityHmacKey()
307 {
308 OUString aPassword("Password");
309
310 std::vector<sal_uInt8> aKeyDataSalt;
311
312 std::vector<sal_uInt8> aHmacKey;
313 std::vector<sal_uInt8> aHmacEncryptedKey;
314
315 SvMemoryStream aEncryptionInfo;
316 {
317 oox::crypto::AgileEngine aEngine;
318 aEngine.setupEncryption(aPassword);
319 oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
320 new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), true);
321 aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
322 aBinaryEncryptionInfoOutputStream.close();
323
324 aHmacKey = aEngine.getInfo().hmacKey;
325 aKeyDataSalt = aEngine.getInfo().keyDataSalt;
326 aHmacEncryptedKey = aEngine.getInfo().hmacEncryptedKey;
327 }
328
329 aEncryptionInfo.Seek(STREAM_SEEK_TO_BEGIN);
330
331 {
332 oox::crypto::AgileEngine aEngine;
333
334 uno::Reference<io::XInputStream> xInputStream(
335 new utl::OSeekableInputStreamWrapper(aEncryptionInfo));
336
337 xInputStream->skipBytes(4); // Encryption type -> Agile
338
339 CPPUNIT_ASSERT(aEngine.readEncryptionInfo(xInputStream));
340 CPPUNIT_ASSERT(aEngine.generateEncryptionKey(aPassword));
341
342 CPPUNIT_ASSERT_EQUAL(toString(aKeyDataSalt), toString(aEngine.getInfo().keyDataSalt));
343
344 CPPUNIT_ASSERT_EQUAL(toString(aHmacEncryptedKey),
345 toString(aEngine.getInfo().hmacEncryptedKey));
346
347 CPPUNIT_ASSERT_EQUAL(size_t(64), aHmacKey.size());
348 CPPUNIT_ASSERT_EQUAL(toString(aHmacKey), toString(aEngine.getInfo().hmacKey));
349 }
350 }
351
testAgileEncryptingAndDecrypting()352 void CryptoTest::testAgileEncryptingAndDecrypting()
353 {
354 OUString aPassword("Password");
355
356 SvMemoryStream aEncryptionInfo;
357 SvMemoryStream aEncryptedStream;
358
359 OString aTestString = "1234567890ABCDEFGH";
360
361 {
362 oox::crypto::AgileEngine aEngine;
363
364 // Setup input
365 SvMemoryStream aUnencryptedInput;
366 uno::Reference<io::XInputStream> xInputStream(
367 new utl::OSeekableInputStreamWrapper(aUnencryptedInput));
368
369 aUnencryptedInput.WriteBytes(aTestString.getStr(), aTestString.getLength() + 1);
370 aUnencryptedInput.Seek(STREAM_SEEK_TO_BEGIN);
371
372 // Setup output
373 uno::Reference<io::XOutputStream> xOutputStream(
374 new utl::OSeekableOutputStreamWrapper(aEncryptedStream));
375
376 // Write content
377 aEngine.setupEncryption(aPassword);
378 aEngine.encrypt(xInputStream, xOutputStream, aUnencryptedInput.GetSize());
379 xOutputStream->flush();
380
381 // Check content
382 sal_uInt64 nSize = aEncryptedStream.GetSize();
383
384 CPPUNIT_ASSERT_EQUAL(sal_uInt64(40), nSize);
385
386 // Setup and write encryption info
387 oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
388 new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), true);
389 aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
390 aBinaryEncryptionInfoOutputStream.close();
391 }
392
393 aEncryptedStream.Seek(STREAM_SEEK_TO_BEGIN);
394 aEncryptionInfo.Seek(STREAM_SEEK_TO_BEGIN);
395
396 {
397 oox::crypto::AgileEngine aEngine;
398
399 // Read encryption info
400 uno::Reference<io::XInputStream> xEncryptionInfo(
401 new utl::OSeekableInputStreamWrapper(aEncryptionInfo));
402
403 xEncryptionInfo->skipBytes(4); // Encryption type -> Agile
404
405 CPPUNIT_ASSERT(aEngine.readEncryptionInfo(xEncryptionInfo));
406
407 // Setup password
408 CPPUNIT_ASSERT(aEngine.generateEncryptionKey(aPassword));
409
410 // Setup encrypted input stream
411 oox::BinaryXInputStream aBinaryInputStream(
412 new utl::OSeekableInputStreamWrapper(aEncryptedStream), true);
413
414 // Setup output stream
415 SvMemoryStream aUnencryptedOutput;
416 oox::BinaryXOutputStream aBinaryOutputStream(
417 new utl::OSeekableOutputStreamWrapper(aUnencryptedOutput), true);
418
419 // Decrypt
420 aEngine.decrypt(aBinaryInputStream, aBinaryOutputStream);
421 aBinaryOutputStream.close();
422 aBinaryInputStream.close();
423
424 // Check decrypted output
425 CPPUNIT_ASSERT_EQUAL(sal_uInt64(19), aUnencryptedOutput.GetSize());
426
427 OString aString(static_cast<const char*>(aUnencryptedOutput.GetData()));
428 CPPUNIT_ASSERT_EQUAL(aTestString, aString);
429
430 // Check data integrity
431 CPPUNIT_ASSERT_EQUAL(true, aEngine.checkDataIntegrity());
432 }
433 }
434
435 CPPUNIT_TEST_SUITE_REGISTRATION(CryptoTest);
436
437 CPPUNIT_PLUGIN_IMPLEMENT();
438
439 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
440