summaryrefslogtreecommitdiffstats
path: root/oox/source/crypto
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--oox/source/crypto/AgileEngine.cxx843
-rw-r--r--oox/source/crypto/CryptTools.cxx507
-rw-r--r--oox/source/crypto/DocumentDecryption.cxx223
-rw-r--r--oox/source/crypto/DocumentEncryption.cxx103
-rw-r--r--oox/source/crypto/Standard2007Engine.cxx333
-rw-r--r--oox/source/crypto/StrongEncryptionDataSpace.cxx203
6 files changed, 2212 insertions, 0 deletions
diff --git a/oox/source/crypto/AgileEngine.cxx b/oox/source/crypto/AgileEngine.cxx
new file mode 100644
index 000000000..f75184981
--- /dev/null
+++ b/oox/source/crypto/AgileEngine.cxx
@@ -0,0 +1,843 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <oox/crypto/AgileEngine.hxx>
+
+#include <oox/helper/binaryinputstream.hxx>
+#include <oox/helper/binaryoutputstream.hxx>
+
+#include <sax/tools/converter.hxx>
+
+#include <comphelper/hash.hxx>
+#include <comphelper/docpasswordhelper.hxx>
+#include <comphelper/random.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/base64.hxx>
+#include <comphelper/sequence.hxx>
+
+#include <filter/msfilter/mscodec.hxx>
+#include <tools/stream.hxx>
+#include <tools/XmlWriter.hxx>
+#include <sax/fastattribs.hxx>
+
+#include <com/sun/star/xml/sax/XFastParser.hpp>
+#include <com/sun/star/xml/sax/XFastTokenHandler.hpp>
+#include <com/sun/star/xml/sax/FastParser.hpp>
+#include <com/sun/star/xml/sax/FastToken.hpp>
+
+using namespace css;
+using namespace css::beans;
+using namespace css::io;
+using namespace css::lang;
+using namespace css::uno;
+using namespace css::xml::sax;
+using namespace css::xml;
+
+namespace oox::crypto {
+
+namespace {
+
+std::u16string_view stripNamespacePrefix(std::u16string_view rsInputName)
+{
+ size_t idx = rsInputName.find(':');
+ if (idx == std::u16string_view::npos)
+ return rsInputName;
+ return rsInputName.substr(idx + 1);
+}
+
+class AgileTokenHandler : public sax_fastparser::FastTokenHandlerBase
+{
+public:
+ virtual sal_Int32 SAL_CALL getTokenFromUTF8(const Sequence< sal_Int8 >& /*nIdentifier*/) override
+ {
+ return FastToken::DONTKNOW;
+ }
+
+ virtual Sequence<sal_Int8> SAL_CALL getUTF8Identifier(sal_Int32 /*nToken*/) override
+ {
+ return Sequence<sal_Int8>();
+ }
+
+ virtual sal_Int32 getTokenDirect( const char * /* pToken */, sal_Int32 /* nLength */ ) const override
+ {
+ return -1;
+ }
+};
+
+class AgileDocumentHandler : public ::cppu::WeakImplHelper<XFastDocumentHandler>
+{
+ AgileEncryptionInfo& mInfo;
+
+public:
+ explicit AgileDocumentHandler(AgileEncryptionInfo& rInfo) :
+ mInfo(rInfo)
+ {}
+
+ void SAL_CALL startDocument() override {}
+ void SAL_CALL endDocument() override {}
+ void SAL_CALL processingInstruction( const OUString& /*rTarget*/, const OUString& /*rData*/ ) override {}
+ void SAL_CALL setDocumentLocator( const Reference< XLocator >& /*xLocator*/ ) override {}
+ void SAL_CALL startFastElement( sal_Int32 /*Element*/, const Reference< XFastAttributeList >& /*Attribs*/ ) override {}
+
+ void SAL_CALL startUnknownElement( const OUString& /*aNamespace*/, const OUString& rName, const Reference< XFastAttributeList >& aAttributeList ) override
+ {
+ std::u16string_view rLocalName = stripNamespacePrefix(rName);
+
+ const css::uno::Sequence<Attribute> aUnknownAttributes = aAttributeList->getUnknownAttributes();
+ for (const Attribute& rAttribute : aUnknownAttributes)
+ {
+ std::u16string_view rAttrLocalName = stripNamespacePrefix(rAttribute.Name);
+
+ if (rAttrLocalName == u"spinCount")
+ {
+ ::sax::Converter::convertNumber(mInfo.spinCount, rAttribute.Value);
+ }
+ else if (rAttrLocalName == u"saltSize")
+ {
+ ::sax::Converter::convertNumber(mInfo.saltSize, rAttribute.Value);
+ }
+ else if (rAttrLocalName == u"blockSize")
+ {
+ ::sax::Converter::convertNumber(mInfo.blockSize, rAttribute.Value);
+ }
+ else if (rAttrLocalName == u"keyBits")
+ {
+ ::sax::Converter::convertNumber(mInfo.keyBits, rAttribute.Value);
+ }
+ else if (rAttrLocalName == u"hashSize")
+ {
+ ::sax::Converter::convertNumber(mInfo.hashSize, rAttribute.Value);
+ }
+ else if (rAttrLocalName == u"cipherAlgorithm")
+ {
+ mInfo.cipherAlgorithm = rAttribute.Value;
+ }
+ else if (rAttrLocalName == u"cipherChaining")
+ {
+ mInfo.cipherChaining = rAttribute.Value;
+ }
+ else if (rAttrLocalName == u"hashAlgorithm")
+ {
+ mInfo.hashAlgorithm = rAttribute.Value;
+ }
+ else if (rAttrLocalName == u"saltValue")
+ {
+ Sequence<sal_Int8> saltValue;
+ comphelper::Base64::decode(saltValue, rAttribute.Value);
+ if (rLocalName == u"encryptedKey")
+ mInfo.saltValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(saltValue);
+ else if (rLocalName == u"keyData")
+ mInfo.keyDataSalt = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(saltValue);
+ }
+ else if (rAttrLocalName == u"encryptedVerifierHashInput")
+ {
+ Sequence<sal_Int8> encryptedVerifierHashInput;
+ comphelper::Base64::decode(encryptedVerifierHashInput, rAttribute.Value);
+ mInfo.encryptedVerifierHashInput = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedVerifierHashInput);
+ }
+ else if (rAttrLocalName == u"encryptedVerifierHashValue")
+ {
+ Sequence<sal_Int8> encryptedVerifierHashValue;
+ comphelper::Base64::decode(encryptedVerifierHashValue, rAttribute.Value);
+ mInfo.encryptedVerifierHashValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedVerifierHashValue);
+ }
+ else if (rAttrLocalName == u"encryptedKeyValue")
+ {
+ Sequence<sal_Int8> encryptedKeyValue;
+ comphelper::Base64::decode(encryptedKeyValue, rAttribute.Value);
+ mInfo.encryptedKeyValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(encryptedKeyValue);
+ }
+ if (rAttrLocalName == u"encryptedHmacKey")
+ {
+ Sequence<sal_Int8> aValue;
+ comphelper::Base64::decode(aValue, rAttribute.Value);
+ mInfo.hmacEncryptedKey = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(aValue);
+ }
+ if (rAttrLocalName == u"encryptedHmacValue")
+ {
+ Sequence<sal_Int8> aValue;
+ comphelper::Base64::decode(aValue, rAttribute.Value);
+ mInfo.hmacEncryptedValue = comphelper::sequenceToContainer<std::vector<sal_uInt8>>(aValue);
+ }
+ }
+ }
+
+ void SAL_CALL endFastElement( sal_Int32 /*aElement*/ ) override
+ {}
+ void SAL_CALL endUnknownElement( const OUString& /*aNamespace*/, const OUString& /*aName*/ ) override
+ {}
+
+ Reference< XFastContextHandler > SAL_CALL createFastChildContext( sal_Int32 /*aElement*/, const Reference< XFastAttributeList >& /*aAttribs*/ ) override
+ {
+ return nullptr;
+ }
+
+ Reference< XFastContextHandler > SAL_CALL createUnknownChildContext( const OUString& /*aNamespace*/, const OUString& /*aName*/, const Reference< XFastAttributeList >& /*aAttribs*/ ) override
+ {
+ return this;
+ }
+
+ void SAL_CALL characters( const OUString& /*aChars*/ ) override
+ {}
+};
+
+constexpr const sal_uInt32 constSegmentLength = 4096;
+
+const std::vector<sal_uInt8> constBlock1 { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 };
+const std::vector<sal_uInt8> constBlock2 { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e };
+const std::vector<sal_uInt8> constBlock3 { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 };
+const std::vector<sal_uInt8> constBlockHmac1 { 0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6 };
+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,
+ std::u16string_view sAlgorithm )
+{
+ if (sAlgorithm == u"SHA1")
+ {
+ std::vector<unsigned char> out = comphelper::Hash::calculateHash(input.data(), input.size(), comphelper::HashType::SHA1);
+ output = out;
+ return true;
+ }
+ else if (sAlgorithm == u"SHA512")
+ {
+ std::vector<unsigned char> out = comphelper::Hash::calculateHash(input.data(), input.size(), comphelper::HashType::SHA512);
+ output = out;
+ return true;
+ }
+ return false;
+}
+
+CryptoHashType cryptoHashTypeFromString(std::u16string_view sAlgorithm)
+{
+ if (sAlgorithm == u"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")
+ return Crypto::AES_128_CBC;
+ else if (rInfo.keyBits == 256 && rInfo.cipherAlgorithm == "AES" && rInfo.cipherChaining == "ChainingModeCBC")
+ return Crypto::AES_256_CBC;
+ return Crypto::UNKNOWN;
+}
+
+static 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,
+ 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());
+
+ Decrypt aDecryptor(key, mInfo.saltValue, cryptoType(mInfo));
+ 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);
+}
+
+namespace
+{
+
+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>& encryptedHashValue = mInfo.encryptedVerifierHashValue;
+ size_t encryptedHashValueSize = encryptedHashValue.size();
+ size_t nHashValueSize = mInfo.hashSize;
+ if (nHashValueSize > encryptedHashValueSize)
+ return false;
+
+ std::vector<sal_uInt8> hashFinal(nHashValueSize, 0);
+ calculateHashFinal(rPassword, hashFinal);
+
+ std::vector<sal_uInt8>& encryptedHashInput = mInfo.encryptedVerifierHashInput;
+ // SALT - needs to be a multiple of block size (?)
+ sal_uInt32 nSaltSize = roundUp(mInfo.saltSize, mInfo.blockSize);
+ if (nSaltSize < encryptedHashInput.size())
+ return false;
+ std::vector<sal_uInt8> hashInput(nSaltSize, 0);
+ calculateBlock(constBlock1, hashFinal, encryptedHashInput, hashInput);
+
+ std::vector<sal_uInt8> hashValue(encryptedHashValueSize, 0);
+ calculateBlock(constBlock2, hashFinal, encryptedHashValue, hashValue);
+
+ std::vector<sal_uInt8> hash(nHashValueSize, 0);
+ hashCalc(hash, hashInput, mInfo.hashAlgorithm);
+
+ return std::equal(hash.begin(), hash.end(), hashValue.begin());
+}
+
+void 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);
+}
+
+// TODO: Rename
+bool AgileEngine::generateEncryptionKey(OUString const & rPassword)
+{
+ bool bResult = decryptAndCheckVerifierHash(rPassword);
+
+ if (bResult)
+ {
+ decryptEncryptionKey(rPassword);
+ decryptHmacKey();
+ decryptHmacValue();
+ }
+ return bResult;
+}
+
+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 without 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 without 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
+ // account 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;
+ sal_uInt32 keySize = mInfo.keyBits / 8;
+
+ sal_uInt32 segment = 0;
+
+ std::vector<sal_uInt8> saltWithBlockKey(saltSize + sizeof(segment), 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;
+ sal_uInt32 remaining = totalSize;
+
+ while ((inputLength = aInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
+ {
+ auto p = saltWithBlockKey.begin() + saltSize;
+ p[0] = segment & 0xFF;
+ p[1] = (segment >> 8) & 0xFF;
+ p[2] = (segment >> 16) & 0xFF;
+ p[3] = segment >> 24;
+
+ hashCalc(hash, saltWithBlockKey, mInfo.hashAlgorithm);
+
+ // Only if hash > keySize
+ std::copy(hash.begin(), hash.begin() + keySize, iv.begin());
+
+ Decrypt aDecryptor(mKey, iv, AgileEngine::cryptoType(mInfo));
+ 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;
+}
+
+bool AgileEngine::readEncryptionInfo(uno::Reference<io::XInputStream> & rxInputStream)
+{
+ // Check reserved value
+ std::vector<sal_uInt8> aExpectedReservedBytes(sizeof(sal_uInt32));
+ ByteOrderConverter::writeLittleEndian(aExpectedReservedBytes.data(), msfilter::AGILE_ENCRYPTION_RESERVED);
+
+ uno::Sequence<sal_Int8> aReadReservedBytes(sizeof(sal_uInt32));
+ rxInputStream->readBytes(aReadReservedBytes, aReadReservedBytes.getLength());
+
+ if (!std::equal(std::cbegin(aReadReservedBytes), std::cend(aReadReservedBytes), aExpectedReservedBytes.begin()))
+ return false;
+
+ mInfo.spinCount = 0;
+ mInfo.saltSize = 0;
+ mInfo.keyBits = 0;
+ mInfo.hashSize = 0;
+ mInfo.blockSize = 0;
+
+ Reference<XFastDocumentHandler> xFastDocumentHandler(new AgileDocumentHandler(mInfo));
+ Reference<XFastTokenHandler> xFastTokenHandler(new AgileTokenHandler);
+
+ Reference<XFastParser> xParser(css::xml::sax::FastParser::create(comphelper::getProcessComponentContext()));
+
+ xParser->setFastDocumentHandler(xFastDocumentHandler);
+ xParser->setTokenHandler(xFastTokenHandler);
+
+ InputSource aInputSource;
+ aInputSource.aInputStream = rxInputStream;
+ xParser->parseStream(aInputSource);
+
+ // CHECK info data
+ if (2 > mInfo.blockSize || mInfo.blockSize > 4096)
+ return false;
+
+ if (0 > mInfo.spinCount || mInfo.spinCount > 10000000)
+ return false;
+
+ if (1 > mInfo.saltSize|| mInfo.saltSize > 65536) // Check
+ return false;
+
+ // AES 128 CBC with SHA1
+ if (mInfo.keyBits == 128 &&
+ mInfo.cipherAlgorithm == "AES" &&
+ mInfo.cipherChaining == "ChainingModeCBC" &&
+ mInfo.hashAlgorithm == "SHA1" &&
+ mInfo.hashSize == comphelper::SHA1_HASH_LENGTH)
+ {
+ return true;
+ }
+
+ // AES 256 CBC with SHA512
+ if (mInfo.keyBits == 256 &&
+ mInfo.cipherAlgorithm == "AES" &&
+ mInfo.cipherChaining == "ChainingModeCBC" &&
+ mInfo.hashAlgorithm == "SHA512" &&
+ mInfo.hashSize == comphelper::SHA512_HASH_LENGTH)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+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::crypto::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 without 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 without 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") });
+
+ return setupEncryptionKey(rPassword);
+}
+
+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)
+{
+ 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::writeEncryptionInfo(BinaryXOutputStream & rStream)
+{
+ rStream.WriteUInt32(msfilter::VERSION_INFO_AGILE);
+ rStream.WriteUInt32(msfilter::AGILE_ENCRYPTION_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(const 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::crypto::roundUp(inputLength, sal_uInt32(mInfo.blockSize));
+
+ // Update Key
+ auto p = saltWithBlockKey.begin() + saltSize;
+ p[0] = nSegment & 0xFF;
+ p[1] = (nSegment >> 8) & 0xFF;
+ p[2] = (nSegment >> 16) & 0xFF;
+ p[3] = nSegment >> 24;
+
+ 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 oox::crypto
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/crypto/CryptTools.cxx b/oox/source/crypto/CryptTools.cxx
new file mode 100644
index 000000000..ee68a8a50
--- /dev/null
+++ b/oox/source/crypto/CryptTools.cxx
@@ -0,0 +1,507 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <oox/crypto/CryptTools.hxx>
+#include <com/sun/star/uno/RuntimeException.hpp>
+
+#include <config_oox.h>
+
+#if USE_TLS_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#endif // USE_TLS_OPENSSL
+
+#if USE_TLS_NSS
+#include <nss.h>
+#include <pk11pub.h>
+#endif // USE_TLS_NSS
+
+namespace oox::crypto {
+
+#if USE_TLS_OPENSSL
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+
+static HMAC_CTX *HMAC_CTX_new(void)
+{
+ HMAC_CTX *pContext = new HMAC_CTX;
+ HMAC_CTX_init(pContext);
+ return pContext;
+}
+
+static void HMAC_CTX_free(HMAC_CTX *pContext)
+{
+ HMAC_CTX_cleanup(pContext);
+ delete pContext;
+}
+#endif
+
+namespace
+{
+ struct cipher_delete
+ {
+ void operator()(EVP_CIPHER_CTX* p) { EVP_CIPHER_CTX_free(p); }
+ };
+
+ struct hmac_delete
+ {
+ void operator()(HMAC_CTX* p) { HMAC_CTX_free(p); }
+ };
+}
+
+struct CryptoImpl
+{
+ std::unique_ptr<EVP_CIPHER_CTX, cipher_delete> mpContext;
+ std::unique_ptr<HMAC_CTX, hmac_delete> mpHmacContext;
+
+ CryptoImpl() = default;
+
+ void setupEncryptContext(std::vector<sal_uInt8>& key, std::vector<sal_uInt8>& iv, Crypto::CryptoType eType)
+ {
+ mpContext.reset(EVP_CIPHER_CTX_new());
+ EVP_CIPHER_CTX_init(mpContext.get());
+
+ const EVP_CIPHER* cipher = getCipher(eType);
+ if (cipher == nullptr)
+ return;
+
+ if (iv.empty())
+ EVP_EncryptInit_ex(mpContext.get(), cipher, nullptr, key.data(), nullptr);
+ else
+ EVP_EncryptInit_ex(mpContext.get(), cipher, nullptr, key.data(), iv.data());
+ EVP_CIPHER_CTX_set_padding(mpContext.get(), 0);
+ }
+
+ void setupDecryptContext(std::vector<sal_uInt8>& key, std::vector<sal_uInt8>& iv, Crypto::CryptoType eType)
+ {
+ mpContext.reset(EVP_CIPHER_CTX_new());
+ EVP_CIPHER_CTX_init(mpContext.get());
+
+ const EVP_CIPHER* pCipher = getCipher(eType);
+ if (pCipher == nullptr)
+ return;
+
+ const size_t nMinKeySize = EVP_CIPHER_key_length(pCipher);
+ if (key.size() < nMinKeySize)
+ key.resize(nMinKeySize, 0);
+
+ if (iv.empty())
+ EVP_DecryptInit_ex(mpContext.get(), pCipher, nullptr, key.data(), nullptr);
+ else
+ {
+ const size_t nMinIVSize = EVP_CIPHER_iv_length(pCipher);
+ if (iv.size() < nMinIVSize)
+ iv.resize(nMinIVSize, 0);
+
+ EVP_DecryptInit_ex(mpContext.get(), pCipher, nullptr, key.data(), iv.data());
+ }
+ EVP_CIPHER_CTX_set_padding(mpContext.get(), 0);
+ }
+
+ void setupCryptoHashContext(std::vector<sal_uInt8>& rKey, CryptoHashType eType)
+ {
+ mpHmacContext.reset(HMAC_CTX_new());
+ const EVP_MD* aEvpMd = nullptr;
+ switch (eType)
+ {
+ case CryptoHashType::SHA1:
+ aEvpMd = EVP_sha1(); break;
+ case CryptoHashType::SHA256:
+ aEvpMd = EVP_sha256(); break;
+ case CryptoHashType::SHA512:
+ aEvpMd = EVP_sha512(); break;
+ }
+ HMAC_Init_ex(mpHmacContext.get(), rKey.data(), rKey.size(), aEvpMd, nullptr);
+ }
+
+ ~CryptoImpl()
+ {
+ if (mpContext)
+ EVP_CIPHER_CTX_cleanup(mpContext.get());
+ }
+
+ static const EVP_CIPHER* getCipher(Crypto::CryptoType type)
+ {
+ switch(type)
+ {
+ case Crypto::CryptoType::AES_128_ECB:
+ return EVP_aes_128_ecb();
+ case Crypto::CryptoType::AES_128_CBC:
+ return EVP_aes_128_cbc();
+ case Crypto::CryptoType::AES_256_CBC:
+ return EVP_aes_256_cbc();
+ default:
+ break;
+ }
+ return nullptr;
+ }
+};
+
+#elif USE_TLS_NSS
+
+#define MAX_WRAPPED_KEY_LEN 128
+
+struct CryptoImpl
+{
+ PK11SlotInfo* mSlot;
+ PK11Context* mContext;
+ SECItem* mSecParam;
+ PK11SymKey* mSymKey;
+ PK11Context* mWrapKeyContext;
+ PK11SymKey* mWrapKey;
+
+ CryptoImpl()
+ : mSlot(nullptr)
+ , mContext(nullptr)
+ , mSecParam(nullptr)
+ , mSymKey(nullptr)
+ , mWrapKeyContext(nullptr)
+ , mWrapKey(nullptr)
+ {
+ // Initialize NSS, database functions are not needed
+ NSS_NoDB_Init(nullptr);
+ }
+
+ ~CryptoImpl()
+ {
+ if (mContext)
+ PK11_DestroyContext(mContext, PR_TRUE);
+ if (mSecParam)
+ SECITEM_FreeItem(mSecParam, PR_TRUE);
+ if (mSymKey)
+ PK11_FreeSymKey(mSymKey);
+ if (mWrapKeyContext)
+ PK11_DestroyContext(mWrapKeyContext, PR_TRUE);
+ if (mWrapKey)
+ PK11_FreeSymKey(mWrapKey);
+ if (mSlot)
+ PK11_FreeSlot(mSlot);
+ }
+
+ PK11SymKey* ImportSymKey(CK_MECHANISM_TYPE mechanism, CK_ATTRIBUTE_TYPE operation, SECItem* key)
+ {
+ mSymKey = PK11_ImportSymKey(mSlot, mechanism, PK11_OriginUnwrap, operation, key, nullptr);
+ if (!mSymKey) //rhbz#1614419 maybe failed due to FIPS, use rhbz#1461450 style workaround
+ {
+ /*
+ * Without FIPS it would be possible to just use
+ * mSymKey = PK11_ImportSymKey( mSlot, mechanism, PK11_OriginUnwrap, CKA_ENCRYPT, &keyItem, nullptr );
+ * with FIPS NSS Level 2 certification has to be "workarounded" (so it becomes Level 1) by using
+ * following method:
+ * 1. Generate wrap key
+ * 2. Encrypt authkey with wrap key
+ * 3. Unwrap encrypted authkey using wrap key
+ */
+
+ /*
+ * Generate wrapping key
+ */
+ CK_MECHANISM_TYPE wrap_mechanism = PK11_GetBestWrapMechanism(mSlot);
+ int wrap_key_len = PK11_GetBestKeyLength(mSlot, wrap_mechanism);
+ mWrapKey = PK11_KeyGen(mSlot, wrap_mechanism, nullptr, wrap_key_len, nullptr);
+ if (!mWrapKey)
+ throw css::uno::RuntimeException("PK11_KeyGen SymKey failure", css::uno::Reference<css::uno::XInterface>());
+
+ /*
+ * Encrypt authkey with wrapping key
+ */
+
+ /*
+ * Initialization of IV is not needed because PK11_GetBestWrapMechanism should return ECB mode
+ */
+ SECItem tmp_sec_item = {};
+ mWrapKeyContext = PK11_CreateContextBySymKey(wrap_mechanism, CKA_ENCRYPT, mWrapKey, &tmp_sec_item);
+ if (!mWrapKeyContext)
+ throw css::uno::RuntimeException("PK11_CreateContextBySymKey failure", css::uno::Reference<css::uno::XInterface>());
+
+ unsigned char wrapped_key_data[MAX_WRAPPED_KEY_LEN];
+ int wrapped_key_len = sizeof(wrapped_key_data);
+
+ if (PK11_CipherOp(mWrapKeyContext, wrapped_key_data, &wrapped_key_len,
+ sizeof(wrapped_key_data), key->data, key->len) != SECSuccess)
+ {
+ throw css::uno::RuntimeException("PK11_CipherOp failure", css::uno::Reference<css::uno::XInterface>());
+ }
+
+ if (PK11_Finalize(mWrapKeyContext) != SECSuccess)
+ throw css::uno::RuntimeException("PK11_Finalize failure", css::uno::Reference<css::uno::XInterface>());
+
+ /*
+ * Finally unwrap sym key
+ */
+ SECItem wrapped_key = {};
+ wrapped_key.data = wrapped_key_data;
+ wrapped_key.len = wrapped_key_len;
+
+ mSymKey = PK11_UnwrapSymKey(mWrapKey, wrap_mechanism, &tmp_sec_item, &wrapped_key,
+ mechanism, operation, key->len);
+ }
+ return mSymKey;
+ }
+
+ void setupCryptoContext(std::vector<sal_uInt8>& key, std::vector<sal_uInt8>& iv, Crypto::CryptoType type, CK_ATTRIBUTE_TYPE operation)
+ {
+ CK_MECHANISM_TYPE mechanism = static_cast<CK_ULONG>(-1);
+
+ SECItem ivItem;
+ ivItem.type = siBuffer;
+ if(iv.empty())
+ ivItem.data = nullptr;
+ else
+ ivItem.data = iv.data();
+ ivItem.len = iv.size();
+
+ SECItem* pIvItem = nullptr;
+
+ switch(type)
+ {
+ case Crypto::CryptoType::AES_128_ECB:
+ mechanism = CKM_AES_ECB;
+ break;
+ case Crypto::CryptoType::AES_128_CBC:
+ mechanism = CKM_AES_CBC;
+ pIvItem = &ivItem;
+ break;
+ case Crypto::CryptoType::AES_256_CBC:
+ mechanism = CKM_AES_CBC;
+ pIvItem = &ivItem;
+ break;
+ default:
+ break;
+ }
+
+ mSlot = PK11_GetBestSlot(mechanism, nullptr);
+
+ if (!mSlot)
+ throw css::uno::RuntimeException("NSS Slot failure", css::uno::Reference<css::uno::XInterface>());
+
+ SECItem keyItem;
+ keyItem.type = siBuffer;
+ keyItem.data = key.data();
+ keyItem.len = key.size();
+
+ mSymKey = ImportSymKey(mechanism, CKA_ENCRYPT, &keyItem);
+ if (!mSymKey)
+ throw css::uno::RuntimeException("NSS SymKey failure", css::uno::Reference<css::uno::XInterface>());
+
+ mSecParam = PK11_ParamFromIV(mechanism, pIvItem);
+ mContext = PK11_CreateContextBySymKey(mechanism, operation, mSymKey, mSecParam);
+ }
+
+ void setupCryptoHashContext(std::vector<sal_uInt8>& rKey, CryptoHashType eType)
+ {
+ CK_MECHANISM_TYPE aMechanism = static_cast<CK_ULONG>(-1);
+
+ switch(eType)
+ {
+ case CryptoHashType::SHA1:
+ aMechanism = CKM_SHA_1_HMAC;
+ break;
+ case CryptoHashType::SHA256:
+ aMechanism = CKM_SHA256_HMAC;
+ break;
+ case CryptoHashType::SHA512:
+ aMechanism = CKM_SHA512_HMAC;
+ break;
+ }
+
+ mSlot = PK11_GetBestSlot(aMechanism, nullptr);
+
+ if (!mSlot)
+ throw css::uno::RuntimeException("NSS Slot failure", css::uno::Reference<css::uno::XInterface>());
+
+ SECItem aKeyItem;
+ aKeyItem.data = rKey.data();
+ aKeyItem.len = rKey.size();
+
+ mSymKey = ImportSymKey(aMechanism, CKA_SIGN, &aKeyItem);
+ if (!mSymKey)
+ throw css::uno::RuntimeException("NSS SymKey failure", css::uno::Reference<css::uno::XInterface>());
+
+ SECItem param;
+ param.data = nullptr;
+ param.len = 0;
+ mContext = PK11_CreateContextBySymKey(aMechanism, CKA_SIGN, mSymKey, &param);
+ }
+};
+#else
+struct CryptoImpl
+{};
+#endif
+
+Crypto::Crypto()
+ : mpImpl(std::make_unique<CryptoImpl>())
+{
+}
+
+Crypto::~Crypto()
+{
+}
+
+// DECRYPT
+
+Decrypt::Decrypt(std::vector<sal_uInt8>& key, std::vector<sal_uInt8>& iv, CryptoType type)
+{
+#if USE_TLS_OPENSSL + USE_TLS_NSS == 0
+ (void)key;
+ (void)iv;
+ (void)type;
+#endif
+
+#if USE_TLS_OPENSSL
+ mpImpl->setupDecryptContext(key, iv, type);
+#endif
+
+#if USE_TLS_NSS
+ mpImpl->setupCryptoContext(key, iv, type, CKA_DECRYPT);
+#endif // USE_TLS_NSS
+}
+
+sal_uInt32 Decrypt::update(std::vector<sal_uInt8>& output, std::vector<sal_uInt8>& input, sal_uInt32 inputLength)
+{
+ int outputLength = 0;
+
+#if USE_TLS_OPENSSL + USE_TLS_NSS > 0
+ sal_uInt32 actualInputLength = inputLength == 0 || inputLength > input.size() ? input.size() : inputLength;
+#else
+ (void)output;
+ (void)input;
+ (void)inputLength;
+#endif
+
+#if USE_TLS_OPENSSL
+ (void)EVP_DecryptUpdate(mpImpl->mpContext.get(), output.data(), &outputLength, input.data(), actualInputLength);
+#endif // USE_TLS_OPENSSL
+
+#if USE_TLS_NSS
+ if (!mpImpl->mContext)
+ return 0;
+ (void)PK11_CipherOp(mpImpl->mContext, output.data(), &outputLength, actualInputLength, input.data(), actualInputLength);
+#endif // USE_TLS_NSS
+
+ return static_cast<sal_uInt32>(outputLength);
+}
+
+sal_uInt32 Decrypt::aes128ecb(std::vector<sal_uInt8>& output, std::vector<sal_uInt8>& input, std::vector<sal_uInt8>& key)
+{
+ sal_uInt32 outputLength = 0;
+ std::vector<sal_uInt8> iv;
+ Decrypt crypto(key, iv, Crypto::AES_128_ECB);
+ outputLength = crypto.update(output, input);
+ return outputLength;
+}
+
+// ENCRYPT
+
+Encrypt::Encrypt(std::vector<sal_uInt8>& key, std::vector<sal_uInt8>& iv, CryptoType type)
+{
+#if USE_TLS_OPENSSL + USE_TLS_NSS == 0
+ (void)key;
+ (void)iv;
+ (void)type;
+#endif
+
+#if USE_TLS_OPENSSL
+ mpImpl->setupEncryptContext(key, iv, type);
+#elif USE_TLS_NSS
+ mpImpl->setupCryptoContext(key, iv, type, CKA_ENCRYPT);
+#endif // USE_TLS_NSS
+}
+
+sal_uInt32 Encrypt::update(std::vector<sal_uInt8>& output, std::vector<sal_uInt8>& input, sal_uInt32 inputLength)
+{
+ int outputLength = 0;
+
+#if USE_TLS_OPENSSL + USE_TLS_NSS > 0
+ sal_uInt32 actualInputLength = inputLength == 0 || inputLength > input.size() ? input.size() : inputLength;
+#else
+ (void)output;
+ (void)input;
+ (void)inputLength;
+#endif
+
+#if USE_TLS_OPENSSL
+ (void)EVP_EncryptUpdate(mpImpl->mpContext.get(), output.data(), &outputLength, input.data(), actualInputLength);
+#endif // USE_TLS_OPENSSL
+
+#if USE_TLS_NSS
+ (void)PK11_CipherOp(mpImpl->mContext, output.data(), &outputLength, actualInputLength, input.data(), actualInputLength);
+#endif // USE_TLS_NSS
+
+ return static_cast<sal_uInt32>(outputLength);
+}
+
+// CryptoHash - HMAC
+
+namespace
+{
+
+sal_Int32 getSizeForHashType(CryptoHashType eType)
+{
+ switch (eType)
+ {
+ case CryptoHashType::SHA1: return 20;
+ case CryptoHashType::SHA256: return 32;
+ case CryptoHashType::SHA512: return 64;
+ }
+ return 0;
+}
+
+} // end anonymous namespace
+
+CryptoHash::CryptoHash(std::vector<sal_uInt8>& rKey, CryptoHashType eType)
+ : mnHashSize(getSizeForHashType(eType))
+{
+#if USE_TLS_OPENSSL
+ mpImpl->setupCryptoHashContext(rKey, eType);
+#elif USE_TLS_NSS
+ mpImpl->setupCryptoHashContext(rKey, eType);
+ PK11_DigestBegin(mpImpl->mContext);
+#else
+ (void)rKey;
+#endif
+}
+
+bool CryptoHash::update(std::vector<sal_uInt8>& rInput, sal_uInt32 nInputLength)
+{
+#if USE_TLS_OPENSSL + USE_TLS_NSS > 0
+ sal_uInt32 nActualInputLength = (nInputLength == 0 || nInputLength > rInput.size()) ? rInput.size() : nInputLength;
+#else
+ (void)rInput;
+ (void)nInputLength;
+#endif
+
+#if USE_TLS_OPENSSL
+ return HMAC_Update(mpImpl->mpHmacContext.get(), rInput.data(), nActualInputLength) != 0;
+#elif USE_TLS_NSS
+ return PK11_DigestOp(mpImpl->mContext, rInput.data(), nActualInputLength) == SECSuccess;
+#else
+ return false; // ???
+#endif
+}
+
+std::vector<sal_uInt8> CryptoHash::finalize()
+{
+ std::vector<sal_uInt8> aHash(mnHashSize, 0);
+ unsigned int nSizeWritten;
+#if USE_TLS_OPENSSL
+ (void) HMAC_Final(mpImpl->mpHmacContext.get(), aHash.data(), &nSizeWritten);
+#elif USE_TLS_NSS
+ PK11_DigestFinal(mpImpl->mContext, aHash.data(), &nSizeWritten, aHash.size());
+#endif
+ (void)nSizeWritten;
+
+ return aHash;
+}
+
+} // namespace oox::crypto
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/crypto/DocumentDecryption.cxx b/oox/source/crypto/DocumentDecryption.cxx
new file mode 100644
index 000000000..cae8a1331
--- /dev/null
+++ b/oox/source/crypto/DocumentDecryption.cxx
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <oox/crypto/DocumentDecryption.hxx>
+
+#include <comphelper/sequenceashashmap.hxx>
+
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/packages/XPackageEncryption.hpp>
+#include <oox/ole/olestorage.hxx>
+#include <oox/helper/binaryinputstream.hxx>
+
+#include <sal/log.hxx>
+
+namespace
+{
+void lcl_getListOfStreams(oox::StorageBase* pStorage, std::vector<OUString>& rElementNames)
+{
+ std::vector<OUString> oElementNames;
+ pStorage->getElementNames(oElementNames);
+ for (const auto& sName : oElementNames)
+ {
+ oox::StorageRef rSubStorage = pStorage->openSubStorage(sName, false);
+ if (rSubStorage && rSubStorage->isStorage())
+ {
+ lcl_getListOfStreams(rSubStorage.get(), rElementNames);
+ }
+ else
+ {
+ if (pStorage->isRootStorage())
+ rElementNames.push_back(sName);
+ else
+ rElementNames.push_back(pStorage->getPath() + "/" + sName);
+ }
+ }
+}
+}
+
+namespace oox::crypto
+{
+using namespace css;
+
+DocumentDecryption::DocumentDecryption(
+ const css::uno::Reference<css::uno::XComponentContext>& rxContext,
+ oox::ole::OleStorage& rOleStorage)
+ : mxContext(rxContext)
+ , mrOleStorage(rOleStorage)
+{
+ // Get OLE streams into sequences for later use in CryptoEngine
+ std::vector<OUString> aStreamNames;
+ lcl_getListOfStreams(&mrOleStorage, aStreamNames);
+
+ comphelper::SequenceAsHashMap aStreamsData;
+ for (const auto& sStreamName : aStreamNames)
+ {
+ uno::Reference<io::XInputStream> xStream = mrOleStorage.openInputStream(sStreamName);
+ if (!xStream.is())
+ throw io::IOException("Cannot open OLE input stream for " + sStreamName + "!");
+
+ BinaryXInputStream aBinaryInputStream(xStream, true);
+
+ css::uno::Sequence<sal_Int8> oData;
+ sal_Int32 nStreamSize = aBinaryInputStream.size();
+ sal_Int32 nReadBytes = aBinaryInputStream.readData(oData, nStreamSize);
+
+ if (nStreamSize != nReadBytes)
+ {
+ SAL_WARN("oox", "OLE stream invalid content");
+ throw io::IOException("OLE stream invalid content for " + sStreamName + "!");
+ }
+
+ aStreamsData[sStreamName] <<= oData;
+ }
+ maStreamsSequence = aStreamsData.getAsConstNamedValueList();
+}
+
+bool DocumentDecryption::generateEncryptionKey(const OUString& rPassword)
+{
+ if (mxPackageEncryption.is())
+ return mxPackageEncryption->generateEncryptionKey(rPassword);
+ return false;
+}
+
+bool DocumentDecryption::readEncryptionInfo()
+{
+ if (!mrOleStorage.isStorage())
+ return false;
+
+ // Read 0x6DataSpaces/DataSpaceMap
+ uno::Reference<io::XInputStream> xDataSpaceMap
+ = mrOleStorage.openInputStream("\006DataSpaces/DataSpaceMap");
+ OUString sDataSpaceName;
+
+ if (xDataSpaceMap.is())
+ {
+ bool bBroken = false;
+
+ BinaryXInputStream aDataSpaceStream(xDataSpaceMap, true);
+ sal_uInt32 aHeaderLength = aDataSpaceStream.readuInt32();
+ SAL_WARN_IF(aHeaderLength != 8, "oox",
+ "DataSpaceMap length != 8 is not supported. Some content may be skipped");
+ sal_uInt32 aEntryCount = aDataSpaceStream.readuInt32();
+ SAL_WARN_IF(aEntryCount != 1, "oox",
+ "DataSpaceMap contains more than one entry. Some content may be skipped");
+
+ // Read each DataSpaceMapEntry (MS-OFFCRYPTO 2.1.6.1)
+ for (sal_uInt32 i = 0; i < aEntryCount && !bBroken; i++)
+ {
+ // entryLen unused for the moment
+ aDataSpaceStream.skip(sizeof(sal_uInt32));
+
+ // Read each DataSpaceReferenceComponent (MS-OFFCRYPTO 2.1.6.2)
+ sal_uInt32 aReferenceComponentCount = aDataSpaceStream.readuInt32();
+ for (sal_uInt32 j = 0; j < aReferenceComponentCount && !bBroken; j++)
+ {
+ // Read next reference component
+ // refComponentType unused for the moment
+ aDataSpaceStream.skip(sizeof(sal_uInt32));
+ sal_uInt32 aReferenceComponentNameLength = aDataSpaceStream.readuInt32();
+ // sReferenceComponentName unused for the moment
+ if (aDataSpaceStream.getRemaining() < aReferenceComponentNameLength)
+ {
+ bBroken = true;
+ break;
+ }
+ aDataSpaceStream.readUnicodeArray(aReferenceComponentNameLength / 2);
+ aDataSpaceStream.skip((4 - (aReferenceComponentNameLength & 3))
+ & 3); // Skip padding
+
+ bBroken |= aDataSpaceStream.isEof();
+ }
+
+ sal_uInt32 aDataSpaceNameLength = aDataSpaceStream.readuInt32();
+ if (aDataSpaceStream.getRemaining() < aDataSpaceNameLength)
+ {
+ bBroken = true;
+ break;
+ }
+ sDataSpaceName = aDataSpaceStream.readUnicodeArray(aDataSpaceNameLength / 2);
+ aDataSpaceStream.skip((4 - (aDataSpaceNameLength & 3)) & 3); // Skip padding
+
+ bBroken |= aDataSpaceStream.isEof();
+ }
+
+ if (bBroken)
+ {
+ SAL_WARN("oox", "EOF on parsing DataSpaceMapEntry table");
+ return false;
+ }
+ }
+ else
+ {
+ // Fallback for documents generated by LO: they sometimes do not have all
+ // required by MS-OFFCRYPTO specification streams (0x6DataSpaces/DataSpaceMap and others)
+ SAL_WARN("oox", "Encrypted package does not contain DataSpaceMap");
+ sDataSpaceName = "StrongEncryptionDataSpace";
+ }
+
+ uno::Sequence<uno::Any> aArguments;
+ mxPackageEncryption.set(
+ mxContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ "com.sun.star.comp.oox.crypto." + sDataSpaceName, aArguments, mxContext),
+ css::uno::UNO_QUERY);
+
+ if (!mxPackageEncryption.is())
+ {
+ // we do not know how to decrypt this document
+ return false;
+ }
+
+ return mxPackageEncryption->readEncryptionInfo(maStreamsSequence);
+}
+
+uno::Sequence<beans::NamedValue> DocumentDecryption::createEncryptionData(const OUString& rPassword)
+{
+ if (!mxPackageEncryption.is())
+ return uno::Sequence<beans::NamedValue>();
+
+ return mxPackageEncryption->createEncryptionData(rPassword);
+}
+
+bool DocumentDecryption::decrypt(const uno::Reference<io::XStream>& xDocumentStream)
+{
+ bool bResult = false;
+
+ if (!mrOleStorage.isStorage())
+ return false;
+
+ if (!mxPackageEncryption.is())
+ return false;
+
+ // open the required input streams in the encrypted package
+ uno::Reference<io::XInputStream> xEncryptedPackage
+ = mrOleStorage.openInputStream("EncryptedPackage");
+
+ // create temporary file for unencrypted package
+ uno::Reference<io::XOutputStream> xDecryptedPackage = xDocumentStream->getOutputStream();
+
+ bResult = mxPackageEncryption->decrypt(xEncryptedPackage, xDecryptedPackage);
+
+ css::uno::Reference<io::XSeekable> xSeekable(xDecryptedPackage, css::uno::UNO_QUERY);
+ xSeekable->seek(0);
+
+ if (bResult)
+ return mxPackageEncryption->checkDataIntegrity();
+
+ return bResult;
+}
+
+} // namespace oox::crypto
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/crypto/DocumentEncryption.cxx b/oox/source/crypto/DocumentEncryption.cxx
new file mode 100644
index 000000000..6b8854929
--- /dev/null
+++ b/oox/source/crypto/DocumentEncryption.cxx
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <oox/crypto/DocumentEncryption.hxx>
+
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/packages/XPackageEncryption.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <oox/helper/binaryoutputstream.hxx>
+#include <oox/ole/olestorage.hxx>
+#include <sal/log.hxx>
+
+namespace oox::crypto {
+
+using namespace css::io;
+using namespace css::uno;
+using namespace css::beans;
+
+DocumentEncryption::DocumentEncryption(const Reference< XComponentContext >& rxContext,
+ Reference<XStream> const & xDocumentStream,
+ oox::ole::OleStorage& rOleStorage,
+ const Sequence<NamedValue>& rMediaEncData)
+ : mxContext(rxContext)
+ , mxDocumentStream(xDocumentStream)
+ , mrOleStorage(rOleStorage)
+ , mMediaEncData(rMediaEncData)
+{
+ // Select engine
+ for (int i = 0; i < rMediaEncData.getLength(); i++)
+ {
+ if (rMediaEncData[i].Name == "CryptoType")
+ {
+ OUString sCryptoType;
+ rMediaEncData[i].Value >>= sCryptoType;
+
+ if (sCryptoType == "Standard")
+ sCryptoType = "StrongEncryptionDataSpace";
+
+ Sequence<Any> aArguments;
+ mxPackageEncryption.set(
+ mxContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ "com.sun.star.comp.oox.crypto." + sCryptoType, aArguments, mxContext), css::uno::UNO_QUERY);
+
+ if (!mxPackageEncryption.is())
+ {
+ SAL_WARN("oox", "Requested encryption method \"" << sCryptoType << "\" is not supported");
+ }
+
+ break;
+ }
+ }
+}
+
+bool DocumentEncryption::encrypt()
+{
+ if (!mxPackageEncryption.is())
+ return false;
+
+ Reference<XInputStream> xInputStream (mxDocumentStream->getInputStream(), UNO_SET_THROW);
+ Reference<XSeekable> xSeekable(xInputStream, UNO_QUERY);
+
+ if (!xSeekable.is())
+ return false;
+
+ xSeekable->seek(0); // seek to begin of the document stream
+
+ if (!mrOleStorage.isStorage())
+ return false;
+
+ mxPackageEncryption->setupEncryption(mMediaEncData);
+
+ Sequence<NamedValue> aStreams = mxPackageEncryption->encrypt(xInputStream);
+
+ for (const NamedValue & aStream : std::as_const(aStreams))
+ {
+ Reference<XOutputStream> xOutputStream(mrOleStorage.openOutputStream(aStream.Name), UNO_SET_THROW);
+ BinaryXOutputStream aBinaryOutputStream(xOutputStream, true);
+
+ css::uno::Sequence<sal_Int8> aStreamSequence;
+ aStream.Value >>= aStreamSequence;
+
+ aBinaryOutputStream.writeData(aStreamSequence);
+
+ aBinaryOutputStream.close();
+ }
+
+ return true;
+}
+
+} // namespace oox::crypto
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/crypto/Standard2007Engine.cxx b/oox/source/crypto/Standard2007Engine.cxx
new file mode 100644
index 000000000..c3b0efad9
--- /dev/null
+++ b/oox/source/crypto/Standard2007Engine.cxx
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <oox/crypto/Standard2007Engine.hxx>
+
+#include <oox/crypto/CryptTools.hxx>
+#include <oox/helper/binaryinputstream.hxx>
+#include <oox/helper/binaryoutputstream.hxx>
+#include <rtl/random.h>
+
+#include <comphelper/hash.hxx>
+
+namespace oox::crypto {
+
+/* =========================================================================== */
+/* Kudos to Caolan McNamara who provided the core decryption implementations. */
+/* =========================================================================== */
+namespace
+{
+
+void lclRandomGenerateValues(sal_uInt8* aArray, sal_uInt32 aSize)
+{
+ rtlRandomPool aRandomPool = rtl_random_createPool();
+ rtl_random_getBytes(aRandomPool, aArray, aSize);
+ rtl_random_destroyPool(aRandomPool);
+}
+
+constexpr OUStringLiteral lclCspName = u"Microsoft Enhanced RSA and AES Cryptographic Provider";
+constexpr const sal_uInt32 AES128Size = 16;
+
+} // end anonymous namespace
+
+bool Standard2007Engine::generateVerifier()
+{
+ // only support key of size 128 bit (16 byte)
+ if (mKey.size() != 16)
+ return false;
+
+ std::vector<sal_uInt8> verifier(msfilter::ENCRYPTED_VERIFIER_LENGTH);
+ std::vector<sal_uInt8> encryptedVerifier(msfilter::ENCRYPTED_VERIFIER_LENGTH);
+
+ lclRandomGenerateValues(verifier.data(), verifier.size());
+
+ std::vector<sal_uInt8> iv;
+ Encrypt aEncryptorVerifier(mKey, iv, Crypto::AES_128_ECB);
+ if (aEncryptorVerifier.update(encryptedVerifier, verifier) != msfilter::ENCRYPTED_VERIFIER_LENGTH)
+ return false;
+ std::copy(encryptedVerifier.begin(), encryptedVerifier.end(), mInfo.verifier.encryptedVerifier);
+
+ mInfo.verifier.encryptedVerifierHashSize = comphelper::SHA1_HASH_LENGTH;
+ std::vector<sal_uInt8> hash = comphelper::Hash::calculateHash(verifier.data(), verifier.size(), comphelper::HashType::SHA1);
+ hash.resize(comphelper::SHA256_HASH_LENGTH, 0);
+
+ std::vector<sal_uInt8> encryptedHash(comphelper::SHA256_HASH_LENGTH, 0);
+
+ Encrypt aEncryptorHash(mKey, iv, Crypto::AES_128_ECB);
+ aEncryptorHash.update(encryptedHash, hash, hash.size());
+ std::copy(encryptedHash.begin(), encryptedHash.end(), mInfo.verifier.encryptedVerifierHash);
+
+ return true;
+}
+
+bool Standard2007Engine::calculateEncryptionKey(const OUString& rPassword)
+{
+ sal_uInt32 saltSize = mInfo.verifier.saltSize;
+ sal_uInt32 passwordByteLength = rPassword.getLength() * 2;
+ const sal_uInt8* saltArray = mInfo.verifier.salt;
+
+ // Prepare initial data -> salt + password (in 16-bit chars)
+ std::vector<sal_uInt8> initialData(saltSize + passwordByteLength);
+ std::copy(saltArray, saltArray + saltSize, initialData.begin());
+
+ auto p = initialData.begin() + saltSize;
+ for (sal_Int32 i = 0; i != rPassword.getLength(); ++i) {
+ auto c = rPassword[i];
+ *p++ = c & 0xFF;
+ *p++ = c >> 8;
+ }
+
+ // use "hash" vector for result of sha1 hashing
+ // calculate SHA1 hash of initialData
+ std::vector<sal_uInt8> hash = comphelper::Hash::calculateHash(initialData.data(), initialData.size(), comphelper::HashType::SHA1);
+
+ // data = iterator (4bytes) + hash
+ std::vector<sal_uInt8> data(comphelper::SHA1_HASH_LENGTH + 4, 0);
+
+ for (sal_Int32 i = 0; i < 50000; ++i)
+ {
+ ByteOrderConverter::writeLittleEndian(data.data(), i);
+ std::copy(hash.begin(), hash.end(), data.begin() + 4);
+ hash = comphelper::Hash::calculateHash(data.data(), data.size(), comphelper::HashType::SHA1);
+ }
+ std::copy(hash.begin(), hash.end(), data.begin() );
+ std::fill(data.begin() + comphelper::SHA1_HASH_LENGTH, data.end(), 0 );
+
+ hash = comphelper::Hash::calculateHash(data.data(), data.size(), comphelper::HashType::SHA1);
+
+ // derive key
+ std::vector<sal_uInt8> buffer(64, 0x36);
+ for (size_t i = 0; i < hash.size(); ++i)
+ buffer[i] ^= hash[i];
+
+ hash = comphelper::Hash::calculateHash(buffer.data(), buffer.size(), comphelper::HashType::SHA1);
+ if (mKey.size() > hash.size())
+ return false;
+ std::copy(hash.begin(), hash.begin() + mKey.size(), mKey.begin());
+
+ return true;
+}
+
+bool Standard2007Engine::generateEncryptionKey(const OUString& password)
+{
+ mKey.clear();
+ /*
+ KeySize (4 bytes): An unsigned integer that specifies the number of bits in the encryption key.
+ MUST be a multiple of 8. MUST be one of the values in the following table:
+ Algorithm Value Comment
+ Any 0x00000000 Determined by Flags
+ RC4 0x00000028 – 0x00000080 (inclusive) 8-bit increments.
+ AES 0x00000080, 0x000000C0, 0x00000100 128, 192 or 256-bit
+ */
+ if (mInfo.header.keyBits > 8192) // should we strictly enforce the above 256 bit limit ?
+ return false;
+ mKey.resize(mInfo.header.keyBits / 8, 0);
+ if (mKey.empty())
+ return false;
+
+ calculateEncryptionKey(password);
+
+ std::vector<sal_uInt8> encryptedVerifier(msfilter::ENCRYPTED_VERIFIER_LENGTH);
+ std::copy(
+ mInfo.verifier.encryptedVerifier,
+ mInfo.verifier.encryptedVerifier + msfilter::ENCRYPTED_VERIFIER_LENGTH,
+ encryptedVerifier.begin());
+
+ std::vector<sal_uInt8> encryptedHash(comphelper::SHA256_HASH_LENGTH);
+ std::copy(
+ mInfo.verifier.encryptedVerifierHash,
+ mInfo.verifier.encryptedVerifierHash + comphelper::SHA256_HASH_LENGTH,
+ encryptedHash.begin());
+
+ std::vector<sal_uInt8> verifier(encryptedVerifier.size(), 0);
+ Decrypt::aes128ecb(verifier, encryptedVerifier, mKey);
+
+ std::vector<sal_uInt8> verifierHash(encryptedHash.size(), 0);
+ Decrypt::aes128ecb(verifierHash, encryptedHash, mKey);
+
+ std::vector<sal_uInt8> hash = comphelper::Hash::calculateHash(verifier.data(), verifier.size(), comphelper::HashType::SHA1);
+
+ return std::equal(hash.begin(), hash.end(), verifierHash.begin());
+}
+
+bool Standard2007Engine::decrypt(BinaryXInputStream& aInputStream,
+ BinaryXOutputStream& aOutputStream)
+{
+ sal_uInt32 totalSize = aInputStream.readuInt32(); // Document unencrypted size - 4 bytes
+ aInputStream.skip(4); // Reserved 4 Bytes
+
+ std::vector<sal_uInt8> iv;
+ Decrypt aDecryptor(mKey, iv, Crypto::AES_128_ECB);
+ std::vector<sal_uInt8> inputBuffer (4096);
+ std::vector<sal_uInt8> outputBuffer(4096);
+ sal_uInt32 inputLength;
+ sal_uInt32 outputLength;
+ sal_uInt32 remaining = totalSize;
+
+ while ((inputLength = aInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
+ {
+ outputLength = aDecryptor.update(outputBuffer, inputBuffer, inputLength);
+ sal_uInt32 writeLength = std::min(outputLength, remaining);
+ aOutputStream.writeMemory(outputBuffer.data(), writeLength);
+ remaining -= outputLength;
+ }
+ return true;
+}
+
+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;
+ mInfo.header.algIdHash = msfilter::ENCRYPT_HASH_SHA1;
+ mInfo.header.keyBits = msfilter::ENCRYPT_KEY_SIZE_AES_128;
+ mInfo.header.providedType = msfilter::ENCRYPT_PROVIDER_TYPE_AES;
+
+ lclRandomGenerateValues(mInfo.verifier.salt, mInfo.verifier.saltSize);
+ const sal_Int32 keyLength = mInfo.header.keyBits / 8;
+
+ mKey.clear();
+ mKey.resize(keyLength, 0);
+
+ if (!calculateEncryptionKey(password))
+ return false;
+
+ if (!generateVerifier())
+ return false;
+
+ return true;
+}
+
+void Standard2007Engine::writeEncryptionInfo(BinaryXOutputStream& rStream)
+{
+ rStream.WriteUInt32(msfilter::VERSION_INFO_2007_FORMAT);
+
+ sal_uInt32 cspNameSize = (lclCspName.getLength() * 2) + 2;
+
+ sal_uInt32 encryptionHeaderSize = static_cast<sal_uInt32>(sizeof(msfilter::EncryptionStandardHeader));
+
+ rStream.WriteUInt32(mInfo.header.flags);
+ sal_uInt32 headerSize = encryptionHeaderSize + cspNameSize;
+ rStream.WriteUInt32(headerSize);
+
+ rStream.WriteUInt32(mInfo.header.flags);
+ rStream.WriteUInt32(mInfo.header.sizeExtra);
+ rStream.WriteUInt32(mInfo.header.algId);
+ rStream.WriteUInt32(mInfo.header.algIdHash);
+ rStream.WriteUInt32(mInfo.header.keyBits);
+ rStream.WriteUInt32(mInfo.header.providedType);
+ rStream.WriteUInt32(mInfo.header.reserved1);
+ rStream.WriteUInt32(mInfo.header.reserved2);
+ rStream.writeUnicodeArray(lclCspName);
+ rStream.WriteUInt16(0);
+
+ rStream.WriteUInt32(mInfo.verifier.saltSize);
+ rStream.writeMemory(&mInfo.verifier.salt, sizeof mInfo.verifier.salt);
+ rStream.writeMemory(&mInfo.verifier.encryptedVerifier, sizeof mInfo.verifier.encryptedVerifier);
+ rStream.WriteUInt32(mInfo.verifier.encryptedVerifierHashSize);
+ rStream.writeMemory(
+ &mInfo.verifier.encryptedVerifierHash, sizeof mInfo.verifier.encryptedVerifierHash);
+}
+
+void Standard2007Engine::encrypt(const 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);
+
+ sal_uInt32 inputLength;
+ sal_uInt32 outputLength;
+
+ std::vector<sal_uInt8> iv;
+ Encrypt aEncryptor(mKey, iv, Crypto::AES_128_ECB);
+
+ while ((inputLength = aBinaryInputStream.readMemory(inputBuffer.data(), inputBuffer.size())) > 0)
+ {
+ // 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);
+ aBinaryOutputStream.writeMemory(outputBuffer.data(), outputLength);
+ }
+}
+
+bool Standard2007Engine::readEncryptionInfo(css::uno::Reference<css::io::XInputStream> & rxInputStream)
+{
+ BinaryXInputStream aBinaryStream(rxInputStream, false);
+
+ mInfo.header.flags = aBinaryStream.readuInt32();
+ if (getFlag(mInfo.header.flags, msfilter::ENCRYPTINFO_EXTERNAL))
+ return false;
+
+ sal_uInt32 nHeaderSize = aBinaryStream.readuInt32();
+
+ sal_uInt32 actualHeaderSize = sizeof(mInfo.header);
+
+ if (nHeaderSize < actualHeaderSize)
+ return false;
+
+ mInfo.header.flags = aBinaryStream.readuInt32();
+ mInfo.header.sizeExtra = aBinaryStream.readuInt32();
+ mInfo.header.algId = aBinaryStream.readuInt32();
+ mInfo.header.algIdHash = aBinaryStream.readuInt32();
+ mInfo.header.keyBits = aBinaryStream.readuInt32();
+ mInfo.header.providedType = aBinaryStream.readuInt32();
+ mInfo.header.reserved1 = aBinaryStream.readuInt32();
+ mInfo.header.reserved2 = aBinaryStream.readuInt32();
+
+ aBinaryStream.skip(nHeaderSize - actualHeaderSize);
+
+ mInfo.verifier.saltSize = aBinaryStream.readuInt32();
+ aBinaryStream.readArray(mInfo.verifier.salt, SAL_N_ELEMENTS(mInfo.verifier.salt));
+ aBinaryStream.readArray(mInfo.verifier.encryptedVerifier, SAL_N_ELEMENTS(mInfo.verifier.encryptedVerifier));
+ mInfo.verifier.encryptedVerifierHashSize = aBinaryStream.readuInt32();
+ aBinaryStream.readArray(mInfo.verifier.encryptedVerifierHash, SAL_N_ELEMENTS(mInfo.verifier.encryptedVerifierHash));
+
+ if (mInfo.verifier.saltSize != 16)
+ return false;
+
+ // check flags and algorithm IDs, required are AES128 and SHA-1
+ if (!getFlag(mInfo.header.flags, msfilter::ENCRYPTINFO_CRYPTOAPI))
+ return false;
+
+ if (!getFlag(mInfo.header.flags, msfilter::ENCRYPTINFO_AES))
+ return false;
+
+ // algorithm ID 0 defaults to AES128 too, if ENCRYPTINFO_AES flag is set
+ if (mInfo.header.algId != 0 && mInfo.header.algId != msfilter::ENCRYPT_ALGO_AES128)
+ return false;
+
+ // hash algorithm ID 0 defaults to SHA-1 too
+ if (mInfo.header.algIdHash != 0 && mInfo.header.algIdHash != msfilter::ENCRYPT_HASH_SHA1)
+ return false;
+
+ if (mInfo.verifier.encryptedVerifierHashSize != 20)
+ return false;
+
+ return !aBinaryStream.isEof();
+}
+
+} // namespace oox::crypto
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/crypto/StrongEncryptionDataSpace.cxx b/oox/source/crypto/StrongEncryptionDataSpace.cxx
new file mode 100644
index 000000000..fd1b823b8
--- /dev/null
+++ b/oox/source/crypto/StrongEncryptionDataSpace.cxx
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <oox/crypto/StrongEncryptionDataSpace.hxx>
+#include <oox/crypto/AgileEngine.hxx>
+#include <oox/crypto/Standard2007Engine.hxx>
+#include <oox/helper/binaryoutputstream.hxx>
+#include <oox/helper/binaryinputstream.hxx>
+#include <com/sun/star/io/SequenceInputStream.hpp>
+#include <com/sun/star/io/XSequenceOutputStream.hpp>
+
+#include <comphelper/sequenceashashmap.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace css;
+using namespace css::beans;
+using namespace css::io;
+using namespace css::lang;
+using namespace css::uno;
+
+namespace oox::crypto
+{
+StrongEncryptionDataSpace::StrongEncryptionDataSpace(const Reference<XComponentContext>& rxContext)
+ : mxContext(rxContext)
+ , mCryptoEngine(new Standard2007Engine)
+{
+}
+
+sal_Bool StrongEncryptionDataSpace::generateEncryptionKey(const OUString& rPassword)
+{
+ if (!mCryptoEngine)
+ return false;
+
+ return mCryptoEngine->generateEncryptionKey(rPassword);
+}
+
+sal_Bool StrongEncryptionDataSpace::checkDataIntegrity()
+{
+ if (!mCryptoEngine)
+ return false;
+
+ return mCryptoEngine->checkDataIntegrity();
+}
+
+sal_Bool StrongEncryptionDataSpace::decrypt(const Reference<XInputStream>& rxInputStream,
+ Reference<XOutputStream>& rxOutputStream)
+{
+ if (!mCryptoEngine)
+ return false;
+
+ BinaryXInputStream aInputStream(rxInputStream, true);
+ BinaryXOutputStream aOutputStream(rxOutputStream, true);
+
+ mCryptoEngine->decrypt(aInputStream, aOutputStream);
+
+ rxOutputStream->flush();
+ return true;
+}
+
+Reference<XInputStream> StrongEncryptionDataSpace::getStream(const Sequence<NamedValue>& rStreams,
+ std::u16string_view sStreamName)
+{
+ for (const auto& aStream : rStreams)
+ {
+ if (aStream.Name == sStreamName)
+ {
+ Sequence<sal_Int8> aSeq;
+ aStream.Value >>= aSeq;
+ Reference<XInputStream> aStream2(
+ io::SequenceInputStream::createStreamFromSequence(mxContext, aSeq),
+ UNO_QUERY_THROW);
+ return aStream2;
+ }
+ }
+ return nullptr;
+}
+
+sal_Bool StrongEncryptionDataSpace::readEncryptionInfo(const Sequence<NamedValue>& aStreams)
+{
+ Reference<XInputStream> xEncryptionInfo = getStream(aStreams, u"EncryptionInfo");
+ if (!xEncryptionInfo.is())
+ return false;
+
+ BinaryXInputStream aBinaryInputStream(xEncryptionInfo, true);
+ sal_uInt32 aVersion = aBinaryInputStream.readuInt32();
+
+ switch (aVersion)
+ {
+ case msfilter::VERSION_INFO_2007_FORMAT:
+ case msfilter::VERSION_INFO_2007_FORMAT_SP2:
+ mCryptoEngine.reset(new Standard2007Engine);
+ break;
+ case msfilter::VERSION_INFO_AGILE:
+ mCryptoEngine.reset(new AgileEngine());
+ break;
+ default:
+ break;
+ }
+
+ if (!mCryptoEngine)
+ return false;
+
+ return mCryptoEngine->readEncryptionInfo(xEncryptionInfo);
+}
+
+sal_Bool StrongEncryptionDataSpace::setupEncryption(const Sequence<NamedValue>& rMediaEncData)
+{
+ if (!mCryptoEngine)
+ return false;
+
+ OUString sPassword;
+ for (const auto& aParam : rMediaEncData)
+ {
+ if (aParam.Name == "OOXPassword")
+ {
+ aParam.Value >>= sPassword;
+ }
+ }
+
+ return mCryptoEngine->setupEncryption(sPassword);
+}
+
+Sequence<NamedValue> StrongEncryptionDataSpace::createEncryptionData(const OUString& rPassword)
+{
+ comphelper::SequenceAsHashMap aEncryptionData;
+ aEncryptionData["OOXPassword"] <<= rPassword;
+ aEncryptionData["CryptoType"] <<= OUString("StrongEncryptionDataSpace");
+
+ return aEncryptionData.getAsConstNamedValueList();
+}
+
+Sequence<NamedValue>
+StrongEncryptionDataSpace::encrypt(const Reference<XInputStream>& rxInputStream)
+{
+ if (!mCryptoEngine)
+ return Sequence<NamedValue>();
+
+ Reference<XSeekable> xSeekable(rxInputStream, UNO_QUERY);
+ if (!xSeekable.is())
+ return Sequence<NamedValue>();
+
+ sal_uInt32 aLength = xSeekable->getLength(); // check length of the stream
+
+ Reference<XOutputStream> xOutputStream(
+ mxContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.io.SequenceOutputStream", mxContext),
+ UNO_QUERY);
+
+ mCryptoEngine->encrypt(rxInputStream, xOutputStream, aLength);
+
+ comphelper::SequenceAsHashMap aStreams;
+
+ Reference<XSequenceOutputStream> xEncodedFileSequenceStream(xOutputStream, UNO_QUERY);
+ aStreams["EncryptedPackage"] <<= xEncodedFileSequenceStream->getWrittenBytes();
+
+ Reference<XOutputStream> aEncryptionInfoStream(
+ mxContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.io.SequenceOutputStream", mxContext),
+ UNO_QUERY);
+ BinaryXOutputStream rStream(aEncryptionInfoStream, false);
+ mCryptoEngine->writeEncryptionInfo(rStream);
+ aEncryptionInfoStream->flush();
+ Reference<XSequenceOutputStream> aEncryptionInfoSequenceStream(aEncryptionInfoStream,
+ UNO_QUERY);
+
+ aStreams["EncryptionInfo"] <<= aEncryptionInfoSequenceStream->getWrittenBytes();
+
+ return aStreams.getAsConstNamedValueList();
+}
+
+OUString SAL_CALL StrongEncryptionDataSpace::getImplementationName()
+{
+ return "com.sun.star.comp.oox.crypto.StrongEncryptionDataSpace";
+}
+
+sal_Bool SAL_CALL StrongEncryptionDataSpace::supportsService(const OUString& rServiceName)
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL StrongEncryptionDataSpace::getSupportedServiceNames()
+{
+ Sequence<OUString> aServices{ "com.sun.star.packages.PackageEncryption" };
+ return aServices;
+}
+
+} // namespace oox::crypto
+
+extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
+com_sun_star_comp_oox_crypto_StrongEncryptionDataSpace_get_implementation(
+ uno::XComponentContext* pCtx, uno::Sequence<uno::Any> const& /*rSeq*/)
+{
+ return cppu::acquire(new oox::crypto::StrongEncryptionDataSpace(pCtx));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */