Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/oox/source/crypto/DocumentDecryption.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 */
10
11
#include <oox/crypto/DocumentDecryption.hxx>
12
13
#include <comphelper/sequenceashashmap.hxx>
14
15
#include <com/sun/star/beans/NamedValue.hpp>
16
#include <com/sun/star/io/XSeekable.hpp>
17
#include <com/sun/star/io/XStream.hpp>
18
#include <com/sun/star/io/IOException.hpp>
19
#include <com/sun/star/uno/XComponentContext.hpp>
20
#include <com/sun/star/packages/XPackageEncryption.hpp>
21
#include <oox/ole/olestorage.hxx>
22
#include <oox/helper/binaryinputstream.hxx>
23
24
#include <sal/log.hxx>
25
#include <utility>
26
27
namespace
28
{
29
void lcl_getListOfStreams(oox::StorageBase* pStorage, std::vector<OUString>& rElementNames)
30
32.8k
{
31
32.8k
    std::vector<OUString> oElementNames;
32
32.8k
    pStorage->getElementNames(oElementNames);
33
32.8k
    for (const auto& sName : oElementNames)
34
96.3k
    {
35
96.3k
        oox::StorageRef rSubStorage = pStorage->openSubStorage(sName, false);
36
96.3k
        if (rSubStorage && rSubStorage->isStorage())
37
26.5k
        {
38
26.5k
            lcl_getListOfStreams(rSubStorage.get(), rElementNames);
39
26.5k
        }
40
69.7k
        else
41
69.7k
        {
42
69.7k
            if (pStorage->isRootStorage())
43
10.4k
                rElementNames.push_back(sName);
44
59.3k
            else
45
59.3k
                rElementNames.push_back(pStorage->getPath() + "/" + sName);
46
69.7k
        }
47
96.3k
    }
48
32.8k
}
49
}
50
51
namespace oox::crypto
52
{
53
using namespace css;
54
55
DocumentDecryption::DocumentDecryption(css::uno::Reference<css::uno::XComponentContext> xContext,
56
                                       oox::ole::OleStorage& rOleStorage)
57
6.22k
    : mxContext(std::move(xContext))
58
6.22k
    , mrOleStorage(rOleStorage)
59
6.22k
{
60
    // Get OLE streams into sequences for later use in CryptoEngine
61
6.22k
    std::vector<OUString> aStreamNames;
62
6.22k
    lcl_getListOfStreams(&mrOleStorage, aStreamNames);
63
64
6.22k
    comphelper::SequenceAsHashMap aStreamsData;
65
6.22k
    for (const auto& sStreamName : aStreamNames)
66
32.6k
    {
67
32.6k
        uno::Reference<io::XInputStream> xStream = mrOleStorage.openInputStream(sStreamName);
68
32.6k
        if (!xStream.is())
69
876
            throw io::IOException("Cannot open OLE input stream for " + sStreamName + "!");
70
71
31.7k
        BinaryXInputStream aBinaryInputStream(xStream, true);
72
73
31.7k
        css::uno::Sequence<sal_Int8> oData;
74
31.7k
        sal_Int32 nStreamSize = aBinaryInputStream.size();
75
31.7k
        sal_Int32 nReadBytes = aBinaryInputStream.readData(oData, nStreamSize);
76
77
31.7k
        if (nStreamSize != nReadBytes)
78
0
        {
79
0
            SAL_WARN("oox", "OLE stream invalid content");
80
0
            throw io::IOException("OLE stream invalid content for " + sStreamName + "!");
81
0
        }
82
83
31.7k
        aStreamsData[sStreamName] <<= oData;
84
31.7k
    }
85
5.34k
    maStreamsSequence = aStreamsData.getAsConstNamedValueList();
86
5.34k
}
87
88
bool DocumentDecryption::generateEncryptionKey(const OUString& rPassword)
89
46
{
90
46
    if (mxPackageEncryption.is())
91
46
        return mxPackageEncryption->generateEncryptionKey(rPassword);
92
0
    return false;
93
46
}
94
95
bool DocumentDecryption::readEncryptionInfo()
96
5.34k
{
97
5.34k
    if (!mrOleStorage.isStorage())
98
0
        return false;
99
100
    // Read 0x6DataSpaces/DataSpaceMap
101
5.34k
    uno::Reference<io::XInputStream> xDataSpaceMap
102
5.34k
        = mrOleStorage.openInputStream(u"\006DataSpaces/DataSpaceMap"_ustr);
103
5.34k
    OUString sDataSpaceName;
104
105
5.34k
    if (xDataSpaceMap.is())
106
288
    {
107
288
        bool bBroken = false;
108
109
288
        BinaryXInputStream aDataSpaceStream(xDataSpaceMap, true);
110
288
        sal_uInt32 aHeaderLength = aDataSpaceStream.readuInt32();
111
288
        SAL_WARN_IF(aHeaderLength != 8, "oox",
112
288
                    "DataSpaceMap length != 8 is not supported. Some content may be skipped");
113
288
        sal_uInt32 aEntryCount = aDataSpaceStream.readuInt32();
114
288
        SAL_WARN_IF(aEntryCount != 1, "oox",
115
288
                    "DataSpaceMap contains more than one entry. Some content may be skipped");
116
117
        // Read each DataSpaceMapEntry (MS-OFFCRYPTO 2.1.6.1)
118
2.74k
        for (sal_uInt32 i = 0; i < aEntryCount && !bBroken; i++)
119
2.52k
        {
120
            // entryLen unused for the moment
121
2.52k
            aDataSpaceStream.skip(sizeof(sal_uInt32));
122
123
            // Read each DataSpaceReferenceComponent (MS-OFFCRYPTO 2.1.6.2)
124
2.52k
            sal_uInt32 aReferenceComponentCount = aDataSpaceStream.readuInt32();
125
8.29k
            for (sal_uInt32 j = 0; j < aReferenceComponentCount && !bBroken; j++)
126
5.87k
            {
127
                // Read next reference component
128
                // refComponentType unused for the moment
129
5.87k
                aDataSpaceStream.skip(sizeof(sal_uInt32));
130
5.87k
                sal_uInt32 aReferenceComponentNameLength = aDataSpaceStream.readuInt32();
131
                // sReferenceComponentName unused for the moment
132
5.87k
                if (aDataSpaceStream.getRemaining() < aReferenceComponentNameLength)
133
101
                {
134
101
                    bBroken = true;
135
101
                    break;
136
101
                }
137
5.77k
                aDataSpaceStream.readUnicodeArray(aReferenceComponentNameLength / 2);
138
5.77k
                aDataSpaceStream.skip((4 - (aReferenceComponentNameLength & 3))
139
5.77k
                                      & 3); // Skip padding
140
141
5.77k
                bBroken |= aDataSpaceStream.isEof();
142
5.77k
            }
143
144
2.52k
            sal_uInt32 aDataSpaceNameLength = aDataSpaceStream.readuInt32();
145
2.52k
            if (aDataSpaceStream.getRemaining() < aDataSpaceNameLength)
146
68
            {
147
68
                bBroken = true;
148
68
                break;
149
68
            }
150
2.45k
            sDataSpaceName = aDataSpaceStream.readUnicodeArray(aDataSpaceNameLength / 2);
151
2.45k
            aDataSpaceStream.skip((4 - (aDataSpaceNameLength & 3)) & 3); // Skip padding
152
153
2.45k
            bBroken |= aDataSpaceStream.isEof();
154
2.45k
        }
155
156
288
        if (bBroken)
157
120
        {
158
120
            SAL_WARN("oox", "EOF on parsing DataSpaceMapEntry table");
159
120
            return false;
160
120
        }
161
288
    }
162
5.06k
    else
163
5.06k
    {
164
        // Fallback for documents generated by LO: they sometimes do not have all
165
        // required by MS-OFFCRYPTO specification streams (0x6DataSpaces/DataSpaceMap and others)
166
5.06k
        SAL_WARN("oox", "Encrypted package does not contain DataSpaceMap");
167
5.06k
        sDataSpaceName = "StrongEncryptionDataSpace";
168
5.06k
    }
169
170
5.22k
    uno::Sequence<uno::Any> aArguments;
171
5.22k
    mxPackageEncryption.set(
172
5.22k
        mxContext->getServiceManager()->createInstanceWithArgumentsAndContext(
173
5.22k
            "com.sun.star.comp.oox.crypto." + sDataSpaceName, aArguments, mxContext),
174
5.22k
        css::uno::UNO_QUERY);
175
176
5.22k
    if (!mxPackageEncryption.is())
177
49
    {
178
        // we do not know how to decrypt this document
179
49
        return false;
180
49
    }
181
182
5.18k
    return mxPackageEncryption->readEncryptionInfo(maStreamsSequence);
183
5.22k
}
184
185
uno::Sequence<beans::NamedValue> DocumentDecryption::createEncryptionData(const OUString& rPassword)
186
29
{
187
29
    if (!mxPackageEncryption.is())
188
0
        return uno::Sequence<beans::NamedValue>();
189
190
29
    return mxPackageEncryption->createEncryptionData(rPassword);
191
29
}
192
193
bool DocumentDecryption::decrypt(const uno::Reference<io::XStream>& xDocumentStream)
194
29
{
195
29
    bool bResult = false;
196
197
29
    if (!mrOleStorage.isStorage())
198
0
        return false;
199
200
29
    if (!mxPackageEncryption.is())
201
0
        return false;
202
203
    // open the required input streams in the encrypted package
204
29
    uno::Reference<io::XInputStream> xEncryptedPackage
205
29
        = mrOleStorage.openInputStream(u"EncryptedPackage"_ustr);
206
207
    // create temporary file for unencrypted package
208
29
    uno::Reference<io::XOutputStream> xDecryptedPackage = xDocumentStream->getOutputStream();
209
210
29
    bResult = mxPackageEncryption->decrypt(xEncryptedPackage, xDecryptedPackage);
211
212
29
    css::uno::Reference<io::XSeekable> xSeekable(xDecryptedPackage, css::uno::UNO_QUERY);
213
29
    xSeekable->seek(0);
214
215
29
    if (bResult)
216
29
        return mxPackageEncryption->checkDataIntegrity();
217
218
0
    return bResult;
219
29
}
220
221
} // namespace oox::crypto
222
223
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */