tdf#105844 package,sfx2: wholesome ODF package wrapping encryption

Redo the ODF encryption by storing an ODF package and wrapping it as a
stream "encrypted-package" in another ODF package, such that there is
only one encrypted stream - this requires only one KDF computation.

* This is only enabled in Experimental mode for now.

* Avoid storing unencrypted data in the pTempFile of SfxMedium, as it
  is usually created in the same directory as the target file, which
  may be on a network share or similar less trusted location.

* SfxMedium::SetEncryptionDataToStorage_Impl() should just set an
  error status if it fails (how can it fail anyway)

* when loading a document, SfxDocPasswordVerifier extracts an encrypted
  inner package (by calling SfxMedium::TryEncryptedInnerPackage())

* SfxMedium::GetStorage() automatically decrypts an encrypted inner
  storage and sets it as the SfxMedium's xStorage

* when storing a document, SfxObjectShell::SaveTo_Impl() creates
  the wrapped storages

* One challenge is to keep the macro/scripting signature working; this
  can only be put in the inner storage, whereas the document signature
  should continue to be on the outer storage; also it must use a Zip
  storage, to see the "META-INF" directory.  This needs a new
  SfxMedium::GetScriptingStorageToSign_Impl() and changes in
  SfxMedium::SignContents_Impl().

Change-Id: Ibfee36ce3a9cd030f2aa2ce1484b6d001cba2389
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160401
Tested-by: Jenkins
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
diff --git a/include/sfx2/docfile.hxx b/include/sfx2/docfile.hxx
index 377bab62..fb8039f 100644
--- a/include/sfx2/docfile.hxx
+++ b/include/sfx2/docfile.hxx
@@ -70,7 +70,7 @@ class SFX2_DLLPUBLIC SfxMedium final : public SvRefBase
    SAL_DLLPRIVATE void CloseOutStream_Impl();
    SAL_DLLPRIVATE void CloseStreams_Impl(bool bInDestruction = false);

    SAL_DLLPRIVATE void SetEncryptionDataToStorage_Impl();
    SAL_DLLPRIVATE bool SetEncryptionDataToStorage_Impl();

public:

@@ -218,10 +218,14 @@ public:
    SAL_DLLPRIVATE OUString const & GetBackup_Impl();

    SAL_DLLPRIVATE css::uno::Reference< css::embed::XStorage > const & GetZipStorageToSign_Impl( bool bReadOnly = true );
    SAL_DLLPRIVATE css::uno::Reference<css::embed::XStorage> GetScriptingStorageToSign_Impl();
    SAL_DLLPRIVATE void CloseZipStorage_Impl();

    // the storage that will be returned by the medium on GetStorage request
    SAL_DLLPRIVATE void SetStorage_Impl( const css::uno::Reference< css::embed::XStorage >& xNewStorage );
    SAL_DLLPRIVATE void SetInnerStorage_Impl(const css::uno::Reference<css::embed::XStorage>& xStorage);
    SAL_DLLPRIVATE css::uno::Reference<css::embed::XStorage>
        TryEncryptedInnerPackage(css::uno::Reference<css::embed::XStorage> xStorage);

    SAL_DLLPRIVATE void CloseAndReleaseStreams_Impl();
    SAL_DLLPRIVATE void AddVersion_Impl( css::util::RevisionTag& rVersion );
