diff options
author | Thorsten Behrens <Thorsten.Behrens@CIB.de> | 2017-12-10 23:40:00 +0100 |
---|---|---|
committer | Thorsten Behrens <Thorsten.Behrens@CIB.de> | 2018-01-13 14:17:48 +0100 |
commit | 01c7a60c051ac4562e3a317dde3c29c507f3f40b (patch) | |
tree | 0b7104b75ba3910ed0363b46bc66b4b5afaede3e | |
parent | 3271e8b90f5d522fdfe1de46b77e7f67cdaa75af (diff) |
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@libreoffice.org>
Reviewed-by: Katarina Behrens <Katarina.Behrens@cib.de>
Reviewed-by: Thorsten Behrens <Thorsten.Behrens@CIB.de>
20 files changed, 403 insertions, 16 deletions
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 Binary files differnew file mode 100644 index 000000000000..9490a0ce58c5 --- /dev/null +++ b/xmlsecurity/qa/unit/signing/data/encryptedGPG.odt diff --git a/xmlsecurity/qa/unit/signing/data/pubring.gpg b/xmlsecurity/qa/unit/signing/data/pubring.gpg Binary files differindex 40a8d53fb401..007ea98afd55 100644 --- a/xmlsecurity/qa/unit/signing/data/pubring.gpg +++ b/xmlsecurity/qa/unit/signing/data/pubring.gpg diff --git a/xmlsecurity/qa/unit/signing/data/secring.gpg b/xmlsecurity/qa/unit/signing/data/secring.gpg Binary files differindex d98950c22ecd..f7626df5aee3 100644 --- a/xmlsecurity/qa/unit/signing/data/secring.gpg +++ b/xmlsecurity/qa/unit/signing/data/secring.gpg diff --git a/xmlsecurity/qa/unit/signing/data/trustdb.gpg b/xmlsecurity/qa/unit/signing/data/trustdb.gpg Binary files differindex c86bb02f3d79..1966b5374cfb 100644 --- a/xmlsecurity/qa/unit/signing/data/trustdb.gpg +++ b/xmlsecurity/qa/unit/signing/data/trustdb.gpg 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) |