vcl/Library_vcl.mk | 1 vcl/inc/pdf/EncryptionHashTransporter.hxx | 2 vcl/inc/pdf/PDFEncryptor.hxx | 39 ++ vcl/inc/pdf/pdfwriter_impl.hxx | 35 -- vcl/source/gdi/pdfwriter_impl.cxx | 102 ------- vcl/source/gdi/pdfwriter_impl2.cxx | 266 -------------------- vcl/source/pdf/PDFEncryptor.cxx | 393 ++++++++++++++++++++++++++++++ 7 files changed, 434 insertions(+), 404 deletions(-)
New commits: commit 88c464eff3f6e83718450a227e750159a4ba7c22 Author: Tomaž Vajngerl <[email protected]> AuthorDate: Mon Nov 4 16:45:25 2024 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Mon Nov 11 14:38:14 2024 +0100 pdf: move encryption methods into PDFEncryptor files Moving more PDF encryption implementation into common encryption files. This will make it easier to add new encryption later on as the code will be in one place. Change-Id: Id40c2f876a2e92bb8db27024a0e251befc5059e5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176017 Reviewed-by: Miklos Vajna <[email protected]> Tested-by: Jenkins CollaboraOffice <[email protected]> diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk index 86efdce3bd8b..215d5451eacc 100644 --- a/vcl/Library_vcl.mk +++ b/vcl/Library_vcl.mk @@ -497,6 +497,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\ vcl/source/pdf/EncryptionHashTransporter \ vcl/source/pdf/ExternalPDFStreams \ vcl/source/pdf/PDFiumTools \ + vcl/source/pdf/PDFEncryptor \ vcl/source/pdf/PdfConfig \ vcl/source/pdf/ResourceDict \ vcl/source/pdf/Matrix3 \ diff --git a/vcl/inc/pdf/EncryptionHashTransporter.hxx b/vcl/inc/pdf/EncryptionHashTransporter.hxx index 0da0db3d8654..3ed1aa375561 100644 --- a/vcl/inc/pdf/EncryptionHashTransporter.hxx +++ b/vcl/inc/pdf/EncryptionHashTransporter.hxx @@ -24,7 +24,7 @@ namespace vcl::pdf clear text passwords down till they can be used in PDFWriter. Unfortunately the MD5 sum of the password (which is needed to create the PDF encryption key) is not sufficient, since an MD5 digest cannot be created in an arbitrary state - which would be needed in PDFWriterImpl::computeEncryptionKey. + which would be needed in computeEncryptionKey. */ class EncryptionHashTransporter : public cppu::WeakImplHelper<css::beans::XMaterialHolder> { diff --git a/vcl/inc/pdf/PDFEncryptor.hxx b/vcl/inc/pdf/PDFEncryptor.hxx index c93fc0bec9e9..e1602f11723f 100644 --- a/vcl/inc/pdf/PDFEncryptor.hxx +++ b/vcl/inc/pdf/PDFEncryptor.hxx @@ -11,11 +11,50 @@ #pragma once #include <rtl/cipher.h> +#include <string_view> +#include <vcl/pdfwriter.hxx> namespace vcl::pdf { +class EncryptionHashTransporter; + constexpr sal_Int32 ENCRYPTED_PWD_SIZE = 32; +// the maximum password length +constexpr sal_Int32 MD5_DIGEST_SIZE = 16; + +// security 128 bit +constexpr sal_Int32 SECUR_128BIT_KEY = 16; + +// maximum length of MD5 digest input, in step 2 of algorithm 3.1 +// PDF spec ver. 1.4: see there for details +constexpr sal_Int32 MAXIMUM_RC4_KEY_LENGTH = SECUR_128BIT_KEY + 3 + 2; + +void padPassword(std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW); + +/* algorithm 3.2: compute an encryption key */ +bool computeEncryptionKey(vcl::pdf::EncryptionHashTransporter*, + vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, + sal_Int32 i_nAccessPermissions); + +/* algorithm 3.3: computing the encryption dictionary'ss owner password value ( /O ) */ +bool computeODictionaryValue(const sal_uInt8* i_pPaddedOwnerPassword, + const sal_uInt8* i_pPaddedUserPassword, + std::vector<sal_uInt8>& io_rOValue, sal_Int32 i_nKeyLength); + +/* algorithm 3.4 or 3.5: computing the encryption dictionary's user password value ( /U ) revision 2 or 3 of the standard security handler */ +bool computeUDictionaryValue(vcl::pdf::EncryptionHashTransporter* i_pTransporter, + vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, + sal_Int32 i_nKeyLength, sal_Int32 i_nAccessPermissions); + +void computeDocumentIdentifier(std::vector<sal_uInt8>& o_rIdentifier, + const vcl::PDFWriter::PDFDocInfo& i_rDocInfo, + const OString& i_rCString1, + const css::util::DateTime& rCreationMetaDate, OString& o_rCString2); + +sal_Int32 computeAccessPermissions(const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties, + sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength); + class PDFEncryptor { public: diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index d01db2fd192b..895389b77c72 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -67,18 +67,9 @@ class FontSubsetInfo; class ZCodec; struct BitStreamState; namespace vcl::font { class PhysicalFontFace; } -namespace vcl::pdf { class EncryptionHashTransporter; } class SvStream; class SvMemoryStream; -// the maximum password length -constexpr sal_Int32 MD5_DIGEST_SIZE = 16; -// security 128 bit -constexpr sal_Int32 SECUR_128BIT_KEY = 16; -// maximum length of MD5 digest input, in step 2 of algorithm 3.1 -// PDF spec ver. 1.4: see there for details -constexpr sal_Int32 MAXIMUM_RC4_KEY_LENGTH = SECUR_128BIT_KEY + 3 + 2; - namespace vcl::pdf { @@ -1081,32 +1072,6 @@ private: methods for PDF security pad a password according algorithm 3.2, step 1 */ - static void padPassword( std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW ); - /* algorithm 3.2: compute an encryption key */ - static bool computeEncryptionKey( vcl::pdf::EncryptionHashTransporter*, - vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, - sal_Int32 i_nAccessPermissions - ); - /* algorithm 3.3: computing the encryption dictionary'ss owner password value ( /O ) */ - static bool computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword, const sal_uInt8* i_pPaddedUserPassword, - std::vector< sal_uInt8 >& io_rOValue, - sal_Int32 i_nKeyLength - ); - /* algorithm 3.4 or 3.5: computing the encryption dictionary's user password value ( /U ) revision 2 or 3 of the standard security handler */ - static bool computeUDictionaryValue( vcl::pdf::EncryptionHashTransporter* i_pTransporter, - vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, - sal_Int32 i_nKeyLength, - sal_Int32 i_nAccessPermissions - ); - - static void computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier, - const vcl::PDFWriter::PDFDocInfo& i_rDocInfo, - const OString& i_rCString1, - const css::util::DateTime& rCreationMetaDate, - OString& o_rCString2 - ); - static sal_Int32 computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties, - sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength ); void setupDocInfo(); bool prepareEncryption( const css::uno::Reference< css::beans::XMaterialHolder >& ); diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 83d1c417e707..0b29ed97b75b 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -1493,108 +1493,6 @@ OString PDFWriter::GetDateTime() return aRet.makeStringAndClear(); } -void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier, - const vcl::PDFWriter::PDFDocInfo& i_rDocInfo, - const OString& i_rCString1, - const css::util::DateTime& rCreationMetaDate, - OString& o_rCString2 - ) -{ - o_rIdentifier.clear(); - - //build the document id - OString aInfoValuesOut; - OStringBuffer aID( 1024 ); - if( !i_rDocInfo.Title.isEmpty() ) - PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID); - if( !i_rDocInfo.Author.isEmpty() ) - PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID); - if( !i_rDocInfo.Subject.isEmpty() ) - PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID); - if( !i_rDocInfo.Keywords.isEmpty() ) - PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID); - if( !i_rDocInfo.Creator.isEmpty() ) - PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID); - if( !i_rDocInfo.Producer.isEmpty() ) - PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID); - - TimeValue aTVal, aGMT; - oslDateTime aDT; - aDT.NanoSeconds = rCreationMetaDate.NanoSeconds; - aDT.Seconds = rCreationMetaDate.Seconds; - aDT.Minutes = rCreationMetaDate.Minutes; - aDT.Hours = rCreationMetaDate.Hours; - aDT.Day = rCreationMetaDate.Day; - aDT.Month = rCreationMetaDate.Month; - aDT.Year = rCreationMetaDate.Year; - - osl_getSystemTime( &aGMT ); - osl_getLocalTimeFromSystemTime( &aGMT, &aTVal ); - OStringBuffer aCreationMetaDateString(64); - - // i59651: we fill the Metadata date string as well, if PDF/A is requested - // according to ISO 19005-1:2005 6.7.3 the date is corrected for - // local time zone offset UTC only, whereas Acrobat 8 seems - // to use the localtime notation only - // according to a recommendation in XMP Specification (Jan 2004, page 75) - // the Acrobat way seems the right approach - aCreationMetaDateString.append( - OStringChar(static_cast<char>('0' + ((aDT.Year/1000)%10)) ) - + OStringChar(static_cast<char>('0' + ((aDT.Year/100)%10)) ) - + OStringChar(static_cast<char>('0' + ((aDT.Year/10)%10)) ) - + OStringChar(static_cast<char>('0' + ((aDT.Year)%10)) ) - + "-" - + OStringChar(static_cast<char>('0' + ((aDT.Month/10)%10)) ) - + OStringChar(static_cast<char>('0' + ((aDT.Month)%10)) ) - + "-" - + OStringChar(static_cast<char>('0' + ((aDT.Day/10)%10)) ) - + OStringChar(static_cast<char>('0' + ((aDT.Day)%10)) ) - + "T" - + OStringChar(static_cast<char>('0' + ((aDT.Hours/10)%10)) ) - + OStringChar(static_cast<char>('0' + ((aDT.Hours)%10)) ) - + ":" - + OStringChar(static_cast<char>('0' + ((aDT.Minutes/10)%10)) ) - + OStringChar(static_cast<char>('0' + ((aDT.Minutes)%10)) ) - + ":" - + OStringChar(static_cast<char>('0' + ((aDT.Seconds/10)%10)) ) - + OStringChar(static_cast<char>('0' + ((aDT.Seconds)%10)) )); - - sal_uInt32 nDelta = 0; - if( aGMT.Seconds > aTVal.Seconds ) - { - nDelta = aGMT.Seconds-aTVal.Seconds; - aCreationMetaDateString.append( "-" ); - } - else if( aGMT.Seconds < aTVal.Seconds ) - { - nDelta = aTVal.Seconds-aGMT.Seconds; - aCreationMetaDateString.append( "+" ); - } - else - { - aCreationMetaDateString.append( "Z" ); - - } - if( nDelta ) - { - aCreationMetaDateString.append( - OStringChar(static_cast<char>('0' + ((nDelta/36000)%10)) ) - + OStringChar(static_cast<char>('0' + ((nDelta/3600)%10)) ) - + ":" - + OStringChar(static_cast<char>('0' + ((nDelta/600)%6)) ) - + OStringChar(static_cast<char>('0' + ((nDelta/60)%10)) )); - } - aID.append( i_rCString1.getStr(), i_rCString1.getLength() ); - - aInfoValuesOut = aID.makeStringAndClear(); - o_rCString2 = aCreationMetaDateString.makeStringAndClear(); - - ::comphelper::Hash aDigest(::comphelper::HashType::MD5); - aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT)); - aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength()); - //the binary form of the doc id is needed for encryption stuff - o_rIdentifier = aDigest.finalize(); -} /* i12626 methods */ /* diff --git a/vcl/source/gdi/pdfwriter_impl2.cxx b/vcl/source/gdi/pdfwriter_impl2.cxx index 25da8b285a9e..88b9f6ad738e 100644 --- a/vcl/source/gdi/pdfwriter_impl2.cxx +++ b/vcl/source/gdi/pdfwriter_impl2.cxx @@ -16,7 +16,6 @@ * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ - #include <pdf/pdfwriter_impl.hxx> #include <pdf/EncryptionHashTransporter.hxx> @@ -43,7 +42,6 @@ #include <com/sun/star/graphic/XGraphicProvider.hpp> #include <com/sun/star/beans/XMaterialHolder.hpp> - #include <o3tl/unit_conversion.hxx> #include <vcl/skia/SkiaHelper.hxx> @@ -1172,270 +1170,6 @@ bool PDFWriterImpl::prepareEncryption( const uno::Reference< beans::XMaterialHol return bSuccess; } -sal_Int32 PDFWriterImpl::computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties, - sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength ) -{ - /* - 2) compute the access permissions, in numerical form - - the default value depends on the revision 2 (40 bit) or 3 (128 bit security): - - for 40 bit security the unused bit must be set to 1, since they are not used - - for 128 bit security the same bit must be preset to 0 and set later if needed - according to the table 3.15, pdf v 1.4 */ - sal_Int32 nAccessPermissions = 0xfffff0c0; - - o_rKeyLength = SECUR_128BIT_KEY; - o_rRC4KeyLength = 16; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16, - // thus maximum permitted value is 16 - - nAccessPermissions |= ( i_rProperties.CanPrintTheDocument ) ? 1 << 2 : 0; - nAccessPermissions |= ( i_rProperties.CanModifyTheContent ) ? 1 << 3 : 0; - nAccessPermissions |= ( i_rProperties.CanCopyOrExtract ) ? 1 << 4 : 0; - nAccessPermissions |= ( i_rProperties.CanAddOrModify ) ? 1 << 5 : 0; - nAccessPermissions |= ( i_rProperties.CanFillInteractive ) ? 1 << 8 : 0; - nAccessPermissions |= ( i_rProperties.CanExtractForAccessibility ) ? 1 << 9 : 0; - nAccessPermissions |= ( i_rProperties.CanAssemble ) ? 1 << 10 : 0; - nAccessPermissions |= ( i_rProperties.CanPrintFull ) ? 1 << 11 : 0; - return nAccessPermissions; -} - -/************************************************************* -begin i12626 methods - -Implements Algorithm 3.2, step 1 only -*/ -void PDFWriterImpl::padPassword( std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW ) -{ - // get ansi-1252 version of the password string CHECKIT ! i12626 - OString aString( OUStringToOString( i_rPassword, RTL_TEXTENCODING_MS_1252 ) ); - - //copy the string to the target - sal_Int32 nToCopy = ( aString.getLength() < ENCRYPTED_PWD_SIZE ) ? aString.getLength() : ENCRYPTED_PWD_SIZE; - sal_Int32 nCurrentChar; - - for( nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++ ) - o_pPaddedPW[nCurrentChar] = static_cast<sal_uInt8>( aString[nCurrentChar] ); - - //pad it with standard byte string - sal_Int32 i,y; - for( i = nCurrentChar, y = 0 ; i < ENCRYPTED_PWD_SIZE; i++, y++ ) - o_pPaddedPW[i] = PDFEncryptor::s_nPadString[y]; -} - -/********************************** -Algorithm 3.2 Compute the encryption key used - -step 1 should already be done before calling, the paThePaddedPassword parameter should contain -the padded password and must be 32 byte long, the encryption key is returned into the paEncryptionKey parameter, -it will be 16 byte long for 128 bit security; for 40 bit security only the first 5 bytes are used - -TODO: in pdf ver 1.5 and 1.6 the step 6 is different, should be implemented. See spec. - -*/ -bool PDFWriterImpl::computeEncryptionKey( EncryptionHashTransporter* i_pTransporter, vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, sal_Int32 i_nAccessPermissions ) -{ - bool bSuccess = true; - ::std::vector<unsigned char> nMD5Sum; - - // transporter contains an MD5 digest with the padded user password already - ::comphelper::Hash *const pDigest = i_pTransporter->getUDigest(); - if (pDigest) - { - //step 3 - if( ! io_rProperties.OValue.empty() ) - pDigest->update(io_rProperties.OValue.data(), io_rProperties.OValue.size()); - else - bSuccess = false; - //Step 4 - sal_uInt8 nPerm[4]; - - nPerm[0] = static_cast<sal_uInt8>(i_nAccessPermissions); - nPerm[1] = static_cast<sal_uInt8>( i_nAccessPermissions >> 8 ); - nPerm[2] = static_cast<sal_uInt8>( i_nAccessPermissions >> 16 ); - nPerm[3] = static_cast<sal_uInt8>( i_nAccessPermissions >> 24 ); - - pDigest->update(nPerm, sizeof(nPerm)); - - //step 5, get the document ID, binary form - pDigest->update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size()); - //get the digest - nMD5Sum = pDigest->finalize(); - - //step 6, only if 128 bit - for (sal_Int32 i = 0; i < 50; i++) - { - nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), ::comphelper::HashType::MD5); - } - } - else - bSuccess = false; - - i_pTransporter->invalidate(); - - //Step 7 - if( bSuccess ) - { - io_rProperties.EncryptionKey.resize( MAXIMUM_RC4_KEY_LENGTH ); - for( sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++ ) - io_rProperties.EncryptionKey[i] = nMD5Sum[i]; - } - else - io_rProperties.EncryptionKey.clear(); - - return bSuccess; -} - -/********************************** -Algorithm 3.3 Compute the encryption dictionary /O value, save into the class data member -the step numbers down here correspond to the ones in PDF v.1.4 specification -*/ -bool PDFWriterImpl::computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword, - const sal_uInt8* i_pPaddedUserPassword, - std::vector< sal_uInt8 >& io_rOValue, - sal_Int32 i_nKeyLength - ) -{ - bool bSuccess = true; - - io_rOValue.resize( ENCRYPTED_PWD_SIZE ); - - rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); - if (aCipher) - { - //step 1 already done, data is in i_pPaddedOwnerPassword - //step 2 - - ::std::vector<unsigned char> nMD5Sum(::comphelper::Hash::calculateHash( - i_pPaddedOwnerPassword, ENCRYPTED_PWD_SIZE, ::comphelper::HashType::MD5)); - //step 3, only if 128 bit - if (i_nKeyLength == SECUR_128BIT_KEY) - { - sal_Int32 i; - for (i = 0; i < 50; i++) - { - nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), ::comphelper::HashType::MD5); - } - } - //Step 4, the key is in nMD5Sum - //step 5 already done, data is in i_pPaddedUserPassword - //step 6 - if (rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, - nMD5Sum.data(), i_nKeyLength , nullptr, 0 ) - == rtl_Cipher_E_None) - { - // encrypt the user password using the key set above - rtl_cipher_encodeARCFOUR( aCipher, i_pPaddedUserPassword, ENCRYPTED_PWD_SIZE, // the data to be encrypted - io_rOValue.data(), sal_Int32(io_rOValue.size()) ); //encrypted data - //Step 7, only if 128 bit - if( i_nKeyLength == SECUR_128BIT_KEY ) - { - sal_uInt32 i; - size_t y; - sal_uInt8 nLocalKey[ SECUR_128BIT_KEY ]; // 16 = 128 bit key - - for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1 - { - for( y = 0; y < sizeof( nLocalKey ); y++ ) - nLocalKey[y] = static_cast<sal_uInt8>( nMD5Sum[y] ^ i ); - - if (rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, - nLocalKey, SECUR_128BIT_KEY, nullptr, 0 ) //destination data area, on init can be NULL - != rtl_Cipher_E_None) - { - bSuccess = false; - break; - } - rtl_cipher_encodeARCFOUR( aCipher, io_rOValue.data(), sal_Int32(io_rOValue.size()), // the data to be encrypted - io_rOValue.data(), sal_Int32(io_rOValue.size()) ); // encrypted data, can be the same as the input, encrypt "in place" - //step 8, store in class data member - } - } - } - else - bSuccess = false; - } - else - bSuccess = false; - - if( aCipher ) - rtl_cipher_destroyARCFOUR( aCipher ); - - if( ! bSuccess ) - io_rOValue.clear(); - return bSuccess; -} - -/********************************** -Algorithms 3.4 and 3.5 Compute the encryption dictionary /U value, save into the class data member, revision 2 (40 bit) or 3 (128 bit) -*/ -bool PDFWriterImpl::computeUDictionaryValue( EncryptionHashTransporter* i_pTransporter, - vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, - sal_Int32 i_nKeyLength, - sal_Int32 i_nAccessPermissions - ) -{ - bool bSuccess = true; - - io_rProperties.UValue.resize( ENCRYPTED_PWD_SIZE ); - - ::comphelper::Hash aDigest(::comphelper::HashType::MD5); - rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); - if (aCipher) - { - //step 1, common to both 3.4 and 3.5 - if( computeEncryptionKey( i_pTransporter, io_rProperties, i_nAccessPermissions ) ) - { - // prepare encryption key for object - for( sal_Int32 i = i_nKeyLength, y = 0; y < 5 ; y++ ) - io_rProperties.EncryptionKey[i++] = 0; - - //or 3.5, for 128 bit security - //step6, initialize the last 16 bytes of the encrypted user password to 0 - for(sal_uInt32 i = MD5_DIGEST_SIZE; i < sal_uInt32(io_rProperties.UValue.size()); i++) - io_rProperties.UValue[i] = 0; - //steps 2 and 3 - aDigest.update(PDFEncryptor::s_nPadString, sizeof(PDFEncryptor::s_nPadString)); - aDigest.update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size()); - - ::std::vector<unsigned char> const nMD5Sum(aDigest.finalize()); - //Step 4 - rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, - io_rProperties.EncryptionKey.data(), SECUR_128BIT_KEY, nullptr, 0 ); //destination data area - rtl_cipher_encodeARCFOUR( aCipher, nMD5Sum.data(), nMD5Sum.size(), // the data to be encrypted - io_rProperties.UValue.data(), SECUR_128BIT_KEY ); //encrypted data, stored in class data member - //step 5 - sal_uInt32 i; - size_t y; - sal_uInt8 nLocalKey[SECUR_128BIT_KEY]; - - for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1 - { - for( y = 0; y < sizeof( nLocalKey ) ; y++ ) - nLocalKey[y] = static_cast<sal_uInt8>( io_rProperties.EncryptionKey[y] ^ i ); - - rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode, - nLocalKey, SECUR_128BIT_KEY, // key and key length - nullptr, 0 ); //destination data area, on init can be NULL - rtl_cipher_encodeARCFOUR( aCipher, io_rProperties.UValue.data(), SECUR_128BIT_KEY, // the data to be encrypted - io_rProperties.UValue.data(), SECUR_128BIT_KEY ); // encrypted data, can be the same as the input, encrypt "in place" - } - } - else - bSuccess = false; - } - else - bSuccess = false; - - if( aCipher ) - rtl_cipher_destroyARCFOUR( aCipher ); - - if( ! bSuccess ) - io_rProperties.UValue.clear(); - return bSuccess; -} - -/* end i12626 methods */ - const tools::Long unsetRun[256] = { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */ diff --git a/vcl/source/pdf/PDFEncryptor.cxx b/vcl/source/pdf/PDFEncryptor.cxx new file mode 100644 index 000000000000..dc309fbbe59e --- /dev/null +++ b/vcl/source/pdf/PDFEncryptor.cxx @@ -0,0 +1,393 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <pdf/PDFEncryptor.hxx> +#include <pdf/EncryptionHashTransporter.hxx> +#include <pdf/pdfwriter_impl.hxx> +#include <comphelper/hash.hxx> + +namespace vcl::pdf +{ +/************************************************************* +begin i12626 methods + +Implements Algorithm 3.2, step 1 only +*/ +void padPassword(std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW) +{ + // get ansi-1252 version of the password string CHECKIT ! i12626 + OString aString(OUStringToOString(i_rPassword, RTL_TEXTENCODING_MS_1252)); + + //copy the string to the target + sal_Int32 nToCopy + = (aString.getLength() < ENCRYPTED_PWD_SIZE) ? aString.getLength() : ENCRYPTED_PWD_SIZE; + sal_Int32 nCurrentChar; + + for (nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++) + o_pPaddedPW[nCurrentChar] = static_cast<sal_uInt8>(aString[nCurrentChar]); + + //pad it with standard byte string + sal_Int32 i, y; + for (i = nCurrentChar, y = 0; i < ENCRYPTED_PWD_SIZE; i++, y++) + o_pPaddedPW[i] = PDFEncryptor::s_nPadString[y]; +} + +/********************************** +Algorithm 3.2 Compute the encryption key used + +step 1 should already be done before calling, the paThePaddedPassword parameter should contain +the padded password and must be 32 byte long, the encryption key is returned into the paEncryptionKey parameter, +it will be 16 byte long for 128 bit security; for 40 bit security only the first 5 bytes are used + +TODO: in pdf ver 1.5 and 1.6 the step 6 is different, should be implemented. See spec. + +*/ +bool computeEncryptionKey(EncryptionHashTransporter* i_pTransporter, + vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, + sal_Int32 i_nAccessPermissions) +{ + bool bSuccess = true; + ::std::vector<unsigned char> nMD5Sum; + + // transporter contains an MD5 digest with the padded user password already + ::comphelper::Hash* const pDigest = i_pTransporter->getUDigest(); + if (pDigest) + { + //step 3 + if (!io_rProperties.OValue.empty()) + pDigest->update(io_rProperties.OValue.data(), io_rProperties.OValue.size()); + else + bSuccess = false; + //Step 4 + sal_uInt8 nPerm[4]; + + nPerm[0] = static_cast<sal_uInt8>(i_nAccessPermissions); + nPerm[1] = static_cast<sal_uInt8>(i_nAccessPermissions >> 8); + nPerm[2] = static_cast<sal_uInt8>(i_nAccessPermissions >> 16); + nPerm[3] = static_cast<sal_uInt8>(i_nAccessPermissions >> 24); + + pDigest->update(nPerm, sizeof(nPerm)); + + //step 5, get the document ID, binary form + pDigest->update(io_rProperties.DocumentIdentifier.data(), + io_rProperties.DocumentIdentifier.size()); + //get the digest + nMD5Sum = pDigest->finalize(); + + //step 6, only if 128 bit + for (sal_Int32 i = 0; i < 50; i++) + { + nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), + ::comphelper::HashType::MD5); + } + } + else + bSuccess = false; + + i_pTransporter->invalidate(); + + //Step 7 + if (bSuccess) + { + io_rProperties.EncryptionKey.resize(MAXIMUM_RC4_KEY_LENGTH); + for (sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++) + io_rProperties.EncryptionKey[i] = nMD5Sum[i]; + } + else + io_rProperties.EncryptionKey.clear(); + + return bSuccess; +} + +/********************************** +Algorithm 3.3 Compute the encryption dictionary /O value, save into the class data member +the step numbers down here correspond to the ones in PDF v.1.4 specification +*/ +bool computeODictionaryValue(const sal_uInt8* i_pPaddedOwnerPassword, + const sal_uInt8* i_pPaddedUserPassword, + std::vector<sal_uInt8>& io_rOValue, sal_Int32 i_nKeyLength) +{ + bool bSuccess = true; + + io_rOValue.resize(ENCRYPTED_PWD_SIZE); + + rtlCipher aCipher = rtl_cipher_createARCFOUR(rtl_Cipher_ModeStream); + if (aCipher) + { + //step 1 already done, data is in i_pPaddedOwnerPassword + //step 2 + + ::std::vector<unsigned char> nMD5Sum(::comphelper::Hash::calculateHash( + i_pPaddedOwnerPassword, ENCRYPTED_PWD_SIZE, ::comphelper::HashType::MD5)); + //step 3, only if 128 bit + if (i_nKeyLength == SECUR_128BIT_KEY) + { + sal_Int32 i; + for (i = 0; i < 50; i++) + { + nMD5Sum = ::comphelper::Hash::calculateHash(nMD5Sum.data(), nMD5Sum.size(), + ::comphelper::HashType::MD5); + } + } + //Step 4, the key is in nMD5Sum + //step 5 already done, data is in i_pPaddedUserPassword + //step 6 + if (rtl_cipher_initARCFOUR(aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), + i_nKeyLength, nullptr, 0) + == rtl_Cipher_E_None) + { + // encrypt the user password using the key set above + rtl_cipher_encodeARCFOUR( + aCipher, i_pPaddedUserPassword, ENCRYPTED_PWD_SIZE, // the data to be encrypted + io_rOValue.data(), sal_Int32(io_rOValue.size())); //encrypted data + //Step 7, only if 128 bit + if (i_nKeyLength == SECUR_128BIT_KEY) + { + sal_uInt32 i; + size_t y; + sal_uInt8 nLocalKey[SECUR_128BIT_KEY]; // 16 = 128 bit key + + for (i = 1; i <= 19; i++) // do it 19 times, start with 1 + { + for (y = 0; y < sizeof(nLocalKey); y++) + nLocalKey[y] = static_cast<sal_uInt8>(nMD5Sum[y] ^ i); + + if (rtl_cipher_initARCFOUR(aCipher, rtl_Cipher_DirectionEncode, nLocalKey, + SECUR_128BIT_KEY, nullptr, + 0) //destination data area, on init can be NULL + != rtl_Cipher_E_None) + { + bSuccess = false; + break; + } + rtl_cipher_encodeARCFOUR( + aCipher, io_rOValue.data(), + sal_Int32(io_rOValue.size()), // the data to be encrypted + io_rOValue.data(), + sal_Int32( + io_rOValue + .size())); // encrypted data, can be the same as the input, encrypt "in place" + //step 8, store in class data member + } + } + } + else + bSuccess = false; + } + else + bSuccess = false; + + if (aCipher) + rtl_cipher_destroyARCFOUR(aCipher); + + if (!bSuccess) + io_rOValue.clear(); + return bSuccess; +} + +/********************************** +Algorithms 3.4 and 3.5 Compute the encryption dictionary /U value, save into the class data member, revision 2 (40 bit) or 3 (128 bit) +*/ +bool computeUDictionaryValue(EncryptionHashTransporter* i_pTransporter, + vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, + sal_Int32 i_nKeyLength, sal_Int32 i_nAccessPermissions) +{ + bool bSuccess = true; + + io_rProperties.UValue.resize(ENCRYPTED_PWD_SIZE); + + ::comphelper::Hash aDigest(::comphelper::HashType::MD5); + rtlCipher aCipher = rtl_cipher_createARCFOUR(rtl_Cipher_ModeStream); + if (aCipher) + { + //step 1, common to both 3.4 and 3.5 + if (computeEncryptionKey(i_pTransporter, io_rProperties, i_nAccessPermissions)) + { + // prepare encryption key for object + for (sal_Int32 i = i_nKeyLength, y = 0; y < 5; y++) + io_rProperties.EncryptionKey[i++] = 0; + + //or 3.5, for 128 bit security + //step6, initialize the last 16 bytes of the encrypted user password to 0 + for (sal_uInt32 i = MD5_DIGEST_SIZE; i < sal_uInt32(io_rProperties.UValue.size()); i++) + io_rProperties.UValue[i] = 0; + //steps 2 and 3 + aDigest.update(PDFEncryptor::s_nPadString, sizeof(PDFEncryptor::s_nPadString)); + aDigest.update(io_rProperties.DocumentIdentifier.data(), + io_rProperties.DocumentIdentifier.size()); + + ::std::vector<unsigned char> const nMD5Sum(aDigest.finalize()); + //Step 4 + rtl_cipher_initARCFOUR(aCipher, rtl_Cipher_DirectionEncode, + io_rProperties.EncryptionKey.data(), SECUR_128BIT_KEY, nullptr, + 0); //destination data area + rtl_cipher_encodeARCFOUR( + aCipher, nMD5Sum.data(), nMD5Sum.size(), // the data to be encrypted + io_rProperties.UValue.data(), + SECUR_128BIT_KEY); //encrypted data, stored in class data member + //step 5 + sal_uInt32 i; + size_t y; + sal_uInt8 nLocalKey[SECUR_128BIT_KEY]; + + for (i = 1; i <= 19; i++) // do it 19 times, start with 1 + { + for (y = 0; y < sizeof(nLocalKey); y++) + nLocalKey[y] = static_cast<sal_uInt8>(io_rProperties.EncryptionKey[y] ^ i); + + rtl_cipher_initARCFOUR(aCipher, rtl_Cipher_DirectionEncode, nLocalKey, + SECUR_128BIT_KEY, // key and key length + nullptr, 0); //destination data area, on init can be NULL + rtl_cipher_encodeARCFOUR( + aCipher, io_rProperties.UValue.data(), + SECUR_128BIT_KEY, // the data to be encrypted + io_rProperties.UValue.data(), + SECUR_128BIT_KEY); // encrypted data, can be the same as the input, encrypt "in place" + } + } + else + bSuccess = false; + } + else + bSuccess = false; + + if (aCipher) + rtl_cipher_destroyARCFOUR(aCipher); + + if (!bSuccess) + io_rProperties.UValue.clear(); + return bSuccess; +} + +void computeDocumentIdentifier(std::vector<sal_uInt8>& o_rIdentifier, + const vcl::PDFWriter::PDFDocInfo& i_rDocInfo, + const OString& i_rCString1, + const css::util::DateTime& rCreationMetaDate, OString& o_rCString2) +{ + o_rIdentifier.clear(); + + //build the document id + OString aInfoValuesOut; + OStringBuffer aID(1024); + if (!i_rDocInfo.Title.isEmpty()) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID); + if (!i_rDocInfo.Author.isEmpty()) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID); + if (!i_rDocInfo.Subject.isEmpty()) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID); + if (!i_rDocInfo.Keywords.isEmpty()) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID); + if (!i_rDocInfo.Creator.isEmpty()) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID); + if (!i_rDocInfo.Producer.isEmpty()) + PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID); + + TimeValue aTVal, aGMT; + oslDateTime aDT; + aDT.NanoSeconds = rCreationMetaDate.NanoSeconds; + aDT.Seconds = rCreationMetaDate.Seconds; + aDT.Minutes = rCreationMetaDate.Minutes; + aDT.Hours = rCreationMetaDate.Hours; + aDT.Day = rCreationMetaDate.Day; + aDT.Month = rCreationMetaDate.Month; + aDT.Year = rCreationMetaDate.Year; + + osl_getSystemTime(&aGMT); + osl_getLocalTimeFromSystemTime(&aGMT, &aTVal); + OStringBuffer aCreationMetaDateString(64); + + // i59651: we fill the Metadata date string as well, if PDF/A is requested + // according to ISO 19005-1:2005 6.7.3 the date is corrected for + // local time zone offset UTC only, whereas Acrobat 8 seems + // to use the localtime notation only + // according to a recommendation in XMP Specification (Jan 2004, page 75) + // the Acrobat way seems the right approach + aCreationMetaDateString.append(OStringChar(static_cast<char>('0' + ((aDT.Year / 1000) % 10))) + + OStringChar(static_cast<char>('0' + ((aDT.Year / 100) % 10))) + + OStringChar(static_cast<char>('0' + ((aDT.Year / 10) % 10))) + + OStringChar(static_cast<char>('0' + ((aDT.Year) % 10))) + "-" + + OStringChar(static_cast<char>('0' + ((aDT.Month / 10) % 10))) + + OStringChar(static_cast<char>('0' + ((aDT.Month) % 10))) + "-" + + OStringChar(static_cast<char>('0' + ((aDT.Day / 10) % 10))) + + OStringChar(static_cast<char>('0' + ((aDT.Day) % 10))) + "T" + + OStringChar(static_cast<char>('0' + ((aDT.Hours / 10) % 10))) + + OStringChar(static_cast<char>('0' + ((aDT.Hours) % 10))) + ":" + + OStringChar(static_cast<char>('0' + ((aDT.Minutes / 10) % 10))) + + OStringChar(static_cast<char>('0' + ((aDT.Minutes) % 10))) + + ":" + + OStringChar(static_cast<char>('0' + ((aDT.Seconds / 10) % 10))) + + OStringChar(static_cast<char>('0' + ((aDT.Seconds) % 10)))); + + sal_uInt32 nDelta = 0; + if (aGMT.Seconds > aTVal.Seconds) + { + nDelta = aGMT.Seconds - aTVal.Seconds; + aCreationMetaDateString.append("-"); + } + else if (aGMT.Seconds < aTVal.Seconds) + { + nDelta = aTVal.Seconds - aGMT.Seconds; + aCreationMetaDateString.append("+"); + } + else + { + aCreationMetaDateString.append("Z"); + } + if (nDelta) + { + aCreationMetaDateString.append( + OStringChar(static_cast<char>('0' + ((nDelta / 36000) % 10))) + + OStringChar(static_cast<char>('0' + ((nDelta / 3600) % 10))) + ":" + + OStringChar(static_cast<char>('0' + ((nDelta / 600) % 6))) + + OStringChar(static_cast<char>('0' + ((nDelta / 60) % 10)))); + } + aID.append(i_rCString1.getStr(), i_rCString1.getLength()); + + aInfoValuesOut = aID.makeStringAndClear(); + o_rCString2 = aCreationMetaDateString.makeStringAndClear(); + + ::comphelper::Hash aDigest(::comphelper::HashType::MD5); + aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT)); + aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), + aInfoValuesOut.getLength()); + //the binary form of the doc id is needed for encryption stuff + o_rIdentifier = aDigest.finalize(); +} + +sal_Int32 computeAccessPermissions(const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties, + sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength) +{ + /* + 2) compute the access permissions, in numerical form + + the default value depends on the revision 2 (40 bit) or 3 (128 bit security): + - for 40 bit security the unused bit must be set to 1, since they are not used + - for 128 bit security the same bit must be preset to 0 and set later if needed + according to the table 3.15, pdf v 1.4 */ + sal_Int32 nAccessPermissions = 0xfffff0c0; + + o_rKeyLength = SECUR_128BIT_KEY; + o_rRC4KeyLength = 16; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16, + // thus maximum permitted value is 16 + + nAccessPermissions |= (i_rProperties.CanPrintTheDocument) ? 1 << 2 : 0; + nAccessPermissions |= (i_rProperties.CanModifyTheContent) ? 1 << 3 : 0; + nAccessPermissions |= (i_rProperties.CanCopyOrExtract) ? 1 << 4 : 0; + nAccessPermissions |= (i_rProperties.CanAddOrModify) ? 1 << 5 : 0; + nAccessPermissions |= (i_rProperties.CanFillInteractive) ? 1 << 8 : 0; + nAccessPermissions |= (i_rProperties.CanExtractForAccessibility) ? 1 << 9 : 0; + nAccessPermissions |= (i_rProperties.CanAssemble) ? 1 << 10 : 0; + nAccessPermissions |= (i_rProperties.CanPrintFull) ? 1 << 11 : 0; + return nAccessPermissions; +} + +} // end vcl::pdf + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