diff --git a/package/source/zippackage/ZipPackage.cxx b/package/source/zippackage/ZipPackage.cxx
index 4dc2021..46e87f4 100644
--- a/package/source/zippackage/ZipPackage.cxx
+++ b/package/source/zippackage/ZipPackage.cxx
@@ -287,7 +287,9 @@ void ZipPackage::parseManifest()
                                    const sal_Int32 nStartKeyAlg = xml::crypto::DigestID::SHA256;
                                    pStream->SetImportedStartKeyAlgorithm( nStartKeyAlg );

                                    if ( !m_bHasEncryptedEntries && pStream->getName() == "content.xml" )
                                    if (!m_bHasEncryptedEntries
                                        && (pStream->getName() == "content.xml"
                                            || pStream->getName() == "encrypted-package"))
                                    {
                                        m_bHasEncryptedEntries = true;
                                        m_nChecksumDigestID = nDigestAlg;
@@ -336,7 +338,9 @@ void ZipPackage::parseManifest()
                                    pStream->SetToBeCompressed ( true );
                                    pStream->SetToBeEncrypted ( true );
                                    pStream->SetIsEncrypted ( true );
                                    if ( !m_bHasEncryptedEntries && pStream->getName() == "content.xml" )
                                    if (!m_bHasEncryptedEntries
                                        && (pStream->getName() == "content.xml"
                                            || pStream->getName() == "encrypted-package"))
                                    {
                                        m_bHasEncryptedEntries = true;
                                        m_nStartKeyGenerationID = nStartKeyAlg;
diff --git a/sfx2/source/appl/appopen.cxx b/sfx2/source/appl/appopen.cxx
index 8e069c1..9693a5f 100644
--- a/sfx2/source/appl/appopen.cxx
+++ b/sfx2/source/appl/appopen.cxx
@@ -115,16 +115,19 @@ namespace {
class SfxDocPasswordVerifier : public ::comphelper::IDocPasswordVerifier
{
public:
    explicit     SfxDocPasswordVerifier( const Reference< embed::XStorage >& rxStorage ) :
                            mxStorage( rxStorage ) {}
    explicit SfxDocPasswordVerifier(SfxMedium& rMedium)
        : m_rMedium(rMedium)
        , mxStorage(rMedium.GetStorage())
    {
    }

    virtual ::comphelper::DocPasswordVerifierResult
                        verifyPassword( const OUString& rPassword, uno::Sequence< beans::NamedValue >& o_rEncryptionData ) override;
    virtual ::comphelper::DocPasswordVerifierResult
                        verifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) override;


private:
    SfxMedium & m_rMedium;
    Reference< embed::XStorage > mxStorage;
};

@@ -147,9 +150,14 @@ private:
        // and immediately closed
        ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( mxStorage, rEncryptionData );

        mxStorage->openStreamElement(
        // for new ODF encryption, try to extract the encrypted inner package
        // (it will become the SfxObjectShell storage)
        if (!m_rMedium.TryEncryptedInnerPackage(mxStorage))
        {   // ... old ODF encryption:
            mxStorage->openStreamElement(
                "content.xml",
                embed::ElementModes::READ | embed::ElementModes::NOCREATE );
        }

        // no exception -> success
        eResult = ::comphelper::DocPasswordVerifierResult::OK;
@@ -247,7 +255,7 @@ ErrCode CheckPasswd_Impl
                                                     { "ForSalvage", css::uno::Any(true) } });
                        }

                        SfxDocPasswordVerifier aVerifier( xStorage );
                        SfxDocPasswordVerifier aVerifier(*pFile);
                        aEncryptionData = ::comphelper::DocPasswordHelper::requestAndVerifyDocPassword(
                            aVerifier, aEncryptionData, aPassword, xInteractionHandler, pFile->GetOrigURL(), comphelper::DocPasswordRequestType::Standard );

diff --git a/sfx2/source/doc/docfile.cxx b/sfx2/source/doc/docfile.cxx
index f4dd7b6..c801fb5 100644
--- a/sfx2/source/doc/docfile.cxx
+++ b/sfx2/source/doc/docfile.cxx
@@ -62,9 +62,11 @@
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/io/XTruncate.hpp>
#include <com/sun/star/io/XSeekable.hpp>
#include <com/sun/star/io/TempFile.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <com/sun/star/ucb/InsertCommandArgument.hpp>
#include <com/sun/star/ucb/NameClash.hpp>
#include <com/sun/star/util/XModifiable.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
@@ -374,6 +376,8 @@ public:
    bool m_bInCheckIn:1;
    bool m_bDisableFileSync = false;
    bool m_bNotifyWhenEditable = false;
    /// if true, xStorage is an inner package and not directly from xStream
    bool m_bODFWholesomeEncryption = false;

    OUString m_aName;
    OUString m_aLogicName;
@@ -410,6 +414,9 @@ public:
    uno::Reference<io::XStream> xStream;
    uno::Reference<io::XStream> m_xLockingStream;
    uno::Reference<task::XInteractionHandler> xInteraction;
    uno::Reference<io::XStream> m_xODFDecryptedInnerPackageStream;
    uno::Reference<embed::XStorage> m_xODFEncryptedOuterStorage;
    uno::Reference<embed::XStorage> m_xODFDecryptedInnerZipStorage;

    ErrCodeMsg  nLastStorageError;

@@ -670,6 +677,7 @@ bool SfxMedium::IsSkipImages() const

