summaryrefslogtreecommitdiffstats
path: root/oox/source/crypto/Standard2007Engine.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'oox/source/crypto/Standard2007Engine.cxx')
-rw-r--r--oox/source/crypto/Standard2007Engine.cxx333
1 files changed, 333 insertions, 0 deletions
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: */