[Libreoffice-commits] core.git: Branch 'libreoffice-6-0' - comphelper/source include/comphelper package/inc package/source sfx2/source xmlsecurity/qa

Thorsten Behrens Thorsten.Behrens at CIB.de
Sat Jan 13 13:18:20 UTC 2018


 comphelper/source/misc/docpasswordhelper.cxx      |   96 ++++++++++++
 comphelper/source/misc/storagehelper.cxx          |    4 
 include/comphelper/docpasswordhelper.hxx          |    3 
 package/inc/ZipPackageEntry.hxx                   |    1 
 package/inc/ZipPackageFolder.hxx                  |    2 
 package/inc/ZipPackageStream.hxx                  |    1 
 package/source/manifest/ManifestImport.cxx        |  172 +++++++++++++++++++++-
 package/source/manifest/ManifestImport.hxx        |   25 +++
 package/source/xstor/xstorage.cxx                 |    4 
 package/source/zipapi/XUnbufferedStream.cxx       |    5 
 package/source/zipapi/ZipFile.cxx                 |    9 +
 package/source/zippackage/ZipPackage.cxx          |   62 +++++++
 package/source/zippackage/ZipPackageFolder.cxx    |   10 -
 package/source/zippackage/ZipPackageStream.cxx    |    3 
 sfx2/source/appl/appopen.cxx                      |    9 +
 xmlsecurity/qa/unit/signing/data/encryptedGPG.odt |binary
 xmlsecurity/qa/unit/signing/data/pubring.gpg      |binary
 xmlsecurity/qa/unit/signing/data/secring.gpg      |binary
 xmlsecurity/qa/unit/signing/data/trustdb.gpg      |binary
 xmlsecurity/qa/unit/signing/signing.cxx           |   13 +
 20 files changed, 403 insertions(+), 16 deletions(-)

New commits:
commit 01c7a60c051ac4562e3a317dde3c29c507f3f40b
Author: Thorsten Behrens <Thorsten.Behrens at CIB.de>
Date:   Sun Dec 10 23:40:00 2017 +0100

    tdf#114550: load back PGP encrypted files
    
    This squashes the following commits from master:
    
     gpg4libre: import PGP encryption manifest
     Change-Id: Iadd7f8f1194299cb50907d8594114c89c668ebd0
    
     gpg4libre: open encrypted files also via gpg
     Change-Id: I1f626143e6c8443b4ad0c4fc5bdbd5ab8d56a451
    
     tdf#114550 use 32 bit random session key for gpg encryption
     Change-Id: I7303be71fd855aa454d07fcae04d7f42e3c9cd9c
    
     tdf#114550 recognize sym key & init vec as valid f/ decrypt
     Change-Id: Ie366f086a3c14d6b54b91b4edee8cfef1a42c44b
    
     tdf#114550 don't use PBKDF2 in package for gpg encryption
     Change-Id: Ic96b2193f8541bbd109795fb9c0212a0a10c7344
    
     gpg4libre: add initial unit test for encryption
     Change-Id: Id782dd865878ae7b8a60c7c80821b1370f6ac7e7
    
    Change-Id: Id77b67a275bf91614ab62b65fdc69e4872247ffc
    Reviewed-on: https://gerrit.libreoffice.org/47784
    Tested-by: Jenkins <ci at libreoffice.org>
    Reviewed-by: Katarina Behrens <Katarina.Behrens at cib.de>
    Reviewed-by: Thorsten Behrens <Thorsten.Behrens at CIB.de>

diff --git a/comphelper/source/misc/docpasswordhelper.cxx b/comphelper/source/misc/docpasswordhelper.cxx
index 7d761bb9c740..81fe3bc5268c 100644
--- a/comphelper/source/misc/docpasswordhelper.cxx
+++ b/comphelper/source/misc/docpasswordhelper.cxx
@@ -17,11 +17,15 @@
  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
  */
 
+#include <config_gpgme.h>
+
 #include <algorithm>
 
 #include <comphelper/docpasswordhelper.hxx>
+#include <comphelper/storagehelper.hxx>
 #include <com/sun/star/beans/PropertyValue.hpp>
 #include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
 
 #include <osl/time.h>
 #include <osl/diagnose.h>
@@ -29,6 +33,13 @@
 #include <rtl/random.h>
 #include <string.h>
 
+#if HAVE_FEATURE_GPGME
+# include <gpgme.h>
+# include <context.h>
+# include <data.h>
+# include <decryptionresult.h>
+#endif
+
 using ::com::sun::star::uno::Sequence;
 using ::com::sun::star::uno::Exception;
 using ::com::sun::star::uno::Reference;
@@ -420,6 +431,91 @@ Sequence< sal_Int8 > DocPasswordHelper::GetXLHashAsSequence(
     return (eResult == DocPasswordVerifierResult::OK) ? aEncData : uno::Sequence< beans::NamedValue >();
 }
 
