diff options
Diffstat (limited to '')
-rw-r--r-- | oox/source/crypto/AgileEngine.cxx | 843 | ||||
-rw-r--r-- | oox/source/crypto/CryptTools.cxx | 507 | ||||
-rw-r--r-- | oox/source/crypto/DocumentDecryption.cxx | 223 | ||||
-rw-r--r-- | oox/source/crypto/DocumentEncryption.cxx | 103 | ||||
-rw-r--r-- | oox/source/crypto/Standard2007Engine.cxx | 333 | ||||
-rw-r--r-- | oox/source/crypto/StrongEncryptionDataSpace.cxx | 203 |
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, ¶m); + } +}; +#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: */ |