diff options
-rw-r--r-- | comphelper/source/misc/docpasswordhelper.cxx | 96 | ||||
-rw-r--r-- | include/comphelper/docpasswordhelper.hxx | 3 | ||||
-rw-r--r-- | package/source/xstor/xstorage.cxx | 4 | ||||
-rw-r--r-- | package/source/zippackage/ZipPackage.cxx | 55 | ||||
-rw-r--r-- | sfx2/source/appl/appopen.cxx | 9 |
5 files changed, 164 insertions, 3 deletions
diff --git a/comphelper/source/misc/docpasswordhelper.cxx b/comphelper/source/misc/docpasswordhelper.cxx index 13ab45f043af..81e1e996816f 100644 --- a/comphelper/source/misc/docpasswordhelper.cxx +++ b/comphelper/source/misc/docpasswordhelper.cxx @@ -17,17 +17,28 @@ * 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/diagnose.h> #include <rtl/digest.h> #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; @@ -418,6 +429,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/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/source/xstor/xstorage.cxx b/package/source/xstor/xstorage.cxx index 21567b8eb2a5..3fabad0e51e7 100644 --- a/package/source/xstor/xstorage.cxx +++ b/package/source/xstor/xstorage.cxx @@ -4388,7 +4388,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 ); @@ -4507,6 +4508,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/zippackage/ZipPackage.cxx b/package/source/zippackage/ZipPackage.cxx index a6eb5f6af97e..967e0eded056 100644 --- a/package/source/zippackage/ZipPackage.cxx +++ b/package/source/zippackage/ZipPackage.cxx @@ -197,12 +197,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++ ) { @@ -235,6 +237,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 ) ) @@ -255,7 +259,50 @@ 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 ); + + // 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; @@ -1758,7 +1805,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 ); } @@ -1801,6 +1848,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/sfx2/source/appl/appopen.cxx b/sfx2/source/appl/appopen.cxx index 0c6c9fd4df10..cba90f661a86 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 ); |