+/*static*/ uno::Sequence< css::beans::NamedValue >
+    DocPasswordHelper::decryptGpgSession(
+        const uno::Sequence< uno::Sequence< beans::NamedValue > >& rGpgProperties )
+{
+#if HAVE_FEATURE_GPGME
+    if ( !rGpgProperties.hasElements() )
+        return uno::Sequence< beans::NamedValue >();
+
+    uno::Sequence< beans::NamedValue > aEncryptionData(1);
+    std::unique_ptr<GpgME::Context> ctx;
+    GpgME::initializeLibrary();
+    GpgME::Error err = GpgME::checkEngine(GpgME::OpenPGP);
+    if (err)
+        throw uno::RuntimeException("The GpgME library failed to initialize for the OpenPGP protocol.");
+
+    ctx.reset( GpgME::Context::createForProtocol(GpgME::OpenPGP) );
+    if (ctx == nullptr)
+        throw uno::RuntimeException("The GpgME library failed to initialize for the OpenPGP protocol.");
+    ctx->setArmor(false);
+
+    const uno::Sequence < beans::NamedValue > *pSequence = rGpgProperties.getConstArray();
+    const sal_Int32 nLength = rGpgProperties.getLength();
+    for ( sal_Int32 i = 0; i < nLength ; i++, pSequence++ )
+    {
+        const beans::NamedValue *pValues = pSequence->getConstArray();
+        if ( pSequence->getLength() == 3 )
+        {
+            // take CipherValue and try to decrypt that - stop after
+            // the first successful decryption
+
+            // ctx is setup now, let's decrypt the lot!
+            uno::Sequence < sal_Int8 > aVector;
+            pValues[2].Value >>= aVector;
+
+            GpgME::Data cipher(
+                reinterpret_cast<const char*>(aVector.getConstArray()),
+                size_t(aVector.getLength()), false);
+            GpgME::Data plain;
+
+            GpgME::DecryptionResult crypt_res = ctx->decrypt(
+                cipher, plain);
+
+            // NO_SECKEY -> skip
+            // BAD_PASSPHRASE -> retry?
+
+            off_t result = plain.seek(0,SEEK_SET);
+            (void) result;
+            assert(result == 0);
+            int len=0, curr=0; char buf;
+            while( (curr=plain.read(&buf, 1)) )
+                len += curr;
+
+            if(crypt_res.error() || !len)
+                continue; // can't use this key, take next one
+
+            uno::Sequence < sal_Int8 > aKeyValue(len);
+            result = plain.seek(0,SEEK_SET);
+            assert(result == 0);
+            if( plain.read(aKeyValue.getArray(), len) != len )
+                throw uno::RuntimeException("The GpgME library failed to read the encrypted value.");
+
+            SAL_INFO("comphelper.crypto", "Extracted gpg session key of length: " << len);
+
+            aEncryptionData[0].Name = PACKAGE_ENCRYPTIONDATA_SHA256UTF8;
+            aEncryptionData[0].Value <<= aKeyValue;
+            break;
+        }
+    }
+
+    if ( aEncryptionData[0].Value.hasValue() )
+    {
+        uno::Sequence< beans::NamedValue > aContainer(2);
+        aContainer[0].Name = "GpgInfos";
+        aContainer[0].Value <<= rGpgProperties;
+        aContainer[1].Name = "EncryptionKey";
+        aContainer[1].Value <<= aEncryptionData;
+
+        return aContainer;
+    }
+#else
+    (void)rGpgProperties;
+#endif
+    return uno::Sequence< beans::NamedValue >();
+}
+
 } // namespace comphelper
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/comphelper/source/misc/storagehelper.cxx b/comphelper/source/misc/storagehelper.cxx
index d51055211e8a..27e72b784e10 100644
--- a/comphelper/source/misc/storagehelper.cxx
+++ b/comphelper/source/misc/storagehelper.cxx
@@ -442,8 +442,8 @@ uno::Sequence< beans::NamedValue > OStorageHelper::CreateGpgPackageEncryptionDat
     rtlRandomPool aRandomPool = rtl_random_createPool();
     rtl_random_addBytes(aRandomPool, &aTime, 8);
 
-    // get 16 random chars out of it
-    uno::Sequence < sal_Int8 > aVector(16);
+    // get 32 random chars out of it
+    uno::Sequence < sal_Int8 > aVector(32);
     rtl_random_getBytes( aRandomPool, aVector.getArray(), aVector.getLength() );
 
     rtl_random_destroyPool(aRandomPool);
diff --git a/include/comphelper/docpasswordhelper.hxx b/include/comphelper/docpasswordhelper.hxx
index 2ef3e040af1a..e420cf3e69af 100644
--- a/include/comphelper/docpasswordhelper.hxx
+++ b/include/comphelper/docpasswordhelper.hxx
@@ -277,6 +277,9 @@ public:
                             const ::std::vector< OUString >* pDefaultPasswords = nullptr,
                             bool* pbIsDefaultPassword = nullptr );
 
+    static css::uno::Sequence< css::beans::NamedValue > decryptGpgSession(
+        const css::uno::Sequence< css::uno::Sequence< css::beans::NamedValue > >& rGpgProperties);
+
 private:
                         ~DocPasswordHelper();
 };
diff --git a/package/inc/ZipPackageEntry.hxx b/package/inc/ZipPackageEntry.hxx
index 300b5f25ea67..5e0064f9bf7e 100644
--- a/package/inc/ZipPackageEntry.hxx
+++ b/package/inc/ZipPackageEntry.hxx
@@ -69,6 +69,7 @@ public:
                             std::vector < css::uno::Sequence < css::beans::PropertyValue > > &rManList,
                             ZipOutputStream & rZipOut,
                             const css::uno::Sequence < sal_Int8 >& rEncryptionKey,
+                            sal_Int32 nPBKDF2IterationCount,
                             const rtlRandomPool &rRandomPool ) = 0;
 
     void clearParent()
diff --git a/package/inc/ZipPackageFolder.hxx b/package/inc/ZipPackageFolder.hxx
index 0fad51f72ba2..1f65b2ca297e 100644
--- a/package/inc/ZipPackageFolder.hxx
+++ b/package/inc/ZipPackageFolder.hxx
@@ -73,6 +73,7 @@ public:
                             std::vector < css::uno::Sequence < css::beans::PropertyValue > > &rManList,
                             ZipOutputStream & rZipOut,
                             const css::uno::Sequence < sal_Int8 >& rEncryptionKey,
+                            sal_Int32 nPBKDF2IterationCount,
                             const rtlRandomPool &rRandomPool ) override;
 
     // Recursive functions
@@ -82,6 +83,7 @@ public:
             std::vector < css::uno::Sequence < css::beans::PropertyValue > > &rManList,
             ZipOutputStream & rZipOut,
             const css::uno::Sequence< sal_Int8 > &rEncryptionKey,
+            sal_Int32 nPBKDF2IterationCount,
             const rtlRandomPool & rRandomPool) const;
 
     // XNameContainer
diff --git a/package/inc/ZipPackageStream.hxx b/package/inc/ZipPackageStream.hxx
index d8f6903e35ac..cf8129e8d0a2 100644
--- a/package/inc/ZipPackageStream.hxx
+++ b/package/inc/ZipPackageStream.hxx
@@ -132,6 +132,7 @@ public:
                             std::vector < css::uno::Sequence < css::beans::PropertyValue > > &rManList,
                             ZipOutputStream & rZipOut,
                             const css::uno::Sequence < sal_Int8 >& rEncryptionKey,
+                            sal_Int32 nPBKDF2IterationCount,
                             const rtlRandomPool &rRandomPool ) override;
 
     void setZipEntryOnLoading( const ZipEntry &rInEntry);
diff --git a/package/source/manifest/ManifestImport.cxx b/package/source/manifest/ManifestImport.cxx
index 5ecd8c1cffcd..98a9d61128b5 100644
--- a/package/source/manifest/ManifestImport.cxx
+++ b/package/source/manifest/ManifestImport.cxx
@@ -34,6 +34,7 @@ using namespace std;
 
 ManifestImport::ManifestImport( vector < Sequence < PropertyValue > > & rNewManVector )
     : bIgnoreEncryptData    ( false )
+    , bPgpEncryption ( false )
     , nDerivedKeySize( 0 )
     , rManVector ( rNewManVector )
 
@@ -57,6 +58,17 @@ ManifestImport::ManifestImport( vector < Sequence < PropertyValue > > & rNewManV
     , sChecksumAttribute            ( ATTRIBUTE_CHECKSUM )
     , sChecksumTypeAttribute        ( ATTRIBUTE_CHECKSUM_TYPE )
 
+    , sKeyInfoElement               ( ELEMENT_ENCRYPTED_KEYINFO )
+    , sManifestKeyInfoElement       ( ELEMENT_MANIFEST_KEYINFO )
+    , sEncryptedKeyElement          ( ELEMENT_ENCRYPTEDKEY )
+    , sEncryptionMethodElement      ( ELEMENT_ENCRYPTIONMETHOD )
+    , sPgpDataElement               ( ELEMENT_PGPDATA )
+    , sPgpKeyIDElement              ( ELEMENT_PGPKEYID )
+    , sPGPKeyPacketElement          ( ELEMENT_PGPKEYPACKET )
+    , sAlgorithmAttribute           ( ATTRIBUTE_ALGORITHM )
+    , sCipherDataElement            ( ELEMENT_CIPHERDATA )
+    , sCipherValueElement           ( ELEMENT_CIPHERVALUE )
+
     , sFullPathProperty             ( "FullPath" )
     , sMediaTypeProperty            ( "MediaType" )
     , sVersionProperty              ( "Version" )
@@ -126,6 +138,80 @@ void ManifestImport::doFileEntry(StringHashMap &rConvertedAttribs)
     }
 }
 
+void ManifestImport::doKeyInfoEntry(StringHashMap &)
+{
+}
+
+void ManifestImport::doEncryptedKey(StringHashMap &)
+{
+    aKeyInfoSequence.clear();
+    aKeyInfoSequence.resize(3);
+}
+
+void ManifestImport::doEncryptionMethod(StringHashMap &rConvertedAttribs)
+{
+    OUString aString = rConvertedAttribs[sAlgorithmAttribute];
+    if ( aKeyInfoSequence.size() != 3
+         || aString != "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" )
+    {
+        bIgnoreEncryptData = true;
+    }
+}
+
+void ManifestImport::doEncryptedKeyInfo(StringHashMap &)
+{
+}
+
+void ManifestImport::doEncryptedCipherData(StringHashMap &)
+{
+}
+
+void ManifestImport::doEncryptedPgpData(StringHashMap &)
+{
+}
+
+void ManifestImport::doEncryptedCipherValue()
+{
+    if ( aKeyInfoSequence.size() == 3 )
+    {
+        aKeyInfoSequence[2].Name = "CipherValue";
+        uno::Sequence < sal_Int8 > aDecodeBuffer;
+        ::sax::Converter::decodeBase64(aDecodeBuffer, aCurrentCharacters);
+        aKeyInfoSequence[2].Value <<= aDecodeBuffer;
+        aCurrentCharacters = ""; // consumed
+    }
+    else
+        bIgnoreEncryptData = true;
+}
+
+void ManifestImport::doEncryptedKeyId()
+{
+    if ( aKeyInfoSequence.size() == 3 )
+    {
+        aKeyInfoSequence[0].Name = "KeyId";
+        uno::Sequence < sal_Int8 > aDecodeBuffer;
+        ::sax::Converter::decodeBase64(aDecodeBuffer, aCurrentCharacters);
+        aKeyInfoSequence[0].Value <<= aDecodeBuffer;
+        aCurrentCharacters = ""; // consumed
+    }
+    else
+        bIgnoreEncryptData = true;
+}
+
+void ManifestImport::doEncryptedKeyPacket()
+{
+    if ( aKeyInfoSequence.size() == 3 )
+    {
+        aKeyInfoSequence[1].Name = "KeyPacket";
+        uno::Sequence < sal_Int8 > aDecodeBuffer;
+        ::sax::Converter::decodeBase64(aDecodeBuffer, aCurrentCharacters);
+        aKeyInfoSequence[1].Value <<= aDecodeBuffer;
+        aCurrentCharacters = ""; // consumed
+    }
+    else
+        bIgnoreEncryptData = true;
+}
+
 void ManifestImport::doEncryptionData(StringHashMap &rConvertedAttribs)
 {
     // If this element exists, then this stream is encrypted and we need
@@ -214,6 +300,9 @@ void ManifestImport::doKeyDerivation(StringHashMap &rConvertedAttribs)
 
             aSequence[PKG_MNFST_DERKEYSIZE].Name = sDerivedKeySizeProperty;
             aSequence[PKG_MNFST_DERKEYSIZE].Value <<= nDerivedKeySize;
+        } else if ( bPgpEncryption ) {
+            if ( aString != "PGP" )
+                bIgnoreEncryptData = true;
         } else
             bIgnoreEncryptData = true;
     }