SvStream* SfxMedium::GetInStream()
{
    //assert(!pImpl->xStorage); // either SvStream or Storage
    if ( pImpl->m_pInStream )
        return pImpl->m_pInStream.get();

@@ -740,6 +748,7 @@ void SfxMedium::CloseInStream_Impl(bool bInDestruction)

SvStream* SfxMedium::GetOutStream()
{
    assert(!pImpl->xStorage); // either SvStream or Storage
    if ( !pImpl->m_pOutStream )
    {
        // Create a temp. file if there is none because we always
@@ -953,8 +962,11 @@ uno::Reference < embed::XStorage > SfxMedium::GetOutputStorage()

    // if the medium was constructed with a Storage: use this one, not a temp. storage
    // if a temporary storage already exists: use it
    if ( pImpl->xStorage.is() && ( pImpl->m_aLogicName.isEmpty() || pImpl->pTempFile ) )
    if (pImpl->xStorage.is()
        && (pImpl->m_bODFWholesomeEncryption || pImpl->m_aLogicName.isEmpty() || pImpl->pTempFile))
    {
        return pImpl->xStorage;
    }

    // if necessary close stream that was used for reading
    if ( pImpl->m_pInStream && !pImpl->m_pInStream->IsWritable() )
@@ -971,15 +983,15 @@ uno::Reference < embed::XStorage > SfxMedium::GetOutputStorage()
}


void SfxMedium::SetEncryptionDataToStorage_Impl()
bool SfxMedium::SetEncryptionDataToStorage_Impl()
{
    // in case media-descriptor contains password it should be used on opening
    if ( !pImpl->xStorage.is() || !pImpl->m_pSet )
        return;
        return false;

    uno::Sequence< beans::NamedValue > aEncryptionData;
    if ( !GetEncryptionData_Impl( pImpl->m_pSet.get(), aEncryptionData ) )
        return;
        return false;

    // replace the password with encryption data
    pImpl->m_pSet->ClearItem( SID_PASSWORD );
@@ -992,9 +1004,10 @@ void SfxMedium::SetEncryptionDataToStorage_Impl()
    catch( const uno::Exception& )
    {
        SAL_WARN( "sfx.doc", "It must be possible to set a common password for the storage" );
        // TODO/LATER: set the error code in case of problem
        // SetError(ERRCODE_IO_GENERAL);
        SetError(ERRCODE_IO_GENERAL);
        return false;
    }
    return true;
}

#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
@@ -1699,12 +1712,54 @@ SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand(bool bLoading, bool bN
#endif
}

// this either returns non-null or throws exception
uno::Reference<embed::XStorage>
SfxMedium::TryEncryptedInnerPackage(uno::Reference<embed::XStorage> const xStorage)
{
    uno::Reference<embed::XStorage> xRet;
    if (xStorage->hasByName("encrypted-package"))
    {
        uno::Reference<io::XStream> const
            xDecryptedInnerPackage = xStorage->openStreamElement(
                "encrypted-package",
                embed::ElementModes::READ | embed::ElementModes::NOCREATE);
        assert(xDecryptedInnerPackage.is()); // just for testing? not if wrong pwd
        // need a seekable stream => copy
        Reference<uno::XComponentContext> const xContext(::comphelper::getProcessComponentContext());
        uno::Reference<io::XStream> const xDecryptedInnerPackageStream(
            xContext->getServiceManager()->createInstanceWithContext(
                "com.sun.star.comp.MemoryStream", xContext),
            UNO_QUERY_THROW);
        comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackage->getInputStream(), xDecryptedInnerPackageStream->getOutputStream());
        xDecryptedInnerPackageStream->getOutputStream()->closeOutput();
#if 0
        // debug: dump to temp file
        uno::Reference<io::XTempFile> const xTempFile(io::TempFile::create(xContext), uno::UNO_SET_THROW);
        xTempFile->setRemoveFile(false);
        comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackageStream->getInputStream(), xTempFile->getOutputStream());
        xTempFile->getOutputStream()->closeOutput();
        SAL_DE BUG("AAA tempfile " << xTempFile->getResourceName());
        uno::Reference<io::XSeekable>(xDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
#endif
        // create storage, if this succeeds assume password is correct
        xRet = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
            PACKAGE_STORAGE_FORMAT_STRING, xDecryptedInnerPackageStream,
            embed::ElementModes::READWRITE, xContext, false);
        assert(xRet.is());
        pImpl->m_bODFWholesomeEncryption = true;
        pImpl->m_xODFDecryptedInnerPackageStream = xDecryptedInnerPackageStream;
        pImpl->m_xODFEncryptedOuterStorage = xStorage;
        pImpl->xStorage = xRet;
    }
    return xRet;
}

uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempFile )
{
    if ( pImpl->xStorage.is() || pImpl->m_bTriedStorage )
        return pImpl->xStorage;

    assert(!pImpl->m_pOutStream /*&& !pImpl->m_pInStream*/); // either SvStream or Storage
    uno::Sequence< uno::Any > aArgs( 2 );
    auto pArgs = aArgs.getArray();

@@ -1797,10 +1852,26 @@ uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempFile )

    pImpl->m_bTriedStorage = true;

    if (pImpl->xStorage.is())
    {
        pImpl->m_bODFWholesomeEncryption = false;
        if (SetEncryptionDataToStorage_Impl())
        {
            try
            {
                TryEncryptedInnerPackage(pImpl->xStorage);
            }
            catch (Exception const&)
            {
                TOOLS_WARN_EXCEPTION("sfx.doc", "exception from TryEncryptedInnerPackage: ");
                SetError(ERRCODE_IO_GENERAL);
            }
        }
    }

    // TODO/LATER: Get versionlist on demand
    if ( pImpl->xStorage.is() )
    {
        SetEncryptionDataToStorage_Impl();
        GetVersionList();
    }

@@ -1867,6 +1938,8 @@ uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempFile )
    if ( bResetStorage )
    {
        pImpl->xStorage.clear();
        pImpl->m_xODFDecryptedInnerPackageStream.clear();
        pImpl->m_xODFEncryptedOuterStorage.clear();
        if ( pImpl->m_pInStream )
            pImpl->m_pInStream->Seek( 0 );
    }
@@ -1875,7 +1948,39 @@ uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempFile )
    return pImpl->xStorage;
}

uno::Reference<embed::XStorage> SfxMedium::GetScriptingStorageToSign_Impl()
{
    // this was set when it was initially loaded
    if (pImpl->m_bODFWholesomeEncryption)
    {
        // (partial) scripting signature can only be in inner storage!
        // Note: a "PackageFormat" storage like pImpl->xStorage doesn't work
        // (even if it's not encrypted) because it hides the "META-INF" dir.
        // This "ZipFormat" storage is used only read-only; a writable one is
        // created manually in SignContents_Impl().
        if (!pImpl->m_xODFDecryptedInnerZipStorage.is())
        {
            GetStorage(false);
            // don't care about xStorage here because Zip is readonly
            SAL_WARN_IF(!pImpl->m_xODFDecryptedInnerPackageStream.is(), "sfx.doc", "no inner package stream?");
            if (pImpl->m_xODFDecryptedInnerPackageStream.is())
            {
                pImpl->m_xODFDecryptedInnerZipStorage =
                    ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
                        ZIP_STORAGE_FORMAT_STRING,
                        pImpl->m_xODFDecryptedInnerPackageStream->getInputStream());
            }
        }
        return pImpl->m_xODFDecryptedInnerZipStorage;
    }
    else
    {
        return GetZipStorageToSign_Impl(true);
    }
}

// note: currently nobody who calls this with "false" writes into an ODF
// storage that is returned here, that is only for OOXML
uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly )
{
    if ( !GetErrorIgnoreWarning() && !pImpl->m_xZipStorage.is() )
@@ -1919,6 +2024,7 @@ void SfxMedium::CloseZipStorage_Impl()

        pImpl->m_xZipStorage.clear();
    }
    pImpl->m_xODFDecryptedInnerZipStorage.clear();
}

void SfxMedium::CloseStorage()
@@ -1938,6 +2044,9 @@ void SfxMedium::CloseStorage()
        }

        pImpl->xStorage.clear();
        pImpl->m_xODFDecryptedInnerPackageStream.clear();
//        pImpl->m_xODFDecryptedInnerZipStorage.clear();
        pImpl->m_xODFEncryptedOuterStorage.clear();
        pImpl->bStorageBasedOnInStream = false;
    }

@@ -3632,12 +3741,17 @@ void SfxMedium::SetLoadTargetFrame(SfxFrame* pFrame )
    pImpl->wLoadTargetFrame = pFrame;
}


void SfxMedium::SetStorage_Impl( const uno::Reference < embed::XStorage >& rStor )
void SfxMedium::SetStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
{
    pImpl->xStorage = rStor;
    pImpl->xStorage = xStorage;
    pImpl->m_bODFWholesomeEncryption = false;
}

void SfxMedium::SetInnerStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
{
    pImpl->xStorage = xStorage;
    pImpl->m_bODFWholesomeEncryption = true;
}

