tdf#115933 XLSX import: fix permission for editing

The passwords for editing in XLSX documents
created with Excel weren't asked and verified.

Note: LibreOffice supports only a subset of the hashing
algorithms specified in MS-OE376, according to
DocPasswordHelper::GetOoxHashAsVector() and
https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oe376/f70a4140-340b-4e94-a604-dff25b9846b1.
Also the documents encrypted with unsupported algorithms
got edit protection now, but it's not possible to add
permission to edit them (copy of these documents are still
editable).

Change-Id: Iabc90f6bba4ed071dd2c60e9dea905481816964b
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/121497
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/comphelper/source/misc/docpasswordhelper.cxx b/comphelper/source/misc/docpasswordhelper.cxx
index cd70909..980faff 100644
--- a/comphelper/source/misc/docpasswordhelper.cxx
+++ b/comphelper/source/misc/docpasswordhelper.cxx
@@ -116,8 +116,7 @@ bool DocPasswordHelper::IsModifyPasswordCorrect( std::u16string_view aPassword, 
    if ( !aPassword.empty() && aInfo.hasElements() )
    {
        OUString sAlgorithm;
        uno::Sequence< sal_Int8 > aSalt;
        uno::Sequence< sal_Int8 > aHash;
        uno::Any aSalt, aHash;
        sal_Int32 nCount = 0;

        for ( const auto & prop : aInfo )
@@ -125,20 +124,43 @@ bool DocPasswordHelper::IsModifyPasswordCorrect( std::u16string_view aPassword, 
            if ( prop.Name == "algorithm-name" )
                prop.Value >>= sAlgorithm;
            else if ( prop.Name == "salt" )
                prop.Value >>= aSalt;
                aSalt = prop.Value;
            else if ( prop.Name == "iteration-count" )
                prop.Value >>= nCount;
            else if ( prop.Name == "hash" )
                prop.Value >>= aHash;
                aHash = prop.Value;
        }

        if ( sAlgorithm == "PBKDF2" && aSalt.hasElements() && nCount > 0 && aHash.hasElements() )
        if ( sAlgorithm == "PBKDF2" )
        {
            uno::Sequence< sal_Int8 > aNewHash = GeneratePBKDF2Hash( aPassword, aSalt, nCount, aHash.getLength() );
            for ( sal_Int32 nInd = 0; nInd < aNewHash.getLength() && nInd < aHash.getLength() && aNewHash[nInd] == aHash[nInd]; nInd ++ )
            uno::Sequence<sal_Int8> aIntSalt, aIntHash;
            aSalt >>= aIntSalt;
            aHash >>= aIntHash;
            if (aIntSalt.hasElements() && nCount > 0 && aIntHash.hasElements())
            {
                if ( nInd == aNewHash.getLength() - 1 && nInd == aHash.getLength() - 1 )
                    bResult = true;
                uno::Sequence<sal_Int8> aNewHash
                    = GeneratePBKDF2Hash(aPassword, aIntSalt, nCount, aIntHash.getLength());
                for (sal_Int32 nInd = 0; nInd < aNewHash.getLength() && nInd < aIntHash.getLength()
                                         && aNewHash[nInd] == aIntHash[nInd];
                     nInd++)
                {
                    if (nInd == aNewHash.getLength() - 1 && nInd == aIntHash.getLength() - 1)
                        bResult = true;
                }
            }
        }
        else if (nCount > 0)
        {
            OUString sSalt, sHash;
            aSalt >>= sSalt;
            aHash >>= sHash;
            if (!sSalt.isEmpty() && !sHash.isEmpty())
            {
                const OUString aNewHash(GetOoxHashAsBase64(OUString(aPassword), sSalt, nCount,
                                                           comphelper::Hash::IterCount::APPEND,
                                                           sAlgorithm));
                if (!aNewHash.isEmpty())
                    bResult = aNewHash == sHash;
            }
        }
    }
diff --git a/oox/source/token/properties.txt b/oox/source/token/properties.txt
index 808df85..2c56d0f 100644
--- a/oox/source/token/properties.txt
+++ b/oox/source/token/properties.txt
@@ -337,6 +337,7 @@ MirroredY
MissingValueTreatment
Model
ModifyPasswordHash
ModifyPasswordInfo
MoveProtect
MovingAveragePeriod
MultiLine
diff --git a/sc/qa/uitest/calc_tests9/tdf115933.py b/sc/qa/uitest/calc_tests9/tdf115933.py
new file mode 100644
index 0000000..be77194
--- /dev/null
+++ b/sc/qa/uitest/calc_tests9/tdf115933.py
@@ -0,0 +1,32 @@
# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
#
# 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/.
#
from uitest.framework import UITestCase
from uitest.uihelper.common import get_url_for_data_file
from libreoffice.uno.propertyvalue import mkPropertyValues

#Bug 115933 - XLSX <fileSharing> password protected with algorithmName, hashValue, saltValue and spinCount

class tdf115933(UITestCase):

    def test_tdf115933(self):
        with self.ui_test.load_file(get_url_for_data_file("tdf115933.xlsx")):
            #The document was created in Excel.
            calcDoc = self.xUITest.getTopFocusWindow()
            gridwin = calcDoc.getChild("grid_window")

            document = self.ui_test.get_component()

            self.assertTrue(document.isReadonly())

            #Without the fix in place, this dialog wouldn't have been displayed
            with self.ui_test.execute_dialog_through_action(gridwin, "TYPE", mkPropertyValues({"KEYCODE": "CTRL+SHIFT+M"})) as xDialog:
                xPassword = xDialog.getChild("newpassEntry")
                xPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "a"}))

            self.assertFalse(document.isReadonly())

# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sc/qa/uitest/data/tdf115933.xlsx b/sc/qa/uitest/data/tdf115933.xlsx
new file mode 100644
index 0000000..a6073a7
--- /dev/null
+++ b/sc/qa/uitest/data/tdf115933.xlsx
Binary files differ
diff --git a/sc/source/filter/oox/workbooksettings.cxx b/sc/source/filter/oox/workbooksettings.cxx
index a7ab887..ae2aa82 100644
--- a/sc/source/filter/oox/workbooksettings.cxx
+++ b/sc/source/filter/oox/workbooksettings.cxx
@@ -210,6 +210,21 @@ void WorkbookSettings::finalizeImport()
        if (maFileSharing.mbRecommendReadOnly || !maFileSharing.maHashValue.isEmpty())
            aSettingsProp.setProperty( PROP_LoadReadonly, true );

        if (!maFileSharing.maHashValue.isEmpty())
        {
            Sequence<PropertyValue> aResult;
            aResult.realloc(4);
            aResult[0].Name = "algorithm-name";
            aResult[0].Value <<= maFileSharing.maAlgorithmName;
            aResult[1].Name = "salt";
            aResult[1].Value <<= maFileSharing.maSaltValue;
            aResult[2].Name = "iteration-count";
            aResult[2].Value <<= maFileSharing.mnSpinCount;
            aResult[3].Name = "hash";
            aResult[3].Value <<= maFileSharing.maHashValue;
            aSettingsProp.setProperty(PROP_ModifyPasswordInfo, aResult);
        }

        if( maFileSharing.mnPasswordHash != 0 )
            aSettingsProp.setProperty( PROP_ModifyPasswordHash, static_cast< sal_Int32 >( maFileSharing.mnPasswordHash ) );
    }