@@ -250,6 +339,8 @@ void SAL_CALL ManifestImport::startElement( const OUString& aName, const uno::Re
     case 2: {
         if (aConvertedName == sFileEntryElement) //manifest:file-entry
             doFileEntry(aConvertedAttribs);
+        else if (aConvertedName == sManifestKeyInfoElement) //loext:KeyInfo
+            doKeyInfoEntry(aConvertedAttribs);
         else
             aStack.back().m_bValid = false;
         break;
@@ -262,6 +353,8 @@ void SAL_CALL ManifestImport::startElement( const OUString& aName, const uno::Re
             aStack.back().m_bValid = false;
         else if (aConvertedName == sEncryptionDataElement)   //manifest:encryption-data
             doEncryptionData(aConvertedAttribs);
+        else if (aConvertedName == sEncryptedKeyElement)   //loext:encrypted-key
+            doEncryptedKey(aConvertedAttribs);
         else
             aStack.back().m_bValid = false;
         break;
@@ -278,6 +371,43 @@ void SAL_CALL ManifestImport::startElement( const OUString& aName, const uno::Re
             doKeyDerivation(aConvertedAttribs);
         else if (aConvertedName == sStartKeyAlgElement)   //manifest:start-key-generation
             doStartKeyAlg(aConvertedAttribs);
+        else if (aConvertedName == sEncryptionMethodElement)   //loext:encryption-method
+            doEncryptionMethod(aConvertedAttribs);
+        else if (aConvertedName == sKeyInfoElement)            //loext:KeyInfo
+            doEncryptedKeyInfo(aConvertedAttribs);
+        else if (aConvertedName == sCipherDataElement)            //loext:CipherData
+            doEncryptedCipherData(aConvertedAttribs);
+        else
+            aStack.back().m_bValid = false;
+        break;
+    }
+    case 5: {
+        ManifestStack::reverse_iterator aIter = aStack.rbegin();
+        ++aIter;
+
+        if (!aIter->m_bValid)
+            aStack.back().m_bValid = false;
+        else if (aConvertedName == sPgpDataElement)   //loext:PGPData
+            doEncryptedPgpData(aConvertedAttribs);
+        else if (aConvertedName == sCipherValueElement) //loext:CipherValue
+            // ciphervalue action happens on endElement
+            aCurrentCharacters = "";
+        else
+            aStack.back().m_bValid = false;
+        break;
+    }
+    case 6: {
+        ManifestStack::reverse_iterator aIter = aStack.rbegin();
+        ++aIter;
+
+        if (!aIter->m_bValid)
+            aStack.back().m_bValid = false;
+        else if (aConvertedName == sPgpKeyIDElement)   //loext:PGPKeyID
+            // ciphervalue action happens on endElement
+            aCurrentCharacters = "";
+        else if (aConvertedName == sPGPKeyPacketElement) //loext:PGPKeyPacket
+            // ciphervalue action happens on endElement
+            aCurrentCharacters = "";
         else
             aStack.back().m_bValid = false;
         break;
@@ -298,9 +428,19 @@ bool isEmpty(const css::beans::PropertyValue &rProp)
 
 void SAL_CALL ManifestImport::endElement( const OUString& aName )
 {
+    size_t nLevel = aStack.size();
+
+    assert(nLevel >= 1);
+
     OUString aConvertedName = ConvertName( aName );
     if ( !aStack.empty() && aStack.rbegin()->m_aConvertedName == aConvertedName ) {
         if ( aConvertedName == sFileEntryElement && aStack.back().m_bValid ) {
+            // root folder gets KeyInfo entry if any, for PGP encryption
+            if (!bIgnoreEncryptData && !aKeys.empty() && aSequence[PKG_MNFST_FULLPATH].Value.get<OUString>() == "/" )
+            {
+                aSequence[PKG_SIZE_NOENCR_MNFST].Name = "KeyInfo";
+                aSequence[PKG_SIZE_NOENCR_MNFST].Value <<= comphelper::containerToSequence(aKeys);
+            }
             css::beans::PropertyValue aEmpty;
             aSequence.erase(std::remove_if(aSequence.begin(), aSequence.end(),
                                            isEmpty), aSequence.end());
@@ -310,13 +450,43 @@ void SAL_CALL ManifestImport::endElement( const OUString& aName )
 
             aSequence.clear();
         }
+        else if ( aConvertedName == sEncryptedKeyElement && aStack.back().m_bValid ) {
+            if ( !bIgnoreEncryptData )
+            {
+                aKeys.push_back( comphelper::containerToSequence(aKeyInfoSequence) );
+                bPgpEncryption = true;
+            }
+            aKeyInfoSequence.clear();
+        }
+
+        // end element handling for elements with cdata
+        switch (nLevel) {
+            case 5: {
+                if (aConvertedName == sCipherValueElement) //loext:CipherValue
+                    doEncryptedCipherValue();
+                else
+                    aStack.back().m_bValid = false;
+                break;
+            }
+            case 6: {
+                if (aConvertedName == sPgpKeyIDElement)   //loext:PGPKeyID
+                    doEncryptedKeyId();
+                else if (aConvertedName == sPGPKeyPacketElement) //loext:PGPKeyPacket
+                    doEncryptedKeyPacket();
+                else
+                    aStack.back().m_bValid = false;
+                break;
+            }
+        }
 
         aStack.pop_back();
+        return;
     }
 }
 
-void SAL_CALL ManifestImport::characters( const OUString& /*aChars*/ )
+void SAL_CALL ManifestImport::characters( const OUString& aChars )
 {
+    aCurrentCharacters += aChars;
 }
 
 void SAL_CALL ManifestImport::ignorableWhitespace( const OUString& /*aWhitespaces*/ )
diff --git a/package/source/manifest/ManifestImport.hxx b/package/source/manifest/ManifestImport.hxx
index 86cafa4ef1d7..26f692be9c5b 100644
--- a/package/source/manifest/ManifestImport.hxx
+++ b/package/source/manifest/ManifestImport.hxx
@@ -22,6 +22,7 @@
 
 #include <cppuhelper/implbase.hxx>
 #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+#include <com/sun/star/beans/NamedValue.hpp>
 #include <vector>
 
 #include <HashMaps.hxx>
@@ -50,9 +51,13 @@ typedef ::std::vector< ManifestScopeEntry > ManifestStack;
 
 class ManifestImport final : public cppu::WeakImplHelper < css::xml::sax::XDocumentHandler >
 {
+    std::vector< css::beans::NamedValue > aKeyInfoSequence;
+    std::vector< css::uno::Sequence< css::beans::NamedValue > > aKeys;
     std::vector< css::beans::PropertyValue > aSequence;
+    OUString aCurrentCharacters;
     ManifestStack aStack;
     bool bIgnoreEncryptData;
+    bool bPgpEncryption;
     sal_Int32 nDerivedKeySize;
     ::std::vector < css::uno::Sequence < css::beans::PropertyValue > > & rManVector;
 
@@ -76,6 +81,17 @@ class ManifestImport final : public cppu::WeakImplHelper < css::xml::sax::XDocum
     const OUString sChecksumAttribute;
     const OUString sChecksumTypeAttribute;
 
+    const OUString sKeyInfoElement;
+    const OUString sManifestKeyInfoElement;
+    const OUString sEncryptedKeyElement;
+    const OUString sEncryptionMethodElement;
+    const OUString sPgpDataElement;
+    const OUString sPgpKeyIDElement;
+    const OUString sPGPKeyPacketElement;
+    const OUString sAlgorithmAttribute;
+    const OUString sCipherDataElement;
+    const OUString sCipherValueElement;
+
     const OUString sFullPathProperty;
     const OUString sMediaTypeProperty;
     const OUString sVersionProperty;
@@ -136,6 +152,15 @@ private:
     void doKeyDerivation(StringHashMap &rConvertedAttribs);
     /// @throws css::uno::RuntimeException
     void doStartKeyAlg(StringHashMap &rConvertedAttribs);
+    void doKeyInfoEntry(StringHashMap &);
+    void doEncryptedKey(StringHashMap &);
+    void doEncryptionMethod(StringHashMap &);
+    void doEncryptedKeyInfo(StringHashMap &);
+    void doEncryptedCipherData(StringHashMap &);
+    void doEncryptedPgpData(StringHashMap &);
+    void doEncryptedCipherValue();
+    void doEncryptedKeyId();
+    void doEncryptedKeyPacket();
 };
 #endif
 
diff --git a/package/source/xstor/xstorage.cxx b/package/source/xstor/xstorage.cxx
index 41d65fb97b7c..5c494a586197 100644
--- a/package/source/xstor/xstorage.cxx
+++ b/package/source/xstor/xstorage.cxx
@@ -4386,7 +4386,8 @@ void SAL_CALL OStorage::setPropertyValue( const OUString& aPropertyName, const u
                                     || aPropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY
                                     || aPropertyName == IS_INCONSISTENT_PROPERTY
                                     || aPropertyName == "URL"
-                                    || aPropertyName == "RepairPackage" ) )
+                                    || aPropertyName == "RepairPackage"
+                                    || aPropertyName == ENCRYPTION_GPG_PROPERTIES) )
            || aPropertyName == "IsRoot"
            || aPropertyName == MEDIATYPE_FALLBACK_USED_PROPERTY )
             throw beans::PropertyVetoException( THROW_WHERE );
