CVE-2020-12801 tdf#93389: keep encryption information for autorecovered MS formats
authorMike Kaganski <mike.kaganski@collabora.com>
Fri, 3 Jan 2020 19:40:07 +0000 (22:40 +0300)
committerBastien Roucariès <rouca@debian.org>
Fri, 29 Dec 2023 09:39:36 +0000 (09:39 +0000)
The autorecovery data is stored in ODF, regardless of the original
document format. When restoring, type detection generates ODF data,
which is stored in the media descriptor attached to document, even
after real filter was restored (see AutoRecovery::implts_openDocs).
If real filter is not ODF, then at the save time, it doesn't find
necessary information in encryption data, and makes not encrypted
package.

This patch adds both MS binary data, and OOXML data, to existing
ODF data for recovered password-protected documents (regardless of
their real filter).

TODO: only add required information to encryption data: pass real
filter name to DocPasswordHelper::requestAndVerifyDocPassword from
AutoRecovery::implts_openDocs.

Reviewed-on: https://gerrit.libreoffice.org/c/core/+/86201
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit dd198398b6e5c84ab1255a90ef96e6445b66a64f)

Conflicts:
comphelper/source/misc/docpasswordhelper.cxx

Change-Id: I4717f067ad3c40167312b99eefef5584a467bfed
(cherry picked from commit 6017cdff264afc3b98beeba1330d6df28102fe7a)

origin: https://github.com/LibreOffice/core/commit/5b6c866d522e7eef636a5fa4048c39c1ea60e466.patch

Gbp-Pq: Name 0096-CVE-2020-12801-tdf-93389-keep-encryption-information.patch

comphelper/source/misc/docpasswordhelper.cxx
package/source/xstor/owriteablestream.cxx
package/source/xstor/owriteablestream.hxx
package/source/xstor/xstorage.cxx
sfx2/source/appl/appopen.cxx

index f75997cfe440d1275dfd0e87005c9df3eee1bc8d..1a4f29755c366a0a29f27cb614f77840c3a66647 100644 (file)
@@ -25,6 +25,7 @@
 #include <comphelper/storagehelper.hxx>
 #include <comphelper/hash.hxx>
 #include <comphelper/base64.hxx>
+#include <comphelper/propertysequence.hxx>
 #include <comphelper/sequence.hxx>
 #include <com/sun/star/beans/PropertyValue.hpp>
 #include <com/sun/star/task/XInteractionHandler.hpp>
