summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>2018-07-04 22:23:44 +0200
committerTomaž Vajngerl <quikee@gmail.com>2018-07-06 18:27:47 +0200
commitce560ee99ebf97fa44aecedd5110b29913cf77a5 (patch)
treee228f26944151c51cafd81ca05654ceeeb624046
parentce7fb7473bc72d8a672c4fdcd49474721c9a2784 (diff)
oox: Agile encryption and data integrity verification
This adds agile encryption for OOXML documents. Previously we always used the standard encryption used in MSO 2007 for max. compatibility, but new MSO versions (2010+) use the agile encryption, which allows more strong encryption methods (AES256 with SHA512). With this change we can now use do AES128 with SHA1 or AES256 with SHA512 encryption. In addition the agile encryption has data verification with HMAC hashing. With this change we also now write the data verification hash into the encrypted document and in addition also do data verification when opening / decrypting a document, so to make sure the document is not corrupted. Change-Id: Ib45d397df228c355941eefb76d51e5d6f8925470 Reviewed-on: https://gerrit.libreoffice.org/56974 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
-rw-r--r--include/oox/crypto/AgileEngine.hxx88
-rw-r--r--include/oox/crypto/CryptoEngine.hxx19
-rw-r--r--include/oox/crypto/Standard2007Engine.hxx18
-rw-r--r--oox/qa/unit/CryptoTest.cxx281
-rw-r--r--oox/source/core/filterdetect.cxx21
-rw-r--r--oox/source/crypto/AgileEngine.cxx481
-rw-r--r--oox/source/crypto/DocumentDecryption.cxx3
-rw-r--r--oox/source/crypto/DocumentEncryption.cxx31
-rw-r--r--oox/source/crypto/Standard2007Engine.cxx39
9 files changed, 885 insertions, 96 deletions
diff --git a/include/oox/crypto/AgileEngine.hxx b/include/oox/crypto/AgileEngine.hxx
index dc7538fafde4..8d4183619f9c 100644
--- a/include/oox/crypto/AgileEngine.hxx
+++ b/include/oox/crypto/AgileEngine.hxx
@@ -13,6 +13,7 @@
#include <vector>
+#include <oox/dllapi.h>
#include <oox/crypto/CryptTools.hxx>
#include <oox/crypto/CryptoEngine.hxx>
#include <rtl/ustring.hxx>
@@ -26,7 +27,7 @@ namespace oox {
namespace oox {
namespace core {
-struct AgileEncryptionInfo
+struct OOX_DLLPUBLIC AgileEncryptionInfo
{
sal_Int32 spinCount;
sal_Int32 saltSize;
@@ -39,15 +40,45 @@ struct AgileEncryptionInfo
OUString hashAlgorithm;
std::vector<sal_uInt8> keyDataSalt;
+
+ // Key Encryptor
std::vector<sal_uInt8> saltValue;
std::vector<sal_uInt8> encryptedVerifierHashInput;
std::vector<sal_uInt8> encryptedVerifierHashValue;
std::vector<sal_uInt8> encryptedKeyValue;
+
+ // HMAC
+ std::vector<sal_uInt8> hmacKey;
+ std::vector<sal_uInt8> hmacHash;
+ std::vector<sal_uInt8> hmacCalculatedHash;
+ std::vector<sal_uInt8> hmacEncryptedKey; // encrypted Key
+ std::vector<sal_uInt8> hmacEncryptedValue; // encrypted Hash
+};
+
+struct OOX_DLLPUBLIC AgileEncryptionParameters
+{
+ sal_Int32 spinCount;
+ sal_Int32 saltSize;
+ sal_Int32 keyBits;
+ sal_Int32 hashSize;
+ sal_Int32 blockSize;
+
+ OUString cipherAlgorithm;
+ OUString cipherChaining;
+ OUString hashAlgorithm;
};
-class AgileEngine : public CryptoEngine
+enum class AgileEncryptionPreset
{
+ AES_128_SHA1,
+ AES_256_SHA512,
+};
+
+class OOX_DLLPUBLIC AgileEngine : public CryptoEngine
+{
+private:
AgileEncryptionInfo mInfo;
+ AgileEncryptionPreset meEncryptionPreset;
void calculateHashFinal(const OUString& rPassword, std::vector<sal_uInt8>& aHashFinal);
@@ -57,28 +88,59 @@ class AgileEngine : public CryptoEngine
std::vector<sal_uInt8>& rInput,
std::vector<sal_uInt8>& rOutput);
+ void encryptBlock(
+ std::vector<sal_uInt8> const & rBlock,
+ std::vector<sal_uInt8>& rHashFinal,
+ std::vector<sal_uInt8>& rInput,
+ std::vector<sal_uInt8>& rOutput);
+
static Crypto::CryptoType cryptoType(const AgileEncryptionInfo& rInfo);
+ bool calculateDecryptionKey(const OUString& rPassword);
+
public:
- AgileEngine() = default;
+ AgileEngine();
AgileEncryptionInfo& getInfo() { return mInfo;}
- virtual void writeEncryptionInfo(
- const OUString& rPassword,
- BinaryXOutputStream& rStream) override;
+ void setPreset(AgileEncryptionPreset ePreset)
+ {
+ meEncryptionPreset = ePreset;
+ }
- virtual bool generateEncryptionKey(const OUString& rPassword) override;
+ // Decryption
- virtual bool decrypt(
- BinaryXInputStream& aInputStream,
- BinaryXOutputStream& aOutputStream) override;
+ bool decryptEncryptionKey(OUString const & rPassword);
+ bool decryptAndCheckVerifierHash(OUString const & rPassword);
+ bool generateEncryptionKey(OUString const & rPassword) override;
bool readEncryptionInfo(css::uno::Reference<css::io::XInputStream> & rxInputStream) override;
+ bool decrypt(BinaryXInputStream& aInputStream,
+ BinaryXOutputStream& aOutputStream) override;
+
+ bool checkDataIntegrity() override;
+
+ bool decryptHmacKey();
+ bool decryptHmacValue();
+
+ // Encryption
+
+ void writeEncryptionInfo(BinaryXOutputStream& rStream) override;
+
+ void encrypt(css::uno::Reference<css::io::XInputStream>& rxInputStream,
+ css::uno::Reference<css::io::XOutputStream>& rxOutputStream,
+ sal_uInt32 nSize) override;
+
+ bool setupEncryption(OUString const & rPassword) override;
+
+ bool generateAndEncryptVerifierHash(OUString const & rPassword);
+
+ bool encryptHmacKey();
+ bool encryptHmacValue();
- virtual void encrypt(
- BinaryXInputStream& aInputStream,
- BinaryXOutputStream& aOutputStream) override;
+ bool encryptEncryptionKey(OUString const & rPassword);
+ void setupEncryptionParameters(AgileEncryptionParameters const & rAgileEncryptionParameters);
+ bool setupEncryptionKey(OUString const & rPassword);
};
} // namespace core
diff --git a/include/oox/crypto/CryptoEngine.hxx b/include/oox/crypto/CryptoEngine.hxx
index 0b6844671457..8a947f10d106 100644
--- a/include/oox/crypto/CryptoEngine.hxx
+++ b/include/oox/crypto/CryptoEngine.hxx
@@ -17,6 +17,7 @@
#include <sal/types.h>
#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
namespace oox {
class BinaryXInputStream;
@@ -38,9 +39,8 @@ public:
virtual ~CryptoEngine()
{}
- virtual void writeEncryptionInfo(
- const OUString& rPassword,
- BinaryXOutputStream& rStream) = 0;
+ // Decryption
+ virtual bool readEncryptionInfo(css::uno::Reference<css::io::XInputStream> & rxInputStream) = 0;
virtual bool generateEncryptionKey(const OUString& rPassword) = 0;
@@ -48,11 +48,16 @@ public:
BinaryXInputStream& aInputStream,
BinaryXOutputStream& aOutputStream) = 0;
- virtual bool readEncryptionInfo(css::uno::Reference<css::io::XInputStream> & rxInputStream) = 0;
+ // Encryption
+ virtual void writeEncryptionInfo(BinaryXOutputStream & rStream) = 0;
- virtual void encrypt(
- BinaryXInputStream& aInputStream,
- BinaryXOutputStream& aOutputStream) = 0;
+ virtual bool setupEncryption(const OUString& rPassword) = 0;
+
+ virtual void encrypt(css::uno::Reference<css::io::XInputStream> & rxInputStream,
+ css::uno::Reference<css::io::XOutputStream> & rxOutputStream,
+ sal_uInt32 nSize) = 0;
+
+ virtual bool checkDataIntegrity() = 0;
};
} // namespace core
diff --git a/include/oox/crypto/Standard2007Engine.hxx b/include/oox/crypto/Standard2007Engine.hxx
index 0ad7a21eec3b..996467ddde82 100644
--- a/include/oox/crypto/Standard2007Engine.hxx
+++ b/include/oox/crypto/Standard2007Engine.hxx
@@ -38,21 +38,23 @@ public:
msfilter::StandardEncryptionInfo& getInfo() { return mInfo;}
- virtual bool generateEncryptionKey(const OUString& rPassword) override;
+ bool readEncryptionInfo(css::uno::Reference<css::io::XInputStream> & rxInputStream) override;
- virtual void writeEncryptionInfo(
- const OUString& rPassword,
- BinaryXOutputStream& rStream) override;
+ virtual bool generateEncryptionKey(OUString const & rPassword) override;
virtual bool decrypt(
BinaryXInputStream& aInputStream,
BinaryXOutputStream& aOutputStream) override;
- bool readEncryptionInfo(css::uno::Reference<css::io::XInputStream> & rxInputStream) override;
+ bool checkDataIntegrity() override;
- virtual void encrypt(
- BinaryXInputStream& aInputStream,
- BinaryXOutputStream& aOutputStream) override;
+ void encrypt(css::uno::Reference<css::io::XInputStream>& rxInputStream,
+ css::uno::Reference<css::io::XOutputStream>& rxOutputStream,
+ sal_uInt32 nSize) override;
+
+ virtual void writeEncryptionInfo(BinaryXOutputStream& rStream) override;
+
+ virtual bool setupEncryption(OUString const & rPassword) override;
};
diff --git a/oox/qa/unit/CryptoTest.cxx b/oox/qa/unit/CryptoTest.cxx
index 0dead9dcec6e..e17f3cc91e9a 100644
--- a/oox/qa/unit/CryptoTest.cxx
+++ b/oox/qa/unit/CryptoTest.cxx
@@ -15,8 +15,8 @@
#include <tools/stream.hxx>
#include <unotools/streamwrap.hxx>
-#include <oox/crypto/CryptTools.hxx>
#include <oox/crypto/Standard2007Engine.hxx>
+#include <oox/crypto/AgileEngine.hxx>
#include <oox/helper/binaryinputstream.hxx>
#include <oox/helper/binaryoutputstream.hxx>
@@ -28,11 +28,19 @@ public:
void testCryptoHash();
void testRoundUp();
void testStandard2007();
+ void testAgileEncryptionVerifier();
+ void testAgileEncrpytionInfoWritingAndParsing();
+ void testAgileDataIntegrityHmacKey();
+ void testAgileEncryptingAndDecrypting();
CPPUNIT_TEST_SUITE(CryptoTest);
CPPUNIT_TEST(testCryptoHash);
CPPUNIT_TEST(testRoundUp);
CPPUNIT_TEST(testStandard2007);
+ CPPUNIT_TEST(testAgileEncryptionVerifier);
+ CPPUNIT_TEST(testAgileEncrpytionInfoWritingAndParsing);
+ CPPUNIT_TEST(testAgileDataIntegrityHmacKey);
+ CPPUNIT_TEST(testAgileEncryptingAndDecrypting);
CPPUNIT_TEST_SUITE_END();
};
@@ -100,11 +108,13 @@ void CryptoTest::testStandard2007()
{
oox::core::Standard2007Engine aEngine;
{
+ aEngine.setupEncryption("Password");
+
SvMemoryStream aEncryptionInfo;
oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), false);
- aEngine.writeEncryptionInfo("Password", aBinaryEncryptionInfoOutputStream);
+ aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
aBinaryEncryptionInfoOutputStream.close();
CPPUNIT_ASSERT_EQUAL(sal_uInt64(224), aEncryptionInfo.GetSize());
@@ -119,17 +129,14 @@ void CryptoTest::testStandard2007()
aUnencryptedInput.Seek(STREAM_SEEK_TO_BEGIN);
{
- oox::BinaryXInputStream aBinaryInputStream(
- new utl::OSeekableInputStreamWrapper(aUnencryptedInput), true);
- oox::BinaryXOutputStream aBinaryOutputStream(
- new utl::OSeekableOutputStreamWrapper(aEncryptedStream), true);
+ uno::Reference<io::XInputStream> xInputStream(
+ new utl::OSeekableInputStreamWrapper(aUnencryptedInput));
+ uno::Reference<io::XOutputStream> xOutputStream(
+ new utl::OSeekableOutputStreamWrapper(aEncryptedStream));
- aEncryptedStream.WriteUInt32(aUnencryptedInput.GetSize());
- aEncryptedStream.WriteUInt32(0U);
+ aEngine.encrypt(xInputStream, xOutputStream, aUnencryptedInput.GetSize());
- aEngine.encrypt(aBinaryInputStream, aBinaryOutputStream);
- aBinaryOutputStream.close();
- aBinaryInputStream.close();
+ xOutputStream->flush();
const sal_uInt8* pData = static_cast<const sal_uInt8*>(aEncryptedStream.GetData());
sal_uInt64 nSize = aEncryptedStream.GetSize();
@@ -164,6 +171,258 @@ void CryptoTest::testStandard2007()
}
}
+void CryptoTest::testAgileEncryptionVerifier()
+{
+ oox::core::AgileEngine aEngine;
+
+ OUString aPassword("Password");
+
+ aEngine.setupEncryptionParameters({ 100000, 16, 128, 20, 16, OUString("AES"),
+ OUString("ChainingModeCBC"), OUString("SHA1") });
+
+ CPPUNIT_ASSERT_EQUAL(true, aEngine.generateAndEncryptVerifierHash(aPassword));
+ CPPUNIT_ASSERT_EQUAL(false, aEngine.decryptAndCheckVerifierHash("Wrong"));
+ CPPUNIT_ASSERT_EQUAL(true, aEngine.decryptAndCheckVerifierHash(aPassword));
+
+ aEngine.setupEncryptionParameters({ 100000, 16, 256, 64, 16, OUString("AES"),
+ OUString("ChainingModeCBC"), OUString("SHA512") });
+
+ CPPUNIT_ASSERT_EQUAL(true, aEngine.generateAndEncryptVerifierHash(aPassword));
+ CPPUNIT_ASSERT_EQUAL(false, aEngine.decryptAndCheckVerifierHash("Wrong"));
+ CPPUNIT_ASSERT_EQUAL(true, aEngine.decryptAndCheckVerifierHash(aPassword));
+}
+
+void CryptoTest::testAgileEncrpytionInfoWritingAndParsing()
+{
+ OUString aPassword("Password");
+ std::vector<sal_uInt8> aKeyDataSalt;
+
+ { // Preset AES128 - SHA1
+ SvMemoryStream aEncryptionInfo;
+ {
+ oox::core::AgileEngine aEngine;
+
+ aEngine.setPreset(oox::core::AgileEncryptionPreset::AES_128_SHA1);
+ aEngine.setupEncryption(aPassword);
+ aKeyDataSalt = aEngine.getInfo().keyDataSalt;
+
+ oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
+ new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), true);
+
+ aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
+ aBinaryEncryptionInfoOutputStream.close();
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(996), aEncryptionInfo.GetSize());
+ }
+
+ aEncryptionInfo.Seek(STREAM_SEEK_TO_BEGIN);
+
+ {
+ oox::core::AgileEngine aEngine;
+
+ uno::Reference<io::XInputStream> xInputStream(
+ new utl::OSeekableInputStreamWrapper(aEncryptionInfo));
+
+ xInputStream->skipBytes(4); // Encryption type -> Agile
+ xInputStream->skipBytes(4); // Reserved
+
+ CPPUNIT_ASSERT(aEngine.readEncryptionInfo(xInputStream));
+
+ oox::core::AgileEncryptionInfo& rInfo = aEngine.getInfo();
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(100000), rInfo.spinCount);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(16), rInfo.saltSize);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(128), rInfo.keyBits);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(20), rInfo.hashSize);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(16), rInfo.blockSize);
+ CPPUNIT_ASSERT_EQUAL(OUString("AES"), rInfo.cipherAlgorithm);
+ CPPUNIT_ASSERT_EQUAL(OUString("ChainingModeCBC"), rInfo.cipherChaining);
+ CPPUNIT_ASSERT_EQUAL(OUString("SHA1"), rInfo.hashAlgorithm);
+ CPPUNIT_ASSERT_EQUAL(toString(aKeyDataSalt), toString(rInfo.keyDataSalt));
+
+ CPPUNIT_ASSERT_EQUAL(false, aEngine.decryptAndCheckVerifierHash("Wrong"));
+ CPPUNIT_ASSERT_EQUAL(true, aEngine.decryptAndCheckVerifierHash(aPassword));
+ }
+ }
+
+ { // Preset AES256 - SHA512
+ SvMemoryStream aEncryptionInfo;
+ {
+ oox::core::AgileEngine aEngine;
+
+ aEngine.setPreset(oox::core::AgileEncryptionPreset::AES_256_SHA512);
+ aEngine.setupEncryption(aPassword);
+ aKeyDataSalt = aEngine.getInfo().keyDataSalt;
+
+ oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
+ new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), true);
+
+ aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
+ aBinaryEncryptionInfoOutputStream.close();
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(1112), aEncryptionInfo.GetSize());
+ }
+
+ aEncryptionInfo.Seek(STREAM_SEEK_TO_BEGIN);
+
+ {
+ oox::core::AgileEngine aEngine;
+
+ uno::Reference<io::XInputStream> xInputStream(
+ new utl::OSeekableInputStreamWrapper(aEncryptionInfo));
+
+ xInputStream->skipBytes(4); // Encryption type -> Agile
+ xInputStream->skipBytes(4); // Reserved
+
+ CPPUNIT_ASSERT(aEngine.readEncryptionInfo(xInputStream));
+
+ oox::core::AgileEncryptionInfo& rInfo = aEngine.getInfo();
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(100000), rInfo.spinCount);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(16), rInfo.saltSize);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(256), rInfo.keyBits);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(64), rInfo.hashSize);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(16), rInfo.blockSize);
+ CPPUNIT_ASSERT_EQUAL(OUString("AES"), rInfo.cipherAlgorithm);
+ CPPUNIT_ASSERT_EQUAL(OUString("ChainingModeCBC"), rInfo.cipherChaining);
+ CPPUNIT_ASSERT_EQUAL(OUString("SHA512"), rInfo.hashAlgorithm);
+ CPPUNIT_ASSERT_EQUAL(toString(aKeyDataSalt), toString(rInfo.keyDataSalt));
+
+ CPPUNIT_ASSERT_EQUAL(false, aEngine.decryptAndCheckVerifierHash("Wrong"));
+ CPPUNIT_ASSERT_EQUAL(true, aEngine.decryptAndCheckVerifierHash(aPassword));
+ }
+ }
+}
+
+void CryptoTest::testAgileDataIntegrityHmacKey()
+{
+ OUString aPassword("Password");
+
+ std::vector<sal_uInt8> aKeyDataSalt;
+
+ std::vector<sal_uInt8> aHmacKey;
+ std::vector<sal_uInt8> aHmacEncryptedKey;
+
+ SvMemoryStream aEncryptionInfo;
+ {
+ oox::core::AgileEngine aEngine;
+ aEngine.setupEncryption(aPassword);
+ oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
+ new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), true);
+ aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
+ aBinaryEncryptionInfoOutputStream.close();
+
+ aHmacKey = aEngine.getInfo().hmacKey;
+ aKeyDataSalt = aEngine.getInfo().keyDataSalt;
+ aHmacEncryptedKey = aEngine.getInfo().hmacEncryptedKey;
+ }
+
+ aEncryptionInfo.Seek(STREAM_SEEK_TO_BEGIN);
+
+ {
+ oox::core::AgileEngine aEngine;
+
+ uno::Reference<io::XInputStream> xInputStream(
+ new utl::OSeekableInputStreamWrapper(aEncryptionInfo));
+
+ xInputStream->skipBytes(4); // Encryption type -> Agile
+ xInputStream->skipBytes(4); // Reserved
+
+ CPPUNIT_ASSERT(aEngine.readEncryptionInfo(xInputStream));
+ CPPUNIT_ASSERT(aEngine.generateEncryptionKey(aPassword));
+
+ CPPUNIT_ASSERT_EQUAL(toString(aKeyDataSalt), toString(aEngine.getInfo().keyDataSalt));
+
+ CPPUNIT_ASSERT_EQUAL(toString(aHmacEncryptedKey),
+ toString(aEngine.getInfo().hmacEncryptedKey));
+
+ CPPUNIT_ASSERT_EQUAL(size_t(64), aHmacKey.size());
+ CPPUNIT_ASSERT_EQUAL(toString(aHmacKey), toString(aEngine.getInfo().hmacKey));
+ }
+}
+
+void CryptoTest::testAgileEncryptingAndDecrypting()
+{
+ OUString aPassword("Password");
+
+ SvMemoryStream aEncryptionInfo;
+ SvMemoryStream aEncryptedStream;
+
+ OString aTestString = OUStringToOString("1234567890ABCDEFGH", RTL_TEXTENCODING_UTF8);
+
+ {
+ oox::core::AgileEngine aEngine;
+
+ // Setup input
+ SvMemoryStream aUnencryptedInput;
+ uno::Reference<io::XInputStream> xInputStream(
+ new utl::OSeekableInputStreamWrapper(aUnencryptedInput));
+
+ aUnencryptedInput.WriteBytes(aTestString.getStr(), aTestString.getLength() + 1);
+ aUnencryptedInput.Seek(STREAM_SEEK_TO_BEGIN);
+
+ // Setup output
+ uno::Reference<io::XOutputStream> xOutputStream(
+ new utl::OSeekableOutputStreamWrapper(aEncryptedStream));
+
+ // Write content
+ aEngine.setupEncryption(aPassword);
+ aEngine.encrypt(xInputStream, xOutputStream, aUnencryptedInput.GetSize());
+ xOutputStream->flush();
+
+ // Check content
+ sal_uInt64 nSize = aEncryptedStream.GetSize();
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(40), nSize);
+
+ // Setup and write encryption info
+ oox::BinaryXOutputStream aBinaryEncryptionInfoOutputStream(
+ new utl::OSeekableOutputStreamWrapper(aEncryptionInfo), true);
+ aEngine.writeEncryptionInfo(aBinaryEncryptionInfoOutputStream);
+ aBinaryEncryptionInfoOutputStream.close();
+ }
+
+ aEncryptedStream.Seek(STREAM_SEEK_TO_BEGIN);
+ aEncryptionInfo.Seek(STREAM_SEEK_TO_BEGIN);
+
+ {
+ oox::core::AgileEngine aEngine;
+
+ // Read encryption info
+ uno::Reference<io::XInputStream> xEncryptionInfo(
+ new utl::OSeekableInputStreamWrapper(aEncryptionInfo));
+
+ xEncryptionInfo->skipBytes(4); // Encryption type -> Agile
+ xEncryptionInfo->skipBytes(4); // Reserved
+
+ CPPUNIT_ASSERT(aEngine.readEncryptionInfo(xEncryptionInfo));
+
+ // Setup password
+ CPPUNIT_ASSERT(aEngine.generateEncryptionKey(aPassword));
+
+ // Setup encrypted input stream
+ oox::BinaryXInputStream aBinaryInputStream(
+ new utl::OSeekableInputStreamWrapper(aEncryptedStream), true);
+
+ // Setup output stream
+ SvMemoryStream aUnencryptedOutput;
+ oox::BinaryXOutputStream aBinaryOutputStream(
+ new utl::OSeekableOutputStreamWrapper(aUnencryptedOutput), true);
+
+ // Decrypt
+ aEngine.decrypt(aBinaryInputStream, aBinaryOutputStream);
+ aBinaryOutputStream.close();
+ aBinaryInputStream.close();
+
+ // Check decrypted output
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(19), aUnencryptedOutput.GetSize());
+
+ OString aString(static_cast<const sal_Char*>(aUnencryptedOutput.GetData()));
+ CPPUNIT_ASSERT_EQUAL(aTestString, aString);
+
+ // Check data integrity
+ CPPUNIT_ASSERT_EQUAL(true, aEngine.checkDataIntegrity());
+ }
+}
+
CPPUNIT_TEST_SUITE_REGISTRATION(CryptoTest);
CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/oox/source/core/filterdetect.cxx b/oox/source/core/filterdetect.cxx
index 1af982c07480..805d2fb4b01d 100644
--- a/oox/source/core/filterdetect.cxx
+++ b/oox/source/core/filterdetect.cxx
@@ -348,14 +348,21 @@ Reference< XInputStream > FilterDetect::extractUnencryptedPackage( MediaDescript
{
// create temporary file for unencrypted package
Reference<XStream> xTempFile( TempFile::create(mxContext), UNO_QUERY_THROW );
- aDecryptor.decrypt( xTempFile );
- // store temp file in media descriptor to keep it alive
- rMediaDescriptor.setComponentDataEntry( "DecryptedPackage", Any( xTempFile ) );
-
- Reference<XInputStream> xDecryptedInputStream = xTempFile->getInputStream();
- if( lclIsZipPackage( mxContext, xDecryptedInputStream ) )
- return xDecryptedInputStream;
+ // if decryption was unsuccessful (corrupted file or any other reason)
+ if (!aDecryptor.decrypt(xTempFile))
+ {
+ rMediaDescriptor[ MediaDescriptor::PROP_ABORTED() ] <<= true;
+ }
+ else
+ {
+ // store temp file in media descriptor to keep it alive
+ rMediaDescriptor.setComponentDataEntry( "DecryptedPackage", Any( xTempFile ) );
+
+ Reference<XInputStream> xDecryptedInputStream = xTempFile->getInputStream();
+ if( lclIsZipPackage( mxContext, xDecryptedInputStream ) )
+ return xDecryptedInputStream;
+ }
}
}
}
diff --git a/oox/source/crypto/AgileEngine.cxx b/oox/source/crypto/AgileEngine.cxx
index 72cd51e16986..a4fa8c476c74 100644
--- a/oox/source/crypto/AgileEngine.cxx
+++ b/oox/source/crypto/AgileEngine.cxx
@@ -23,6 +23,7 @@
#include <comphelper/sequence.hxx>
#include <filter/msfilter/mscodec.hxx>
+#include <tools/XmlWriter.hxx>
#include <com/sun/star/io/XSeekable.hpp>
#include <com/sun/star/io/XStream.hpp>
@@ -146,6 +147,18 @@ public:
comphelper::Base64::decode(encryptedKeyValue, rAttribute.Value);
mInfo.encryptedKeyValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedKeyValue);
}
+ if (rAttrLocalName == "encryptedHmacKey")
+ {
+ Sequence<sal_Int8> aValue;
+ comphelper::Base64::decode(aValue, rAttribute.Value);
+ mInfo.hmacEncryptedKey = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(aValue);
+ }
+ if (rAttrLocalName == "encryptedHmacValue")
+ {
+ Sequence<sal_Int8> aValue;
+ comphelper::Base64::decode(aValue, rAttribute.Value);
+ mInfo.hmacEncryptedValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(aValue);
+ }
}
}
@@ -168,7 +181,13 @@ public:
{}
};
-const sal_uInt32 constSegmentLength = 4096;
+constexpr const sal_uInt32 constSegmentLength = 4096;
+
+static const std::vector<sal_uInt8> constBlock1 { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 };
+static const std::vector<sal_uInt8> constBlock2 { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e };
+static const std::vector<sal_uInt8> constBlock3 { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 };
+static const std::vector<sal_uInt8> constBlockHmac1 { 0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6 };
+static const std::vector<sal_uInt8> constBlockHmac2 { 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33 };
bool hashCalc(std::vector<sal_uInt8>& output,
std::vector<sal_uInt8>& input,
@@ -189,8 +208,19 @@ bool hashCalc(std::vector<sal_uInt8>& output,
return false;
}
+CryptoHashType cryptoHashTypeFromString(OUString const & sAlgorithm)
+{
+ if (sAlgorithm == "SHA512")
+ return CryptoHashType::SHA512;
+ return CryptoHashType::SHA1;
+}
+
} // namespace
+AgileEngine::AgileEngine()
+ : meEncryptionPreset(AgileEncryptionPreset::AES_256_SHA512)
+{}
+
Crypto::CryptoType AgileEngine::cryptoType(const AgileEncryptionInfo& rInfo)
{
if (rInfo.keyBits == 128 && rInfo.cipherAlgorithm == "AES" && rInfo.cipherChaining == "ChainingModeCBC")
@@ -200,6 +230,19 @@ Crypto::CryptoType AgileEngine::cryptoType(const AgileEncryptionInfo& rInfo)
return Crypto::UNKNOWN;
}
+std::vector<sal_uInt8> calculateIV(comphelper::HashType eType,
+ std::vector<sal_uInt8> const & rSalt,
+ std::vector<sal_uInt8> const & rBlock,
+ sal_Int32 nCipherBlockSize)
+{
+ comphelper::Hash aHasher(eType);
+ aHasher.update(rSalt.data(), rSalt.size());
+ aHasher.update(rBlock.data(), rBlock.size());
+ std::vector<sal_uInt8> aIV = aHasher.finalize();
+ aIV.resize(roundUp(sal_Int32(aIV.size()), nCipherBlockSize), 0x36);
+ return aIV;
+}
+
void AgileEngine::calculateBlock(
std::vector<sal_uInt8> const & rBlock,
std::vector<sal_uInt8>& rHashFinal,
@@ -222,21 +265,55 @@ void AgileEngine::calculateBlock(
aDecryptor.update(rOutput, rInput);
}
+void AgileEngine::encryptBlock(
+ std::vector<sal_uInt8> const & rBlock,
+ std::vector<sal_uInt8> & rHashFinal,
+ std::vector<sal_uInt8> & rInput,
+ std::vector<sal_uInt8> & rOutput)
+{
+ std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
+ std::vector<sal_uInt8> dataFinal(mInfo.hashSize + rBlock.size(), 0);
+ std::copy(rHashFinal.begin(), rHashFinal.end(), dataFinal.begin());
+ std::copy(rBlock.begin(), rBlock.end(), dataFinal.begin() + mInfo.hashSize);
+
+ hashCalc(hash, dataFinal, mInfo.hashAlgorithm);
+
+ sal_Int32 keySize = mInfo.keyBits / 8;
+ std::vector<sal_uInt8> key(keySize, 0);
+
+ std::copy(hash.begin(), hash.begin() + keySize, key.begin());
+
+ Encrypt aEncryptor(key, mInfo.saltValue, cryptoType(mInfo));
+
+ aEncryptor.update(rOutput, rInput);
+}
+
void AgileEngine::calculateHashFinal(const OUString& rPassword, std::vector<sal_uInt8>& aHashFinal)
{
- aHashFinal = comphelper::DocPasswordHelper::GetOoxHashAsVector( rPassword, mInfo.saltValue,
- mInfo.spinCount, comphelper::Hash::IterCount::PREPEND, mInfo.hashAlgorithm);
+ aHashFinal = comphelper::DocPasswordHelper::GetOoxHashAsVector(
+ rPassword, mInfo.saltValue, mInfo.spinCount,
+ comphelper::Hash::IterCount::PREPEND, mInfo.hashAlgorithm);
}
-bool AgileEngine::generateEncryptionKey(const OUString& rPassword)
+namespace
{
- static const std::vector<sal_uInt8> constBlock1{ 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 };
- static const std::vector<sal_uInt8> constBlock2{ 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e };
- static const std::vector<sal_uInt8> constBlock3{ 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 };
- mKey.clear();
- mKey.resize(mInfo.keyBits / 8, 0);
+bool generateBytes(std::vector<sal_uInt8> & rBytes, sal_Int32 nSize)
+{
+ size_t nMax = std::min(rBytes.size(), size_t(nSize));
+
+ for (size_t i = 0; i < nMax; ++i)
+ {
+ rBytes[i] = sal_uInt8(comphelper::rng::uniform_uint_distribution(0, 0xFF));
+ }
+ return true;
+}
+
+} // end anonymous namespace
+
+bool AgileEngine::decryptAndCheckVerifierHash(OUString const & rPassword)
+{
std::vector<sal_uInt8> hashFinal(mInfo.hashSize, 0);
calculateHashFinal(rPassword, hashFinal);
@@ -251,22 +328,114 @@ bool AgileEngine::generateEncryptionKey(const OUString& rPassword)
std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
hashCalc(hash, hashInput, mInfo.hashAlgorithm);
- if (hash.size() <= hashValue.size() && std::equal(hash.begin(), hash.end(), hashValue.begin()))
+ return (hash.size() <= hashValue.size() && std::equal(hash.begin(), hash.end(), hashValue.begin()));
+}
+
+bool AgileEngine::decryptEncryptionKey(OUString const & rPassword)
+{
+ sal_Int32 nKeySize = mInfo.keyBits / 8;
+
+ mKey.clear();
+ mKey.resize(nKeySize, 0);
+
+ std::vector<sal_uInt8> aPasswordHash(mInfo.hashSize, 0);
+ calculateHashFinal(rPassword, aPasswordHash);
+
+ calculateBlock(constBlock3, aPasswordHash, mInfo.encryptedKeyValue, mKey);
+
+ return true;
+}
+
+// TODO: Rename
+bool AgileEngine::generateEncryptionKey(OUString const & rPassword)
+{
+ bool bResult = decryptAndCheckVerifierHash(rPassword);
+
+ if (bResult)
{
- std::vector<sal_uInt8>& encryptedKeyValue = mInfo.encryptedKeyValue;
- calculateBlock(constBlock3, hashFinal, encryptedKeyValue, mKey);
- return true;
+ decryptEncryptionKey(rPassword);
+ decryptHmacKey();
+ decryptHmacValue();
}
+ return bResult;
+}
- return false;
+bool AgileEngine::decryptHmacKey()
+{
+ // Initialize hmacKey
+ mInfo.hmacKey.clear();
+ mInfo.hmacKey.resize(mInfo.hmacEncryptedKey.size(), 0);
+
+ // Calculate IV
+ comphelper::HashType eType;
+ if (mInfo.hashAlgorithm == "SHA1")
+ eType = comphelper::HashType::SHA1;
+ else if (mInfo.hashAlgorithm == "SHA512")
+ eType = comphelper::HashType::SHA512;
+ else
+ return false;
+
+ std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac1, mInfo.blockSize);
+
+ // Decrypt with out key, calculated iv
+ Decrypt aDecrypt(mKey, iv, cryptoType(mInfo));
+ aDecrypt.update(mInfo.hmacKey, mInfo.hmacEncryptedKey);
+
+ mInfo.hmacKey.resize(mInfo.hashSize, 0);
+
+ return true;
+}
+
+bool AgileEngine::decryptHmacValue()
+{
+ // Initialize hmacHash
+ mInfo.hmacHash.clear();
+ mInfo.hmacHash.resize(mInfo.hmacEncryptedValue.size(), 0);
+
+ // Calculate IV
+ comphelper::HashType eType;
+ if (mInfo.hashAlgorithm == "SHA1")
+ eType = comphelper::HashType::SHA1;
+ else if (mInfo.hashAlgorithm == "SHA512")
+ eType = comphelper::HashType::SHA512;
+ else
+ return false;
+ std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac2, mInfo.blockSize);
+
+ // Decrypt with out key, calculated iv
+ Decrypt aDecrypt(mKey, iv, cryptoType(mInfo));
+ aDecrypt.update(mInfo.hmacHash, mInfo.hmacEncryptedValue);
+
+ mInfo.hmacHash.resize(mInfo.hashSize, 0);
+
+ return true;
+}
+
+bool AgileEngine::checkDataIntegrity()
+{
+ bool bResult = (mInfo.hmacHash.size() == mInfo.hmacCalculatedHash.size() &&
+ std::equal(mInfo.hmacHash.begin(), mInfo.hmacHash.end(), mInfo.hmacCalculatedHash.begin()));
+
+ return bResult;
}
bool AgileEngine::decrypt(BinaryXInputStream& aInputStream,
BinaryXOutputStream& aOutputStream)
{
+ CryptoHash aCryptoHash(mInfo.hmacKey, cryptoHashTypeFromString(mInfo.hashAlgorithm));
+
sal_uInt32 totalSize = aInputStream.readuInt32(); // Document unencrypted size - 4 bytes
+ // account for size in HMAC
+ std::vector<sal_uInt8> aSizeBytes(sizeof(sal_uInt32));
+ ByteOrderConverter::writeLittleEndian(aSizeBytes.data(), totalSize);
+ aCryptoHash.update(aSizeBytes);
+
aInputStream.skip(4); // Reserved 4 Bytes
+ // accout for reserved 4 bytes (must be 0)
+ std::vector<sal_uInt8> aReserved{0,0,0,0};
+ aCryptoHash.update(aReserved);
+ // setup decryption
std::vector<sal_uInt8>& keyDataSalt = mInfo.keyDataSalt;
sal_uInt32 saltSize = mInfo.saltSize;
@@ -286,7 +455,7 @@ bool AgileEngine::decrypt(BinaryXInputStream& aInputStream,
sal_uInt32 outputLength;
sal_uInt32 remaining = totalSize;
- while ((inputLength = aInputStream.readMemory(inputBuffer.data(), constSegmentLength)) > 0)
+ while ((inputLength = aInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
{
sal_uInt8* segmentBegin = reinterpret_cast<sal_uInt8*>(&segment);
sal_uInt8* segmentEnd = segmentBegin + sizeof(segment);
@@ -301,12 +470,17 @@ bool AgileEngine::decrypt(BinaryXInputStream& aInputStream,
outputLength = aDecryptor.update(outputBuffer, inputBuffer, inputLength);
sal_uInt32 writeLength = std::min(outputLength, remaining);
+
+ aCryptoHash.update(inputBuffer, inputLength);
+
aOutputStream.writeMemory(outputBuffer.data(), writeLength);
remaining -= outputLength;
segment++;
}
+ mInfo.hmacCalculatedHash = aCryptoHash.finalize();
+
return true;
}
@@ -363,18 +537,279 @@ bool AgileEngine::readEncryptionInfo(uno::Reference<io::XInputStream> & rxInputS
return false;
}
-void AgileEngine::writeEncryptionInfo(
- const OUString& /*aPassword*/,
- BinaryXOutputStream& /*rStream*/)
+bool AgileEngine::generateAndEncryptVerifierHash(OUString const & rPassword)
+{
+ if (!generateBytes(mInfo.saltValue, mInfo.saltSize))
+ return false;
+
+ std::vector<sal_uInt8> unencryptedVerifierHashInput(mInfo.saltSize);
+ if (!generateBytes(unencryptedVerifierHashInput, mInfo.saltSize))
+ return false;
+
+ // HASH - needs to be modified to be multiple of block size
+ sal_Int32 nVerifierHash = roundUp(mInfo.hashSize, mInfo.blockSize);
+ std::vector<sal_uInt8> unencryptedVerifierHashValue;
+ if (!hashCalc(unencryptedVerifierHashValue, unencryptedVerifierHashInput, mInfo.hashAlgorithm))
+ return false;
+ unencryptedVerifierHashValue.resize(nVerifierHash, 0);
+
+ std::vector<sal_uInt8> hashFinal(mInfo.hashSize, 0);
+ calculateHashFinal(rPassword, hashFinal);
+
+ encryptBlock(constBlock1, hashFinal, unencryptedVerifierHashInput, mInfo.encryptedVerifierHashInput);
+
+ encryptBlock(constBlock2, hashFinal, unencryptedVerifierHashValue, mInfo.encryptedVerifierHashValue);
+
+ return true;
+}
+
+bool AgileEngine::encryptHmacKey()
+{
+ // Initialize hmacKey
+ mInfo.hmacKey.clear();
+ mInfo.hmacKey.resize(mInfo.hashSize, 0);
+
+ if (!generateBytes(mInfo.hmacKey, mInfo.hashSize))
+ return false;
+
+ // Encrypted salt must be multiple of block size
+ sal_Int32 nEncryptedSaltSize = oox::core::roundUp(mInfo.hashSize, mInfo.blockSize);
+
+ // We need to extend hmacSalt to multiple of block size, padding with 0x36
+ std::vector<sal_uInt8> extendedSalt(mInfo.hmacKey);
+ extendedSalt.resize(nEncryptedSaltSize, 0x36);
+
+ // Initialize hmacEncryptedKey
+ mInfo.hmacEncryptedKey.clear();
+ mInfo.hmacEncryptedKey.resize(nEncryptedSaltSize, 0);
+
+ // Calculate IV
+ comphelper::HashType eType;
+ if (mInfo.hashAlgorithm == "SHA1")
+ eType = comphelper::HashType::SHA1;
+ else if (mInfo.hashAlgorithm == "SHA512")
+ eType = comphelper::HashType::SHA512;
+ else
+ return false;
+
+ std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac1, mInfo.blockSize);
+
+ // Encrypt with out key, calculated iv
+ Encrypt aEncryptor(mKey, iv, cryptoType(mInfo));
+ aEncryptor.update(mInfo.hmacEncryptedKey, extendedSalt);
+
+ return true;
+}
+
+bool AgileEngine::encryptHmacValue()
+{
+ sal_Int32 nEncryptedValueSize = roundUp(mInfo.hashSize, mInfo.blockSize);
+ mInfo.hmacEncryptedValue.clear();
+ mInfo.hmacEncryptedValue.resize(nEncryptedValueSize, 0);
+
+ std::vector<sal_uInt8> extendedHash(mInfo.hmacHash);
+ extendedHash.resize(nEncryptedValueSize, 0x36);
+
+ // Calculate IV
+ comphelper::HashType eType;
+ if (mInfo.hashAlgorithm == "SHA1")
+ eType = comphelper::HashType::SHA1;
+ else if (mInfo.hashAlgorithm == "SHA512")
+ eType = comphelper::HashType::SHA512;
+ else
+ return false;
+
+ std::vector<sal_uInt8> iv = calculateIV(eType, mInfo.keyDataSalt, constBlockHmac2, mInfo.blockSize);
+
+ // Encrypt with out key, calculated iv
+ Encrypt aEncryptor(mKey, iv, cryptoType(mInfo));
+ aEncryptor.update(mInfo.hmacEncryptedValue, extendedHash);
+
+ return true;
+}
+
+bool AgileEngine::encryptEncryptionKey(OUString const & rPassword)
+{
+ sal_Int32 nKeySize = mInfo.keyBits / 8;
+
+ mKey.clear();
+ mKey.resize(nKeySize, 0);
+
+ mInfo.encryptedKeyValue.clear();
+ mInfo.encryptedKeyValue.resize(nKeySize, 0);
+
+ if (!generateBytes(mKey, nKeySize))
+ return false;
+
+ std::vector<sal_uInt8> aPasswordHash(mInfo.hashSize, 0);
+ calculateHashFinal(rPassword, aPasswordHash);
+
+ encryptBlock(constBlock3, aPasswordHash, mKey, mInfo.encryptedKeyValue);
+
+ return true;
+}
+
+bool AgileEngine::setupEncryption(OUString const & rPassword)
+{
+ if (meEncryptionPreset == AgileEncryptionPreset::AES_128_SHA1)
+ setupEncryptionParameters({ 100000, 16, 128, 20, 16, OUString("AES"), OUString("ChainingModeCBC"), OUString("SHA1") });
+ else
+ setupEncryptionParameters({ 100000, 16, 256, 64, 16, OUString("AES"), OUString("ChainingModeCBC"), OUString("SHA512") });
+
+ if (!setupEncryptionKey(rPassword))
+ return false;
+ return true;
+}
+
+void AgileEngine::setupEncryptionParameters(AgileEncryptionParameters const & rAgileEncryptionParameters)
+{
+ mInfo.spinCount = rAgileEncryptionParameters.spinCount;
+ mInfo.saltSize = rAgileEncryptionParameters.saltSize;
+ mInfo.keyBits = rAgileEncryptionParameters.keyBits;
+ mInfo.hashSize = rAgileEncryptionParameters.hashSize;
+ mInfo.blockSize = rAgileEncryptionParameters.blockSize;
+
+ mInfo.cipherAlgorithm = rAgileEncryptionParameters.cipherAlgorithm;
+ mInfo.cipherChaining = rAgileEncryptionParameters.cipherChaining;
+ mInfo.hashAlgorithm = rAgileEncryptionParameters.hashAlgorithm;
+
+ mInfo.keyDataSalt.resize(mInfo.saltSize);
+ mInfo.saltValue.resize(mInfo.saltSize);
+ mInfo.encryptedVerifierHashInput.resize(mInfo.saltSize);
+ mInfo.encryptedVerifierHashValue.resize(roundUp(mInfo.hashSize, mInfo.blockSize), 0);
+}
+
+bool AgileEngine::setupEncryptionKey(OUString const & rPassword)
{
- // Agile encrypting is not supported for now
+ if (!generateAndEncryptVerifierHash(rPassword))
+ return false;
+ if (!encryptEncryptionKey(rPassword))
+ return false;
+ if (!generateBytes(mInfo.keyDataSalt, mInfo.saltSize))
+ return false;
+ if (!encryptHmacKey())
+ return false;
+ return true;
}
-void AgileEngine::encrypt(
- BinaryXInputStream& /*aInputStream*/,
- BinaryXOutputStream& /*aOutputStream*/)
+void AgileEngine::writeEncryptionInfo(BinaryXOutputStream & rStream)
{
- // Agile encrypting is not supported for now
+ rStream.WriteUInt32(msfilter::VERSION_INFO_AGILE);
+ rStream.WriteUInt32(0); // reserved
+
+ SvMemoryStream aMemStream;
+ tools::XmlWriter aXmlWriter(&aMemStream);
+
+ if (aXmlWriter.startDocument(0/*nIndent*/))
+ {
+ aXmlWriter.startElement("", "encryption", "http://schemas.microsoft.com/office/2006/encryption");
+ aXmlWriter.attribute("xmlns:p", OString("http://schemas.microsoft.com/office/2006/keyEncryptor/password"));
+
+ aXmlWriter.startElement("keyData");
+ aXmlWriter.attribute("saltSize", mInfo.saltSize);
+ aXmlWriter.attribute("blockSize", mInfo.blockSize);
+ aXmlWriter.attribute("keyBits", mInfo.keyBits);
+ aXmlWriter.attribute("hashSize", mInfo.hashSize);
+ aXmlWriter.attribute("cipherAlgorithm", mInfo.cipherAlgorithm);
+ aXmlWriter.attribute("cipherChaining", mInfo.cipherChaining);
+ aXmlWriter.attribute("hashAlgorithm", mInfo.hashAlgorithm);
+ aXmlWriter.attributeBase64("saltValue", mInfo.keyDataSalt);
+ aXmlWriter.endElement();
+
+ aXmlWriter.startElement("dataIntegrity");
+ aXmlWriter.attributeBase64("encryptedHmacKey", mInfo.hmacEncryptedKey);
+ aXmlWriter.attributeBase64("encryptedHmacValue", mInfo.hmacEncryptedValue);
+ aXmlWriter.endElement();
+
+ aXmlWriter.startElement("keyEncryptors");
+ aXmlWriter.startElement("keyEncryptor");
+ aXmlWriter.attribute("uri", OString("http://schemas.microsoft.com/office/2006/keyEncryptor/password"));
+
+ aXmlWriter.startElement("p", "encryptedKey", "");
+ aXmlWriter.attribute("spinCount", mInfo.spinCount);
+ aXmlWriter.attribute("saltSize", mInfo.saltSize);
+ aXmlWriter.attribute("blockSize", mInfo.blockSize);
+ aXmlWriter.attribute("keyBits", mInfo.keyBits);
+ aXmlWriter.attribute("hashSize", mInfo.hashSize);
+ aXmlWriter.attribute("cipherAlgorithm", mInfo.cipherAlgorithm);
+ aXmlWriter.attribute("cipherChaining", mInfo.cipherChaining);
+ aXmlWriter.attribute("hashAlgorithm", mInfo.hashAlgorithm);
+ aXmlWriter.attributeBase64("saltValue", mInfo.saltValue);
+ aXmlWriter.attributeBase64("encryptedVerifierHashInput", mInfo.encryptedVerifierHashInput);
+ aXmlWriter.attributeBase64("encryptedVerifierHashValue", mInfo.encryptedVerifierHashValue);
+ aXmlWriter.attributeBase64("encryptedKeyValue", mInfo.encryptedKeyValue);
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ aXmlWriter.endElement();
+
+ aXmlWriter.endElement();
+ aXmlWriter.endDocument();
+ }
+ rStream.writeMemory(aMemStream.GetData(), aMemStream.GetSize());
+}
+
+void AgileEngine::encrypt(css::uno::Reference<css::io::XInputStream> & rxInputStream,
+ css::uno::Reference<css::io::XOutputStream> & rxOutputStream,
+ sal_uInt32 nSize)
+{
+ CryptoHash aCryptoHash(mInfo.hmacKey, cryptoHashTypeFromString(mInfo.hashAlgorithm));
+
+ BinaryXOutputStream aBinaryOutputStream(rxOutputStream, false);
+ BinaryXInputStream aBinaryInputStream(rxInputStream, false);
+
+ std::vector<sal_uInt8> aSizeBytes(sizeof(sal_uInt32));
+ ByteOrderConverter::writeLittleEndian(aSizeBytes.data(), nSize);
+ aBinaryOutputStream.writeMemory(aSizeBytes.data(), aSizeBytes.size()); // size
+ aCryptoHash.update(aSizeBytes, aSizeBytes.size());
+
+ std::vector<sal_uInt8> aNull{0,0,0,0};
+ aBinaryOutputStream.writeMemory(aNull.data(), aNull.size()); // reserved
+ aCryptoHash.update(aNull, aNull.size());
+
+ std::vector<sal_uInt8>& keyDataSalt = mInfo.keyDataSalt;
+
+ sal_uInt32 saltSize = mInfo.saltSize;
+ sal_uInt32 keySize = mInfo.keyBits / 8;
+
+ sal_uInt32 nSegment = 0;
+ sal_uInt32 nSegmentByteSize = sizeof(nSegment);
+
+ std::vector<sal_uInt8> saltWithBlockKey(saltSize + nSegmentByteSize, 0);
+ std::copy(keyDataSalt.begin(), keyDataSalt.end(), saltWithBlockKey.begin());
+
+ std::vector<sal_uInt8> hash(mInfo.hashSize, 0);
+ std::vector<sal_uInt8> iv(keySize, 0);
+
+ std::vector<sal_uInt8> inputBuffer(constSegmentLength);
+ std::vector<sal_uInt8> outputBuffer(constSegmentLength);
+ sal_uInt32 inputLength;
+ sal_uInt32 outputLength;
+
+ while ((inputLength = aBinaryInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
+ {
+ sal_uInt32 correctedInputLength = inputLength % mInfo.blockSize == 0 ?
+ inputLength : oox::core::roundUp(inputLength, sal_uInt32(mInfo.blockSize));
+
+ // Update Key
+ sal_uInt8* segmentBegin = reinterpret_cast<sal_uInt8*>(&nSegment);
+ sal_uInt8* segmentEnd = segmentBegin + nSegmentByteSize;
+ std::copy(segmentBegin, segmentEnd, saltWithBlockKey.begin() + saltSize);
+
+ hashCalc(hash, saltWithBlockKey, mInfo.hashAlgorithm);
+
+ // Only if hash > keySize
+ std::copy(hash.begin(), hash.begin() + keySize, iv.begin());
+
+ Encrypt aEncryptor(mKey, iv, AgileEngine::cryptoType(mInfo));
+ outputLength = aEncryptor.update(outputBuffer, inputBuffer, correctedInputLength);
+ aBinaryOutputStream.writeMemory(outputBuffer.data(), outputLength);
+ aCryptoHash.update(outputBuffer, outputLength);
+
+ nSegment++;
+ }
+ mInfo.hmacHash = aCryptoHash.finalize();
+ encryptHmacValue();
}
} // namespace core
diff --git a/oox/source/crypto/DocumentDecryption.cxx b/oox/source/crypto/DocumentDecryption.cxx
index 40d854123568..b68882ad6b03 100644
--- a/oox/source/crypto/DocumentDecryption.cxx
+++ b/oox/source/crypto/DocumentDecryption.cxx
@@ -106,6 +106,9 @@ bool DocumentDecryption::decrypt(const uno::Reference<io::XStream>& xDocumentStr
xDecryptedPackage->flush();
aDecryptedPackage.seekToStart();
+ if (bResult)
+ return mEngine->checkDataIntegrity();
+
return bResult;
}
diff --git a/oox/source/crypto/DocumentEncryption.cxx b/oox/source/crypto/DocumentEncryption.cxx
index d8b783ac2d41..8cb62c23f006 100644
--- a/oox/source/crypto/DocumentEncryption.cxx
+++ b/oox/source/crypto/DocumentEncryption.cxx
@@ -41,37 +41,30 @@ bool DocumentEncryption::encrypt()
if (!xSeekable.is())
return false;
- sal_uInt32 aLength = xSeekable->getLength();
+ sal_uInt32 aLength = xSeekable->getLength(); // check length of the stream
+ xSeekable->seek(0); // seek to begin of the document stream
if (!mrOleStorage.isStorage())
return false;
+ mEngine.setupEncryption(maPassword);
+
+ Reference<XOutputStream> xOutputStream(mrOleStorage.openOutputStream("EncryptedPackage"), UNO_SET_THROW);
+
+ mEngine.encrypt(xInputStream, xOutputStream, aLength);
+
+ xOutputStream->flush();
+ xOutputStream->closeOutput();
+
Reference<XOutputStream> xEncryptionInfo(mrOleStorage.openOutputStream("EncryptionInfo"), UNO_SET_THROW);
BinaryXOutputStream aEncryptionInfoBinaryOutputStream(xEncryptionInfo, false);
- mEngine.writeEncryptionInfo(maPassword, aEncryptionInfoBinaryOutputStream);
+ mEngine.writeEncryptionInfo(aEncryptionInfoBinaryOutputStream);
aEncryptionInfoBinaryOutputStream.close();
xEncryptionInfo->flush();
xEncryptionInfo->closeOutput();
- Reference<XOutputStream> xEncryptedPackage(mrOleStorage.openOutputStream("EncryptedPackage"), UNO_SET_THROW);
- BinaryXOutputStream aEncryptedPackageStream(xEncryptedPackage, false);
-
- BinaryXInputStream aDocumentInputStream(xInputStream, false);
- aDocumentInputStream.seekToStart();
-
- aEncryptedPackageStream.WriteUInt32(aLength); // size
- aEncryptedPackageStream.WriteUInt32(0U); // reserved
-
- mEngine.encrypt(aDocumentInputStream, aEncryptedPackageStream);
-
- aEncryptedPackageStream.close();
- aDocumentInputStream.close();
-
- xEncryptedPackage->flush();
- xEncryptedPackage->closeOutput();
-
return true;
}
diff --git a/oox/source/crypto/Standard2007Engine.cxx b/oox/source/crypto/Standard2007Engine.cxx
index d1d92269d96c..6dd3e758b641 100644
--- a/oox/source/crypto/Standard2007Engine.cxx
+++ b/oox/source/crypto/Standard2007Engine.cxx
@@ -35,6 +35,7 @@ void lclRandomGenerateValues(sal_uInt8* aArray, sal_uInt32 aSize)
}
static const OUString lclCspName = "Microsoft Enhanced RSA and AES Cryptographic Provider";
+constexpr const sal_uInt32 AES128Size = 16;
} // end anonymous namespace
@@ -172,7 +173,12 @@ bool Standard2007Engine::decrypt(BinaryXInputStream& aInputStream,
return true;
}
-void Standard2007Engine::writeEncryptionInfo(const OUString& password, BinaryXOutputStream& rStream)
+bool Standard2007Engine::checkDataIntegrity()
+{
+ return true;
+}
+
+bool Standard2007Engine::setupEncryption(OUString const & password)
{
mInfo.header.flags = msfilter::ENCRYPTINFO_AES | msfilter::ENCRYPTINFO_CRYPTOAPI;
mInfo.header.algId = msfilter::ENCRYPT_ALGO_AES128;
@@ -187,11 +193,16 @@ void Standard2007Engine::writeEncryptionInfo(const OUString& password, BinaryXOu
mKey.resize(keyLength, 0);
if (!calculateEncryptionKey(password))
- return;
+ return false;
if (!generateVerifier())
- return;
+ return false;
+ return true;
+}
+
+void Standard2007Engine::writeEncryptionInfo(BinaryXOutputStream& rStream)
+{
rStream.WriteUInt32(msfilter::VERSION_INFO_2007_FORMAT);
sal_uInt32 cspNameSize = (lclCspName.getLength() * 2) + 2;
@@ -209,9 +220,19 @@ void Standard2007Engine::writeEncryptionInfo(const OUString& password, BinaryXOu
rStream.writeMemory(&mInfo.verifier, sizeof(msfilter::EncryptionVerifierAES));
}
-void Standard2007Engine::encrypt(BinaryXInputStream& aInputStream,
- BinaryXOutputStream& aOutputStream)
+void Standard2007Engine::encrypt(css::uno::Reference<css::io::XInputStream> & rxInputStream,
+ css::uno::Reference<css::io::XOutputStream> & rxOutputStream,
+ sal_uInt32 nSize)
{
+ if (mKey.empty())
+ return;
+
+ BinaryXOutputStream aBinaryOutputStream(rxOutputStream, false);
+ BinaryXInputStream aBinaryInputStream(rxInputStream, false);
+
+ aBinaryOutputStream.WriteUInt32(nSize); // size
+ aBinaryOutputStream.WriteUInt32(0U); // reserved
+
std::vector<sal_uInt8> inputBuffer(1024);
std::vector<sal_uInt8> outputBuffer(1024);
@@ -221,11 +242,13 @@ void Standard2007Engine::encrypt(BinaryXInputStream& aInputStream,
std::vector<sal_uInt8> iv;
Encrypt aEncryptor(mKey, iv, Crypto::AES_128_ECB);
- while ((inputLength = aInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
+ while ((inputLength = aBinaryInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
{
- inputLength = inputLength % 16 == 0 ? inputLength : ((inputLength / 16) * 16) + 16;
+ // increase size to multiple of 16 (size of mKey) if necessary
+ inputLength = inputLength % AES128Size == 0 ?
+ inputLength : roundUp(inputLength, AES128Size);
outputLength = aEncryptor.update(outputBuffer, inputBuffer, inputLength);
- aOutputStream.writeMemory(outputBuffer.data(), outputLength);
+ aBinaryOutputStream.writeMemory(outputBuffer.data(), outputLength);
}
}