/* -*- 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>
#include <utility>
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(css::uno::Reference<css::uno::XComponentContext> xContext,
oox::ole::OleStorage& rOleStorage)
: mxContext(std::move(xContext))
, 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(u"\006DataSpaces/DataSpaceMap"_ustr);
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(u"EncryptedPackage"_ustr);
// 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: */
↑ V530 The return value of function 'readUnicodeArray' is required to be utilized.