SfxItemSet& SfxMedium::GetItemSet() const
{
@@ -4173,7 +4287,18 @@ bool SfxMedium::SignContents_Impl(weld::Window* pDialogParent,
        bool bODF = GetFilter()->IsOwnFormat();
        try
        {
            xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
            if (pImpl->m_bODFWholesomeEncryption && bSignScriptingContent)
            {
                assert(pImpl->xStorage); // GetStorage was called above
                assert(pImpl->m_xODFDecryptedInnerPackageStream);
                xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
                    ZIP_STORAGE_FORMAT_STRING, pImpl->m_xODFDecryptedInnerPackageStream);
            }
            else
            {
                xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
                    ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
            }
        }
        catch (const io::IOException&)
        {
@@ -4205,7 +4330,11 @@ bool SfxMedium::SignContents_Impl(weld::Window* pDialogParent,
                                                embed::ElementModes::READWRITE ),
                uno::UNO_SET_THROW );

            if ( xSigner->signScriptingContent( GetZipStorageToSign_Impl(), xStream ) )
            // note: the storage passed here must be independent from the
            // xWriteableZipStor because a writable storage can't have 2
            // instances of sub-storage for the same directory open, but with
            // independent storages it somehow works
            if (xSigner->signScriptingContent(GetScriptingStorageToSign_Impl(), xStream))
            {
                // remove the document signature if any
                OUString aDocSigName = xSigner->getDocumentContentSignatureDefaultStreamName();
@@ -4217,6 +4346,20 @@ bool SfxMedium::SignContents_Impl(weld::Window* pDialogParent,
                xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
                xTransact->commit();

                if (pImpl->m_bODFWholesomeEncryption)
                {   // manually copy the inner package to the outer one
                    uno::Reference<io::XSeekable>(pImpl->m_xODFDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
                    uno::Reference<io::XStream> const xEncryptedPackage =
                        pImpl->m_xODFEncryptedOuterStorage->openStreamElement(
                            "encrypted-package",
                            embed::ElementModes::WRITE|embed::ElementModes::TRUNCATE);
                    comphelper::OStorageHelper::CopyInputToOutput(pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), xEncryptedPackage->getOutputStream());
                    xTransact.set(pImpl->m_xODFEncryptedOuterStorage, uno::UNO_QUERY_THROW);
                    xTransact->commit(); // Commit() below won't do this
                }

                assert(!pImpl->xStorage.is() // ensure this doesn't overwrite
                    || !uno::Reference<util::XModifiable>(pImpl->xStorage, uno::UNO_QUERY_THROW)->isModified());
                // the temporary file has been written, commit it to the original file
                Commit();
                bChanges = true;
diff --git a/sfx2/source/doc/objmisc.cxx b/sfx2/source/doc/objmisc.cxx
index ede8983..5b7aa98 100644
--- a/sfx2/source/doc/objmisc.cxx
+++ b/sfx2/source/doc/objmisc.cxx
@@ -1924,7 +1924,7 @@ bool SfxObjectShell_Impl::hasTrustedScriptingSignature(
                    {
                        task::DocumentMacroConfirmationRequest aRequest;
                        aRequest.DocumentURL = getDocumentLocation();
                        aRequest.DocumentStorage = rDocShell.GetMedium()->GetZipStorageToSign_Impl();
                        aRequest.DocumentStorage = rDocShell.GetMedium()->GetScriptingStorageToSign_Impl();
                        aRequest.DocumentSignatureInformation = aInfo;
                        aRequest.DocumentVersion = aVersion;
                        aRequest.Classification = task::InteractionClassification_QUERY;
diff --git a/sfx2/source/doc/objserv.cxx b/sfx2/source/doc/objserv.cxx
index fcdc66f..5372807 100644
--- a/sfx2/source/doc/objserv.cxx
+++ b/sfx2/source/doc/objserv.cxx
@@ -1840,8 +1840,11 @@ uno::Sequence< security::DocumentSignatureInformation > SfxObjectShell::GetDocum
            }

            if ( bScriptingContent )
                aResult = xLocSigner->verifyScriptingContentSignatures( GetMedium()->GetZipStorageToSign_Impl(),
                                                                uno::Reference< io::XInputStream >() );
            {
                aResult = xLocSigner->verifyScriptingContentSignatures(
                    GetMedium()->GetScriptingStorageToSign_Impl(),
                    uno::Reference<io::XInputStream>());
            }
            else
            {
                if (GetMedium()->GetStorage(false).is())
@@ -2034,7 +2037,7 @@ bool SfxObjectShell::CheckIsReadonly(bool bSignScriptingContent, weld::Window* p
                xSigner->setParentWindow(pDialogParent->GetXWindow());

            if (bSignScriptingContent)
                xSigner->showScriptingContentSignatures(GetMedium()->GetZipStorageToSign_Impl(),
                xSigner->showScriptingContentSignatures(GetMedium()->GetScriptingStorageToSign_Impl(),
                                                        uno::Reference<io::XInputStream>());
            else
            {
diff --git a/sfx2/source/doc/objstor.cxx b/sfx2/source/doc/objstor.cxx
index b39b5d1..9ae2c8f 100644
--- a/sfx2/source/doc/objstor.cxx
+++ b/sfx2/source/doc/objstor.cxx
@@ -1150,6 +1150,9 @@ bool SfxObjectShell::SaveTo_Impl
    // tdf#41063, tdf#135244: prevent jumping to cursor at any temporary modification
    auto aViewGuard(LockAllViews());

    uno::Reference<uno::XComponentContext> const xContext(
        ::comphelper::getProcessComponentContext());

    std::shared_ptr<const SfxFilter> pFilter = rMedium.GetFilter();
    if ( !pFilter )
    {
@@ -1173,6 +1176,12 @@ bool SfxObjectShell::SaveTo_Impl
        return false;
    }

    SvtSaveOptions::ODFSaneDefaultVersion nVersion(SvtSaveOptions::ODFSVER_LATEST_EXTENDED);
    if (bOwnTarget && !utl::ConfigManager::IsFuzzing())
    {
        nVersion = GetODFSaneDefaultVersion();
    }

    bool bNeedsDisconnectionOnFail = false;

    bool bStoreToSameLocation = false;
@@ -1193,7 +1202,6 @@ bool SfxObjectShell::SaveTo_Impl
        if ( bTryToPreserveScriptSignature )
        {
            // check that the storage format stays the same
            SvtSaveOptions::ODFSaneDefaultVersion nVersion = GetODFSaneDefaultVersion();

            OUString aODFVersion;
            try
@@ -1220,6 +1228,33 @@ bool SfxObjectShell::SaveTo_Impl
        }
    }

    uno::Reference<io::XStream> xODFDecryptedInnerPackageStream;
    uno::Reference<embed::XStorage> xODFDecryptedInnerPackage;
    uno::Sequence<beans::NamedValue> aEncryptionData;
    if (GetEncryptionData_Impl(&rMedium.GetItemSet(), aEncryptionData))
    {
        assert(aEncryptionData.getLength() != 0);
        if (bOwnTarget && nVersion == SvtSaveOptions::ODFSVER_LATEST_EXTENDED
            && officecfg::Office::Common::Misc::ExperimentalMode::get())
        {
            // when embedded objects are stored here, it should be called from
            // this function for the root document and encryption data was cleared
            assert(GetCreateMode() != SfxObjectCreateMode::EMBEDDED);
            // clear now to store inner package (+ embedded objects) unencrypted
            rMedium.GetItemSet().ClearItem(SID_ENCRYPTIONDATA);
            rMedium.GetItemSet().ClearItem(SID_PASSWORD);
            xODFDecryptedInnerPackageStream.set(
                xContext->getServiceManager()->createInstanceWithContext(
                    "com.sun.star.comp.MemoryStream", xContext),
                UNO_QUERY_THROW);
            xODFDecryptedInnerPackage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
                PACKAGE_STORAGE_FORMAT_STRING, xODFDecryptedInnerPackageStream,
                css::embed::ElementModes::WRITE, xContext, false);
            assert(xODFDecryptedInnerPackage.is());
        }
    }

    bool isStreamAndInputStreamCleared(false);
    // use UCB for case sensitive/insensitive file name comparison
    if ( !pMedium->GetName().equalsIgnoreAsciiCase("private:stream")
      && !rMedium.GetName().equalsIgnoreAsciiCase("private:stream")
@@ -1280,6 +1315,7 @@ bool SfxObjectShell::SaveTo_Impl
              || ConnectTmpStorage_Impl( pMedium->GetStorage(), pMedium ) )
            {
                pMedium->CloseAndRelease();
                isStreamAndInputStreamCleared = true;

                // TODO/LATER: for now the medium must be closed since it can already contain streams from old medium
                //             in future those streams should not be copied in case a valid target url is provided,
@@ -1287,7 +1323,15 @@ bool SfxObjectShell::SaveTo_Impl
                //             reachable.
                rMedium.CloseAndRelease();
                rMedium.SetHasEmbeddedObjects(GetEmbeddedObjectContainer().HasEmbeddedObjects());
                rMedium.GetOutputStorage();
                if (xODFDecryptedInnerPackageStream.is())
                {
                    assert(!rMedium.GetItemSet().GetItem(SID_STREAM));
                    rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
                }
                else
                {
                    rMedium.GetOutputStorage();
                }
                rMedium.SetHasEmbeddedObjects(false);
            }
        }
@@ -1299,6 +1343,7 @@ bool SfxObjectShell::SaveTo_Impl

            pMedium->CloseAndRelease();
            rMedium.CloseAndRelease();
            isStreamAndInputStreamCleared = true;
            rMedium.CreateTempFileNoCopy();
            rMedium.GetOutStream();
        }
@@ -1310,7 +1355,16 @@ bool SfxObjectShell::SaveTo_Impl

            pMedium->CloseAndRelease();
            rMedium.CloseAndRelease();
            rMedium.GetOutputStorage();
            isStreamAndInputStreamCleared = true;
            if (xODFDecryptedInnerPackageStream.is())
            {
                assert(!rMedium.GetItemSet().GetItem(SID_STREAM));
                rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
            }
            else
            {
                rMedium.GetOutputStorage();
            }
        }
        else // means if ( bStorageBasedSource && !bStorageBasedTarget )
        {
@@ -1325,6 +1379,7 @@ bool SfxObjectShell::SaveTo_Impl
            {
                pMedium->CloseAndRelease();
                rMedium.CloseAndRelease();
                isStreamAndInputStreamCleared = true;
                rMedium.CreateTempFileNoCopy();
                rMedium.GetOutStream();
            }
@@ -1339,10 +1394,20 @@ bool SfxObjectShell::SaveTo_Impl
        // TODO/LATER: let the medium be prepared for alien formats as well

        rMedium.CloseAndRelease();
        isStreamAndInputStreamCleared = true;
        if ( bStorageBasedTarget )
        {
            rMedium.SetHasEmbeddedObjects(GetEmbeddedObjectContainer().HasEmbeddedObjects());
            rMedium.GetOutputStorage();
            if (xODFDecryptedInnerPackageStream.is())
            {
                assert(!rMedium.GetItemSet().GetItem(SID_STREAM));
                // this should set only xStorage, all of the streams remain null
                rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
            }
            else
            {
                rMedium.GetOutputStorage();
            }
            rMedium.SetHasEmbeddedObjects(false);
        }
    }
@@ -1354,6 +1419,9 @@ bool SfxObjectShell::SaveTo_Impl
        return false;
    }

    // these have been cleared on all paths that don't take above error return
    assert(isStreamAndInputStreamCleared); (void) isStreamAndInputStreamCleared;

    rMedium.LockOrigFileOnDemand( false, false );

    if ( bStorageBasedTarget )
@@ -1416,19 +1484,26 @@ bool SfxObjectShell::SaveTo_Impl
        }

        // transfer password from the parameters to the storage
        uno::Sequence< beans::NamedValue > aEncryptionData;
        bool bPasswdProvided = false;
        if ( GetEncryptionData_Impl( &rMedSet, aEncryptionData ) )
        if (aEncryptionData.getLength() != 0)
        {
            bPasswdProvided = true;
            try {
                ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( xMedStorage, aEncryptionData );
            if (xODFDecryptedInnerPackageStream.is())
            {
                bOk = true;
            }
            catch( uno::Exception& )
            else
            {
                SAL_WARN( "sfx.doc", "Setting of common encryption key failed!" );
                SetError(ERRCODE_IO_GENERAL);
                // TODO: GetStorage() already did that?
                try {
                    ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( xMedStorage, aEncryptionData );
                    bOk = true;
                }
                catch( uno::Exception& )
                {
                    SAL_WARN( "sfx.doc", "Setting of common encryption key failed!" );
                    SetError(ERRCODE_IO_GENERAL);
                }
            }
        }
        else
@@ -1576,6 +1651,13 @@ bool SfxObjectShell::SaveTo_Impl

    if ( bOk )
    {
        uno::Any mediaType;
        if (xODFDecryptedInnerPackageStream.is())
        {   // before the signature copy closes it
            mediaType = uno::Reference<beans::XPropertySet>(xODFDecryptedInnerPackage,
                uno::UNO_QUERY_THROW)->getPropertyValue("MediaType");
        }

        // if ODF version of oasis format changes on saving the signature should not be preserved
        if ( bTryToPreserveScriptSignature && bNoPreserveForOasis )
            bTryToPreserveScriptSignature = ( SotStorage::GetVersion( rMedium.GetStorage() ) == SOFFICE_FILEFORMAT_60 );
@@ -1610,14 +1692,26 @@ bool SfxObjectShell::SaveTo_Impl
                    rMedium.StorageCommit_Impl();
                    rMedium.CloseStorage();

                    uno::Reference< embed::XStorage > xReadOrig = pMedium->GetZipStorageToSign_Impl();
                    // signature must use Zip storage, not Package storage
                    uno::Reference<embed::XStorage> const xReadOrig(
                            pMedium->GetScriptingStorageToSign_Impl());
                    uno::Reference<embed::XStorage> xTarget;
                    if (xODFDecryptedInnerPackageStream.is())
                    {
                        xTarget = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
                            ZIP_STORAGE_FORMAT_STRING, xODFDecryptedInnerPackageStream);
                    }
                    else
                    {
                        xTarget = rMedium.GetZipStorageToSign_Impl(false);
                    }

                    if ( !xReadOrig.is() )
                        throw uno::RuntimeException();
                    uno::Reference< embed::XStorage > xMetaInf = xReadOrig->openStorageElement(
                                "META-INF",
                                embed::ElementModes::READ );

                    uno::Reference< embed::XStorage > xTarget = rMedium.GetZipStorageToSign_Impl( false );
                    if ( !xTarget.is() )
                        throw uno::RuntimeException();
                    uno::Reference< embed::XStorage > xTargetMetaInf = xTarget->openStorageElement(
@@ -1648,6 +1742,12 @@ bool SfxObjectShell::SaveTo_Impl
                            xTransact.set( xTarget, uno::UNO_QUERY );
                            if ( xTransact.is() )
                                xTransact->commit();
                            if (xODFDecryptedInnerPackageStream.is())
                            {   // recreate, to have it with copied sig
                                xODFDecryptedInnerPackage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
                                    PACKAGE_STORAGE_FORMAT_STRING, xODFDecryptedInnerPackageStream,
                                    css::embed::ElementModes::WRITE, xContext, false);
                            }
                        }
                        else
                        {
@@ -1665,6 +1765,49 @@ bool SfxObjectShell::SaveTo_Impl
            rMedium.CloseZipStorage_Impl();
        }

        if (xODFDecryptedInnerPackageStream.is())
        {
            rMedium.StorageCommit_Impl();
            // prevent dispose as inner storage will be needed later
            assert(!rMedium.WillDisposeStorageOnClose_Impl());
            rMedium.CloseStorage();
            // restore encryption for outer package, note: disable for debugging
            rMedium.GetItemSet().Put(SfxUnoAnyItem(SID_ENCRYPTIONDATA, uno::Any(aEncryptionData)));
            assert(xODFDecryptedInnerPackageStream.is());
            // now create the outer storage
            uno::Reference<embed::XStorage> const xOuterStorage(rMedium.GetOutputStorage());
            assert(xOuterStorage.is());
            // the outer storage needs the same properties as the inner one
            SetupStorage(xOuterStorage, SOFFICE_FILEFORMAT_CURRENT, false);

#if 0
            // does this need to happen here? - GetStorage already did it
            try {
                ::comphelper::OStorageHelper::SetCommonStorageEncryptionData(xOuterStorage, aEncryptionData);
            }
            catch (uno::Exception&)
            {
                SAL_WARN("sfx.doc", "Setting of common encryption key failed!");
                SetError(ERRCODE_IO_GENERAL);
                bOk = false;
            }
#endif

            uno::Reference<io::XStream> const xEncryptedInnerPackage =
                xOuterStorage->openStreamElement(
                    "encrypted-package", embed::ElementModes::WRITE);
            uno::Reference<beans::XPropertySet> const xEncryptedPackageProps(
                    xEncryptedInnerPackage, uno::UNO_QUERY_THROW);
            xEncryptedPackageProps->setPropertyValue("MediaType", mediaType);

            // encryption: just copy into package stream
            uno::Reference<io::XSeekable>(xODFDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
            comphelper::OStorageHelper::CopyInputToOutput(
                xODFDecryptedInnerPackageStream->getInputStream(),
                xEncryptedInnerPackage->getOutputStream());
            // rely on Commit() below
        }

        const OUString sName( rMedium.GetName( ) );
        bOk = rMedium.Commit();
        const OUString sNewName( rMedium.GetName( ) );
@@ -1672,6 +1815,11 @@ bool SfxObjectShell::SaveTo_Impl
        if ( sName != sNewName )
            GetMedium( )->SwitchDocumentToFile( sNewName );

        if (xODFDecryptedInnerPackageStream.is())
        {   // set the inner storage on the medium again, after Switch
            rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
        }

        if ( bOk )
        {
            // if the target medium is an alien format and the "old" medium was an own format and the "old" medium