@@ -4505,6 +4506,7 @@ uno::Any SAL_CALL OStorage::getPropertyValue( const OUString& aPropertyName )
         else if ( m_pData->m_nStorageType == embed::StorageFormats::PACKAGE
           && ( aPropertyName == HAS_ENCRYPTED_ENTRIES_PROPERTY
             || aPropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY
+            || aPropertyName == ENCRYPTION_GPG_PROPERTIES
             || aPropertyName == IS_INCONSISTENT_PROPERTY ) )
         {
             try {
diff --git a/package/source/zipapi/XUnbufferedStream.cxx b/package/source/zipapi/XUnbufferedStream.cxx
index 162bab6d7580..d20b46195c3a 100644
--- a/package/source/zipapi/XUnbufferedStream.cxx
+++ b/package/source/zipapi/XUnbufferedStream.cxx
@@ -78,7 +78,10 @@ XUnbufferedStream::XUnbufferedStream(
     if (mnZipSize < 0)
         throw ZipIOException("The stream seems to be broken!");
 
-    bool bHaveEncryptData = rData.is() && rData->m_aSalt.getLength() && rData->m_aInitVector.getLength() && rData->m_nIterationCount != 0;
+    bool bHaveEncryptData = rData.is() && rData->m_aInitVector.getLength() &&
+        ((rData->m_aSalt.getLength() && rData->m_nIterationCount != 0)
+         ||
+         rData->m_aKey.getLength());
     bool bMustDecrypt = nStreamMode == UNBUFF_STREAM_DATA && bHaveEncryptData && bIsEncrypted;
 
     if ( bMustDecrypt )
diff --git a/package/source/zipapi/ZipFile.cxx b/package/source/zipapi/ZipFile.cxx
index de4c1a364acd..f772287eeaf8 100644
--- a/package/source/zipapi/ZipFile.cxx
+++ b/package/source/zipapi/ZipFile.cxx
@@ -161,7 +161,14 @@ uno::Reference< xml::crypto::XCipherContext > ZipFile::StaticGetCipher( const un
         }
 
         uno::Sequence< sal_Int8 > aDerivedKey( xEncryptionData->m_nDerivedKeySize );
-        if ( rtl_Digest_E_None != rtl_digest_PBKDF2( reinterpret_cast< sal_uInt8* >( aDerivedKey.getArray() ),
+        if ( !xEncryptionData->m_nIterationCount &&
+             xEncryptionData->m_nDerivedKeySize == xEncryptionData->m_aKey.getLength() )
+        {
+            // gpg4libre: no need to derive key, m_aKey is already
+            // usable as symmetric session key
+            aDerivedKey = xEncryptionData->m_aKey;
+        }
+        else if ( rtl_Digest_E_None != rtl_digest_PBKDF2( reinterpret_cast< sal_uInt8* >( aDerivedKey.getArray() ),
                             aDerivedKey.getLength(),
                             reinterpret_cast< const sal_uInt8 * > (xEncryptionData->m_aKey.getConstArray() ),
                             xEncryptionData->m_aKey.getLength(),
diff --git a/package/source/zippackage/ZipPackage.cxx b/package/source/zippackage/ZipPackage.cxx
index 5e61d4678490..9a4853f439c6 100644
--- a/package/source/zippackage/ZipPackage.cxx
+++ b/package/source/zippackage/ZipPackage.cxx
@@ -198,12 +198,14 @@ void ZipPackage::parseManifest()
                         const OUString sPropDigestAlgorithm ("DigestAlgorithm");
                         const OUString sPropEncryptionAlgorithm ("EncryptionAlgorithm");
                         const OUString sPropStartKeyAlgorithm ("StartKeyAlgorithm");
+                        const OUString sKeyInfo ("KeyInfo");
 
                         uno::Sequence < uno::Sequence < PropertyValue > > aManifestSequence = xReader->readManifestSequence ( xSink->getInputStream() );
                         sal_Int32 nLength = aManifestSequence.getLength();
                         const uno::Sequence < PropertyValue > *pSequence = aManifestSequence.getConstArray();
                         ZipPackageStream *pStream = nullptr;
                         ZipPackageFolder *pFolder = nullptr;
+                        const Any *pKeyInfo = nullptr;
 
                         for ( sal_Int32 i = 0; i < nLength ; i++, pSequence++ )
                         {
@@ -236,6 +238,8 @@ void ZipPackage::parseManifest()
                                     pStartKeyAlg = &( pValue[j].Value );
                                 else if ( pValue[j].Name == sPropDerivedKeySize )
                                     pDerivedKeySize = &( pValue[j].Value );
+                                else if ( pValue[j].Name == sKeyInfo )
+                                    pKeyInfo = &( pValue[j].Value );
                             }
 
                             if ( !sPath.isEmpty() && hasByHierarchicalName ( sPath ) )
@@ -256,7 +260,51 @@ void ZipPackage::parseManifest()
                                     pStream->SetMediaType ( sMediaType );
                                     pStream->SetFromManifest( true );
 
-                                    if ( pSalt && pVector && pCount && pSize && pDigest && pDigestAlg && pEncryptionAlg )
+                                    if ( pKeyInfo && pVector && pSize && pDigest && pDigestAlg && pEncryptionAlg )
+                                    {
+                                        uno::Sequence < sal_Int8 > aSequence;
+                                        sal_Int64 nSize = 0;
+                                        sal_Int32 nDigestAlg = 0, nEncryptionAlg = 0;
+
+                                        pStream->SetToBeEncrypted ( true );
+
+                                        *pVector >>= aSequence;
+                                        pStream->setInitialisationVector ( aSequence );
+
+                                        *pSize >>= nSize;
+                                        pStream->setSize ( nSize );
+
+                                        *pDigest >>= aSequence;
+                                        pStream->setDigest ( aSequence );
+
+                                        *pDigestAlg >>= nDigestAlg;
+                                        pStream->SetImportedChecksumAlgorithm( nDigestAlg );
+
+                                        *pEncryptionAlg >>= nEncryptionAlg;
+                                        pStream->SetImportedEncryptionAlgorithm( nEncryptionAlg );
+
+                                        *pKeyInfo >>= m_aGpgProps;
+
+                                        pStream->SetToBeCompressed ( true );
+                                        pStream->SetToBeEncrypted ( true );
+                                        pStream->SetIsEncrypted ( true );
+                                        pStream->setIterationCount(0);
+
+                                        // clamp to default SHA256 start key magic value,
+                                        // c.f. ZipPackageStream::GetEncryptionKey()
+                                        // trying to get key value from properties
+                                        const sal_Int32 nStartKeyAlg = xml::crypto::DigestID::SHA256;
+                                        pStream->SetImportedStartKeyAlgorithm( nStartKeyAlg );
+
+                                        if ( !m_bHasEncryptedEntries && pStream->getName() == "content.xml" )
+                                        {
+                                            m_bHasEncryptedEntries = true;
+                                            m_nChecksumDigestID = nDigestAlg;
+                                            m_nCommonEncryptionID = nEncryptionAlg;
+                                            m_nStartKeyGenerationID = nStartKeyAlg;
+                                        }
+                                    }
+                                    else if ( pSalt && pVector && pCount && pSize && pDigest && pDigestAlg && pEncryptionAlg )
                                     {
                                         uno::Sequence < sal_Int8 > aSequence;
                                         sal_Int64 nSize = 0;
@@ -1203,10 +1251,10 @@ uno::Reference< io::XInputStream > ZipPackage::writeTempFile()
         const OUString sMediaType ("MediaType");
         const OUString sVersion ("Version");
         const OUString sFullPath ("FullPath");
+        const bool bIsGpgEncrypt = m_aGpgProps.hasElements();
 
         if ( m_nFormat == embed::StorageFormats::PACKAGE )
         {
-            bool bIsGpgEncrypt = m_aGpgProps.hasElements();
             uno::Sequence < PropertyValue > aPropSeq(
                 bIsGpgEncrypt ? PKG_SIZE_NOENCR_MNFST+1 : PKG_SIZE_NOENCR_MNFST );
             aPropSeq [PKG_MNFST_MEDIATYPE].Name = sMediaType;
@@ -1229,8 +1277,10 @@ uno::Reference< io::XInputStream > ZipPackage::writeTempFile()
             // for encrypted streams
             RandomPool aRandomPool;
 
+            sal_Int32 const nPBKDF2IterationCount = 100000;
+
             // call saveContents ( it will recursively save sub-directories
-            m_xRootFolder->saveContents("", aManList, aZipOut, GetEncryptionKey(), aRandomPool.get());
+            m_xRootFolder->saveContents("", aManList, aZipOut, GetEncryptionKey(), bIsGpgEncrypt ? 0 : nPBKDF2IterationCount, aRandomPool.get());
         }
 
         if( m_nFormat == embed::StorageFormats::PACKAGE )
@@ -1759,7 +1809,7 @@ void SAL_CALL ZipPackage::setPropertyValue( const OUString& aPropertyName, const
     else if ( aPropertyName == ENCRYPTION_GPG_PROPERTIES )
     {
         uno::Sequence< uno::Sequence< beans::NamedValue > > aGpgProps;
-        if ( m_pZipFile || !( aValue >>= aGpgProps ) || aGpgProps.getLength() == 0 )
+        if ( !( aValue >>= aGpgProps ) || aGpgProps.getLength() == 0 )
         {
             throw IllegalArgumentException(THROW_WHERE "unexpected Gpg properties are provided.", uno::Reference< uno::XInterface >(), 2 );
         }
@@ -1802,6 +1852,10 @@ Any SAL_CALL ZipPackage::getPropertyValue( const OUString& PropertyName )
     {
         return Any(m_bHasEncryptedEntries);
     }
+    else if ( PropertyName == ENCRYPTION_GPG_PROPERTIES )
+    {
+        return Any(m_aGpgProps);
+    }
     else if ( PropertyName == HAS_NONENCRYPTED_ENTRIES_PROPERTY )
     {
         return Any(m_bHasNonEncryptedEntries);
diff --git a/package/source/zippackage/ZipPackageFolder.cxx b/package/source/zippackage/ZipPackageFolder.cxx
index f70a048b393a..01b4789720b8 100644
--- a/package/source/zippackage/ZipPackageFolder.cxx
+++ b/package/source/zippackage/ZipPackageFolder.cxx
@@ -258,6 +258,7 @@ bool ZipPackageFolder::saveChild(
         std::vector < uno::Sequence < PropertyValue > > &rManList,
         ZipOutputStream & rZipOut,
         const uno::Sequence < sal_Int8 >& rEncryptionKey,
+        sal_Int32 nPBKDF2IterationCount,
         const rtlRandomPool &rRandomPool)
 {
     const OUString sMediaTypeProperty ("MediaType");
@@ -279,7 +280,7 @@ bool ZipPackageFolder::saveChild(
     else
         aPropSet.realloc( 0 );
 
-    saveContents( sTempName, rManList, rZipOut, rEncryptionKey, rRandomPool);
+    saveContents( sTempName, rManList, rZipOut, rEncryptionKey, nPBKDF2IterationCount, rRandomPool);
 
     // folder can have a mediatype only in package format
     if ( aPropSet.getLength() && ( m_nFormat == embed::StorageFormats::PACKAGE ) )
@@ -293,6 +294,7 @@ void ZipPackageFolder::saveContents(
         std::vector < uno::Sequence < PropertyValue > > &rManList,
         ZipOutputStream & rZipOut,
         const uno::Sequence < sal_Int8 >& rEncryptionKey,
+        sal_Int32 nPBKDF2IterationCount,
         const rtlRandomPool &rRandomPool ) const
 {
     bool bWritingFailed = false;
@@ -331,7 +333,7 @@ void ZipPackageFolder::saveContents(
         {
             bMimeTypeStreamStored = true;
             bWritingFailed = !aIter->second->pStream->saveChild(
-                rPath + aIter->first, rManList, rZipOut, rEncryptionKey, rRandomPool );
+                rPath + aIter->first, rManList, rZipOut, rEncryptionKey, nPBKDF2IterationCount, rRandomPool );
         }
     }
 
@@ -347,12 +349,12 @@ void ZipPackageFolder::saveContents(
             if (rInfo.bFolder)
             {
                 bWritingFailed = !rInfo.pFolder->saveChild(
-                    rPath + rShortName, rManList, rZipOut, rEncryptionKey, rRandomPool );
+                    rPath + rShortName, rManList, rZipOut, rEncryptionKey, nPBKDF2IterationCount, rRandomPool );
             }
             else
             {
                 bWritingFailed = !rInfo.pStream->saveChild(
-                    rPath + rShortName, rManList, rZipOut, rEncryptionKey, rRandomPool );
+                    rPath + rShortName, rManList, rZipOut, rEncryptionKey, nPBKDF2IterationCount, rRandomPool );
             }
         }
     }
diff --git a/package/source/zippackage/ZipPackageStream.cxx b/package/source/zippackage/ZipPackageStream.cxx
index 160dfa8d310f..f415995a125e 100644
--- a/package/source/zippackage/ZipPackageStream.cxx
+++ b/package/source/zippackage/ZipPackageStream.cxx
@@ -502,6 +502,7 @@ bool ZipPackageStream::saveChild(
         std::vector < uno::Sequence < beans::PropertyValue > > &rManList,
         ZipOutputStream & rZipOut,
         const uno::Sequence < sal_Int8 >& rEncryptionKey,
+        sal_Int32 nPBKDF2IterationCount,
         const rtlRandomPool &rRandomPool)
 {
     bool bSuccess = true;
@@ -647,8 +648,6 @@ bool ZipPackageStream::saveChild(
                 uno::Sequence < sal_Int8 > aSalt( 16 ), aVector( GetBlockSize() );
                 rtl_random_getBytes ( rRandomPool, aSalt.getArray(), 16 );
                 rtl_random_getBytes ( rRandomPool, aVector.getArray(), aVector.getLength() );
-                sal_Int32 const nPBKDF2IterationCount = 100000;
-
                 if ( !m_bHaveOwnKey )
                 {
                     m_aEncryptionKey = rEncryptionKey;
diff --git a/sfx2/source/appl/appopen.cxx b/sfx2/source/appl/appopen.cxx
index 0ae86d85f9b4..6c5d833af317 100644
--- a/sfx2/source/appl/appopen.cxx
+++ b/sfx2/source/appl/appopen.cxx
@@ -195,9 +195,12 @@ ErrCode CheckPasswd_Impl
             if ( xStorageProps.is() )
             {
                 bool bIsEncrypted = false;
+                uno::Sequence< uno::Sequence< beans::NamedValue > > aGpgProperties;
                 try {
                     xStorageProps->getPropertyValue("HasEncryptedEntries")
                         >>= bIsEncrypted;
+                    xStorageProps->getPropertyValue("EncryptionGpGProperties")
+                        >>= aGpgProperties;
                 } catch( uno::Exception& )
                 {
                     // TODO/LATER:
@@ -230,6 +233,12 @@ ErrCode CheckPasswd_Impl
                             if ( pEncryptionDataItem )
                                 pEncryptionDataItem->GetValue() >>= aEncryptionData;
 
+                            // try if one of the public key entries is
+                            // decryptable, then extract session key
+                            // from it
+                            if ( !aEncryptionData.hasElements() && aGpgProperties.hasElements() )
+                                aEncryptionData = ::comphelper::DocPasswordHelper::decryptGpgSession(aGpgProperties);
+
                             SfxDocPasswordVerifier aVerifier( xStorage );
                             aEncryptionData = ::comphelper::DocPasswordHelper::requestAndVerifyDocPassword(
                                 aVerifier, aEncryptionData, aPassword, xInteractionHandler, pFile->GetOrigURL(), comphelper::DocPasswordRequestType::Standard );
diff --git a/xmlsecurity/qa/unit/signing/data/encryptedGPG.odt b/xmlsecurity/qa/unit/signing/data/encryptedGPG.odt
new file mode 100644
index 000000000000..9490a0ce58c5
Binary files /dev/null and b/xmlsecurity/qa/unit/signing/data/encryptedGPG.odt differ
diff --git a/xmlsecurity/qa/unit/signing/data/pubring.gpg b/xmlsecurity/qa/unit/signing/data/pubring.gpg
index 40a8d53fb401..007ea98afd55 100644
Binary files a/xmlsecurity/qa/unit/signing/data/pubring.gpg and b/xmlsecurity/qa/unit/signing/data/pubring.gpg differ
diff --git a/xmlsecurity/qa/unit/signing/data/secring.gpg b/xmlsecurity/qa/unit/signing/data/secring.gpg
index d98950c22ecd..f7626df5aee3 100644
Binary files a/xmlsecurity/qa/unit/signing/data/secring.gpg and b/xmlsecurity/qa/unit/signing/data/secring.gpg differ
diff --git a/xmlsecurity/qa/unit/signing/data/trustdb.gpg b/xmlsecurity/qa/unit/signing/data/trustdb.gpg
index c86bb02f3d79..1966b5374cfb 100644
Binary files a/xmlsecurity/qa/unit/signing/data/trustdb.gpg and b/xmlsecurity/qa/unit/signing/data/trustdb.gpg differ
diff --git a/xmlsecurity/qa/unit/signing/signing.cxx b/xmlsecurity/qa/unit/signing/signing.cxx
index 18dd99644f35..39a6d0ca6299 100644
--- a/xmlsecurity/qa/unit/signing/signing.cxx
+++ b/xmlsecurity/qa/unit/signing/signing.cxx
@@ -113,6 +113,8 @@ public:
     void testODFBrokenStreamGPG();
     /// Test a typical broken ODF signature where the XML dsig hash is corrupted.
     void testODFBrokenDsigGPG();
+    /// Test loading an encrypted ODF document
+    void testODFEncryptedGPG();
 #endif
     CPPUNIT_TEST_SUITE(SigningTest);
     CPPUNIT_TEST(testDescription);
@@ -141,6 +143,7 @@ public:
     CPPUNIT_TEST(testODFUntrustedGoodGPG);
     CPPUNIT_TEST(testODFBrokenStreamGPG);
     CPPUNIT_TEST(testODFBrokenDsigGPG);
+    CPPUNIT_TEST(testODFEncryptedGPG);
 #endif
     CPPUNIT_TEST_SUITE_END();
 
@@ -739,6 +742,16 @@ void SigningTest::testODFBrokenDsigGPG()
     CPPUNIT_ASSERT(pObjectShell);
     CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN), static_cast<int>(pObjectShell->GetDocumentSignatureState()));
 }
+
+void SigningTest::testODFEncryptedGPG()
+{
+    createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "encryptedGPG.odt");
+    SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
+    CPPUNIT_ASSERT(pBaseModel);
+    SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+    CPPUNIT_ASSERT(pObjectShell);
+}
+
 #endif
 
 void SigningTest::registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx)


More information about the Libreoffice-commits mailing list