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