@@ -429,6 +430,25 @@ OUString DocPasswordHelper::GetOoxHashAsBase64(
     OUString aPassword;
     DocPasswordVerifierResult eResult = DocPasswordVerifierResult::WrongPassword;
 
+    sal_Int32 nMediaEncDataCount = rMediaEncData.getLength();
+
+    // tdf#93389: if the document is being restored from autorecovery, we need to add encryption
+    // data also for real document type.
+    // TODO: get real filter name here (from CheckPasswd_Impl), to only add necessary data
+    bool bForSalvage = false;
+    if (nMediaEncDataCount)
+    {
+        for (auto& val : rMediaEncData)
+        {
+            if (val.Name == "ForSalvage")
+            {
+                --nMediaEncDataCount; // don't consider this element below
+                val.Value >>= bForSalvage;
+                break;
+            }
+        }
+    }
+
     // first, try provided default passwords
     if( pbIsDefaultPassword )
         *pbIsDefaultPassword = false;
@@ -453,7 +473,7 @@ OUString DocPasswordHelper::GetOoxHashAsBase64(
     // try media encryption data (skip, if result is OK or ABORT)
     if( eResult == DocPasswordVerifierResult::WrongPassword )
     {
-        if( rMediaEncData.getLength() > 0 )
+        if (nMediaEncDataCount)
         {
             eResult = rVerifier.verifyEncryptionData( rMediaEncData );
             if( eResult == DocPasswordVerifierResult::OK )
@@ -512,6 +532,26 @@ OUString DocPasswordHelper::GetOoxHashAsBase64(
             aEncData = comphelper::concatSequences(
                 aEncData, OStorageHelper::CreatePackageEncryptionData(aPassword));
         }
+
+        if (bForSalvage)
+        {
+            // TODO: add individual methods for different target filter, and only call what's needed
+
+            // 1. Prepare binary MS formats encryption data
+            auto aUniqueID = GenerateRandomByteSequence(16);
+            auto aEnc97Key = GenerateStd97Key(aPassword.getStr(), aUniqueID);
+            // 2. Add MS binary and OOXML encryption data to result
+            uno::Sequence< beans::NamedValue > aContainer(3);
+            aContainer[0].Name = "STD97EncryptionKey";
+            aContainer[0].Value <<= aEnc97Key;
+            aContainer[1].Name = "STD97UniqueID";
+            aContainer[1].Value <<= aUniqueID;
+            aContainer[2].Name = "OOXPassword";
+            aContainer[2].Value <<= aPassword;
+
+            aEncData = comphelper::concatSequences(
+                aEncData, aContainer);
+        }
     }
 
     return (eResult == DocPasswordVerifierResult::OK) ? aEncData : uno::Sequence< beans::NamedValue >();
index 2f49e497dd471cfc3966d959e150dcfa6b470d60..19e28e9dcb33d95b04c10f41f3e17b7673a52bed 100644 (file)
@@ -79,9 +79,11 @@ struct WSInternalData_Impl
 namespace package
 {
 
-bool PackageEncryptionDatasEqual( const ::comphelper::SequenceAsHashMap& aHash1, const ::comphelper::SequenceAsHashMap& aHash2 )
+bool PackageEncryptionDataLessOrEqual( const ::comphelper::SequenceAsHashMap& aHash1, const ::comphelper::SequenceAsHashMap& aHash2 )
 {
-    bool bResult = !aHash1.empty() && aHash1.size() == aHash2.size();
+    // tdf#93389: aHash2 may contain more than in aHash1, if it contains also data for other package
+    // formats (as in case of autorecovery)
+    bool bResult = !aHash1.empty() && aHash1.size() <= aHash2.size();
     for ( ::comphelper::SequenceAsHashMap::const_iterator aIter = aHash1.begin();
           bResult && aIter != aHash1.end();
           ++aIter )
@@ -1160,7 +1162,7 @@ uno::Reference< io::XStream > OWriteStream_Impl::GetStream( sal_Int32 nStreamMod
 
     if ( m_bHasCachedEncryptionData )
     {
-        if ( !::package::PackageEncryptionDatasEqual( m_aEncryptionData, aEncryptionData ) )
+        if ( !::package::PackageEncryptionDataLessOrEqual( m_aEncryptionData, aEncryptionData ) )
             throw packages::WrongPasswordException();
 
         // the correct key must be set already
index e3eeaf09d4fd2bbe480955e7762eb1e27021ade0..5501d6a4df47d8886a1fe7031b60009069c57605 100644 (file)
@@ -55,7 +55,8 @@ namespace com { namespace sun { namespace star { namespace uno {
 } } } }
 
 namespace package {
-    bool PackageEncryptionDatasEqual( const ::comphelper::SequenceAsHashMap& aHash1, const ::comphelper::SequenceAsHashMap& aHash2 );
+    // all data in aHash1 is contained in aHash2
+    bool PackageEncryptionDataLessOrEqual( const ::comphelper::SequenceAsHashMap& aHash1, const ::comphelper::SequenceAsHashMap& aHash2 );
 }
 
 struct WSInternalData_Impl;
index 0a43a07869f973d561dd30312d4eb2285b2293d9..1725b1627762dfd6c5b1f29166a5803260f723a0 100644 (file)
Binary files a/package/source/xstor/xstorage.cxx and b/package/source/xstor/xstorage.cxx differ
index 47ba55e34947b29bd5b4629ab79343bf83ad0f1f..bea5aba9fe4d3b42b3bf5ff270f8173b26b5076f 100644 (file)
@@ -44,6 +44,7 @@
 #include <comphelper/processfactory.hxx>
 #include <comphelper/storagehelper.hxx>
 #include <comphelper/string.hxx>
+#include <comphelper/sequence.hxx>
 #include <comphelper/synchronousdispatch.hxx>
 
 #include <vcl/wrkwin.hxx>
@@ -240,6 +241,21 @@ ErrCode CheckPasswd_Impl
                             if ( !aEncryptionData.hasElements() && aGpgProperties.hasElements() )
                                 aEncryptionData = ::comphelper::DocPasswordHelper::decryptGpgSession(aGpgProperties);
 
+                            // tdf#93389: if recoverying a document, encryption data should contain
+                            // entries for the real filter, not only for recovery ODF, to keep it
+                            // encrypted. Pass this in encryption data.
+                            // TODO: pass here the real filter (from AutoRecovery::implts_openDocs)
+                            // to marshal this to requestAndVerifyDocPassword
+                            if (pSet->GetItemState(SID_DOC_SALVAGE, false) == SfxItemState::SET)
+                            {
+                                uno::Sequence< beans::NamedValue > aContainer(1);
+                                aContainer[0].Name = "ForSalvage";
+                                aContainer[0].Value <<= true;
+
+                                aEncryptionData = comphelper::concatSequences(
+                                    aEncryptionData, aContainer);
+                            }
+
                             SfxDocPasswordVerifier aVerifier( xStorage );
                             aEncryptionData = ::comphelper::DocPasswordHelper::requestAndVerifyDocPassword(
                                 aVerifier, aEncryptionData, aPassword, xInteractionHandler, pFile->GetOrigURL(), comphelper::DocPasswordRequestType::Standard );