include/vcl/pdfwriter.hxx | 92 ++++++------ vcl/inc/pdf/PDFEncryptor.hxx | 85 ++++------- vcl/inc/pdf/pdfwriter_impl.hxx | 9 - vcl/source/gdi/pdfwriter.cxx | 6 vcl/source/gdi/pdfwriter_impl.cxx | 138 ++++++++++++++----- vcl/source/gdi/pdfwriter_impl2.cxx | 61 -------- vcl/source/pdf/PDFEncryptor.cxx | 265 +++++++++++++++++++------------------ 7 files changed, 330 insertions(+), 326 deletions(-)
New commits: commit 61bfc4c50567bf4b84164ae7a880a322f32ad106 Author: Tomaž Vajngerl <[email protected]> AuthorDate: Tue Nov 5 16:48:26 2024 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Mon Nov 25 09:19:48 2024 +0100 pdf: wrap more encryption specific code into PDFEncryptor To make the code contained, so it is easier to add the new encryption method later. Change-Id: Ie3e3194c2148f6c3cb8ac58709fcd5f74e84a93a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176212 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index 57457f1a95bb..b901505c28cd 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -74,6 +74,51 @@ class VCL_DLLPUBLIC PDFOutputStream virtual void write( const css::uno::Reference< css::io::XOutputStream >& xStream ) = 0; }; + +/* The following structure describes the permissions used in PDF security */ +struct PDFEncryptionProperties +{ + //for both 40 and 128 bit security, see 3.5.2 PDF v 1.4 table 3.15, v 1.5 and v 1.6 table 3.20. + bool CanPrintTheDocument; + bool CanModifyTheContent; + bool CanCopyOrExtract; + bool CanAddOrModify; + //for revision 3 (bit 128 security) only + bool CanFillInteractive; + bool CanExtractForAccessibility; + bool CanAssemble; + bool CanPrintFull; + + // encryption will only happen if EncryptionKey is not empty + // EncryptionKey is actually a construct out of OValue, UValue and DocumentIdentifier + // if these do not match, behavior is undefined, most likely an invalid PDF will be produced + // OValue, UValue, EncryptionKey and DocumentIdentifier can be computed from + // PDFDocInfo, Owner password and User password used the InitEncryption method which + // implements the algorithms described in the PDF reference chapter 3.5: Encryption + std::vector<sal_uInt8> OValue; + std::vector<sal_uInt8> UValue; + std::vector<sal_uInt8> EncryptionKey; + std::vector<sal_uInt8> DocumentIdentifier; + + //permission default set for 128 bit, accessibility only + PDFEncryptionProperties() : + CanPrintTheDocument ( false ), + CanModifyTheContent ( false ), + CanCopyOrExtract ( false ), + CanAddOrModify ( false ), + CanFillInteractive ( false ), + CanExtractForAccessibility ( true ), + CanAssemble ( false ), + CanPrintFull ( false ) + {} + + + bool Encrypt() const + { + return ! OValue.empty() && ! UValue.empty() && ! DocumentIdentifier.empty(); + } +}; + class VCL_DLLPUBLIC PDFWriter { ScopedVclPtr<PDFWriterImpl> xImplementation; @@ -521,51 +566,6 @@ public: LaunchAction }; -/* -The following structure describes the permissions used in PDF security - */ - struct PDFEncryptionProperties - { - - //for both 40 and 128 bit security, see 3.5.2 PDF v 1.4 table 3.15, v 1.5 and v 1.6 table 3.20. - bool CanPrintTheDocument; - bool CanModifyTheContent; - bool CanCopyOrExtract; - bool CanAddOrModify; - //for revision 3 (bit 128 security) only - bool CanFillInteractive; - bool CanExtractForAccessibility; - bool CanAssemble; - bool CanPrintFull; - - // encryption will only happen if EncryptionKey is not empty - // EncryptionKey is actually a construct out of OValue, UValue and DocumentIdentifier - // if these do not match, behavior is undefined, most likely an invalid PDF will be produced - // OValue, UValue, EncryptionKey and DocumentIdentifier can be computed from - // PDFDocInfo, Owner password and User password used the InitEncryption method which - // implements the algorithms described in the PDF reference chapter 3.5: Encryption - std::vector<sal_uInt8> OValue; - std::vector<sal_uInt8> UValue; - std::vector<sal_uInt8> EncryptionKey; - std::vector<sal_uInt8> DocumentIdentifier; - - //permission default set for 128 bit, accessibility only - PDFEncryptionProperties() : - CanPrintTheDocument ( false ), - CanModifyTheContent ( false ), - CanCopyOrExtract ( false ), - CanAddOrModify ( false ), - CanFillInteractive ( false ), - CanExtractForAccessibility ( true ), - CanAssemble ( false ), - CanPrintFull ( false ) - {} - - - bool Encrypt() const - { return ! OValue.empty() && ! UValue.empty() && ! DocumentIdentifier.empty(); } - }; - struct PDFDocInfo { OUString Title; // document title @@ -647,7 +647,7 @@ The following structure describes the permissions used in PDF security sal_Int32 InitialPage; sal_Int32 OpenBookmarkLevels; // -1 means all levels - PDFWriter::PDFEncryptionProperties Encryption; + PDFEncryptionProperties Encryption; PDFWriter::PDFDocInfo DocumentInfo; bool SignPDF; diff --git a/vcl/inc/pdf/PDFEncryptor.hxx b/vcl/inc/pdf/PDFEncryptor.hxx index e1602f11723f..dca2255139e8 100644 --- a/vcl/inc/pdf/PDFEncryptor.hxx +++ b/vcl/inc/pdf/PDFEncryptor.hxx @@ -10,75 +10,54 @@ #pragma once -#include <rtl/cipher.h> #include <string_view> -#include <vcl/pdfwriter.hxx> +#include <rtl/cipher.h> +#include <com/sun/star/uno/Reference.hxx> + +namespace vcl +{ +struct PDFEncryptionProperties; +} +namespace com::sun::star::beans +{ +class XMaterialHolder; +} 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 { +private: + /* the numerical value of the access permissions, according to PDF spec, must be signed */ + sal_Int32 m_nAccessPermissions = 0; + + /* The encryption key, formed with the user password according to algorithm 3.2, + * maximum length is 16 bytes + 3 + 2 for 128 bit security */ + + sal_Int32 m_nKeyLength = 0; // key length, 16 or 5 + sal_Int32 m_nRC4KeyLength = 0; // key length, 16 or 10, to be input to the algorithm 3.1 public: + PDFEncryptor(); + ~PDFEncryptor(); + /* used to cipher the stream data and for password management */ rtlCipher m_aCipher = nullptr; - /* pad string used for password in Standard security handler */ - static const sal_uInt8 s_nPadString[ENCRYPTED_PWD_SIZE]; - /* set to true if the following stream must be encrypted, used inside writeBuffer() */ bool m_bEncryptThisStream = false; - /* The encryption key, formed with the user password according to algorithm 3.2, - * maximum length is 16 bytes + 3 + 2 for 128 bit security */ - sal_Int32 m_nKeyLength = 0; // key length, 16 or 5 - sal_Int32 m_nRC4KeyLength = 0; // key length, 16 or 10, to be input to the algorithm 3.1 - - PDFEncryptor() - { - /* prepare the cypher engine */ - m_aCipher = rtl_cipher_createARCFOUR(rtl_Cipher_ModeStream); - } + sal_Int32 getAccessPermissions() { return m_nAccessPermissions; } + sal_Int32 getKeyLength() { return m_nKeyLength; } + sal_Int32 getRC4KeyLength() { return m_nRC4KeyLength; } - ~PDFEncryptor() { rtl_cipher_destroyARCFOUR(m_aCipher); } + static css::uno::Reference<css::beans::XMaterialHolder> + initEncryption(const OUString& i_rOwnerPassword, const OUString& i_rUserPassword); + virtual bool prepareEncryption( + const css::uno::Reference<css::beans::XMaterialHolder>& xEncryptionMaterialHolder, + PDFEncryptionProperties& rProperties); + void setupKeysAndCheck(PDFEncryptionProperties& rProperties); }; } diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index 895389b77c72..486363e83cf4 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -828,8 +828,6 @@ private: return nPosition; } - /* the numerical value of the access permissions, according to PDF spec, must be signed */ - sal_Int32 m_nAccessPermissions; /* string to hold the PDF creation date */ OString m_aCreationDateString; /* string to hold the PDF creation date, for PDF/A metadata */ @@ -842,7 +840,7 @@ private: /* this function implements part of the PDF spec algorithm 3.1 in encryption, the rest (the actual encryption) is in PDFWriterImpl::writeBuffer */ void checkAndEnableStreamEncryption( sal_Int32 nObject ) override; - void disableStreamEncryption() override { m_aPDFEncryptor.m_bEncryptThisStream = false; }; + void disableStreamEncryption() override { m_aPDFEncryptor.m_bEncryptThisStream = false; } /* */ void enableStringEncryption( sal_Int32 nObject ); @@ -1073,7 +1071,6 @@ private: pad a password according algorithm 3.2, step 1 */ void setupDocInfo(); - bool prepareEncryption( const css::uno::Reference< css::beans::XMaterialHolder >& ); // helper for playMetafile void implWriteGradient( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, @@ -1094,10 +1091,6 @@ public: ~PDFWriterImpl() override; void dispose() override; - static css::uno::Reference< css::beans::XMaterialHolder > - initEncryption( const OUString& i_rOwnerPassword, - const OUString& i_rUserPassword ); - /* document structure */ void newPage( double nPageWidth , double nPageHeight, PDFWriter::Orientation eOrientation ); bool emit(); diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx index 60437f55fe34..30d7f3e3d7a2 100644 --- a/vcl/source/gdi/pdfwriter.cxx +++ b/vcl/source/gdi/pdfwriter.cxx @@ -470,11 +470,9 @@ std::set< PDFWriter::ErrorCode > const & PDFWriter::GetErrors() const } css::uno::Reference< css::beans::XMaterialHolder > -PDFWriter::InitEncryption( const OUString& i_rOwnerPassword, - const OUString& i_rUserPassword - ) +PDFWriter::InitEncryption(const OUString& i_rOwnerPassword, const OUString& i_rUserPassword) { - return PDFWriterImpl::initEncryption( i_rOwnerPassword, i_rUserPassword ); + return PDFEncryptor::initEncryption(i_rOwnerPassword, i_rUserPassword); } void PDFWriter::PlayMetafile( const GDIMetaFile& i_rMTF, const vcl::PDFWriter::PlayMetafileContext& i_rPlayContext, PDFExtOutDevData* i_pData ) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index bdf546bb3bab..b6f916a560c1 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -276,15 +276,6 @@ void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer ) } } // end anonymous namespace -namespace vcl::pdf -{ -const sal_uInt8 PDFEncryptor::s_nPadString[32] = -{ - 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, - 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A -}; -} - namespace vcl { @@ -725,7 +716,106 @@ const char* getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion) } } -} // end namespace +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(char('0' + ((aDT.Year / 1000) % 10))); + aCreationMetaDateString.append(char('0' + ((aDT.Year / 100) % 10))); + aCreationMetaDateString.append(char('0' + ((aDT.Year / 10) % 10))); + aCreationMetaDateString.append(char('0' + ((aDT.Year) % 10))); + aCreationMetaDateString.append("-"); + aCreationMetaDateString.append(char('0' + ((aDT.Month / 10) % 10))); + aCreationMetaDateString.append(char('0' + ((aDT.Month) % 10))); + aCreationMetaDateString.append("-"); + aCreationMetaDateString.append(char('0' + ((aDT.Day / 10) % 10))); + aCreationMetaDateString.append(char('0' + ((aDT.Day) % 10))); + aCreationMetaDateString.append("T"); + aCreationMetaDateString.append(char('0' + ((aDT.Hours / 10) % 10))); + aCreationMetaDateString.append(char('0' + ((aDT.Hours) % 10))); + aCreationMetaDateString.append(":"); + aCreationMetaDateString.append(char('0' + ((aDT.Minutes / 10) % 10))); + aCreationMetaDateString.append(char('0' + ((aDT.Minutes) % 10))); + aCreationMetaDateString.append(":"); + aCreationMetaDateString.append(char('0' + ((aDT.Seconds / 10) % 10))); + aCreationMetaDateString.append(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(char('0' + ((nDelta / 36000) % 10))); + aCreationMetaDateString.append(char('0' + ((nDelta / 3600) % 10))); + aCreationMetaDateString.append(":"); + aCreationMetaDateString.append(char('0' + ((nDelta / 600) % 6))); + aCreationMetaDateString.append(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(); +} + +} // end anonymous namespace PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation ) : @@ -1305,7 +1395,7 @@ double PDFPage::getHeight() const } PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, - const css::uno::Reference< css::beans::XMaterialHolder >& xEnc, + const css::uno::Reference< css::beans::XMaterialHolder >& xEncryptionMaterialHolder, PDFWriter& i_rOuterFace) : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF), m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ), @@ -1326,7 +1416,6 @@ PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, m_aFile(m_aContext.URL), m_bOpen(false), m_DocDigest(::comphelper::HashType::MD5), - m_nAccessPermissions(0), m_rOuterFace( i_rOuterFace ) { m_aStructure.emplace_back( ); @@ -1364,25 +1453,12 @@ PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, // setup DocInfo setupDocInfo(); - if( xEnc.is() ) - prepareEncryption( xEnc ); + if (xEncryptionMaterialHolder.is()) + m_aPDFEncryptor.prepareEncryption(xEncryptionMaterialHolder, m_aContext.Encryption); - if( m_aContext.Encryption.Encrypt() ) + if (m_aContext.Encryption.Encrypt()) { - // sanity check - if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE || - m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE || - m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH - ) - { - // the field lengths are invalid ? This was not setup by initEncryption. - // do not encrypt after all - m_aContext.Encryption.OValue.clear(); - m_aContext.Encryption.UValue.clear(); - OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" ); - } - else // setup key lengths - m_nAccessPermissions = computeAccessPermissions(m_aContext.Encryption, m_aPDFEncryptor.m_nKeyLength, m_aPDFEncryptor.m_nRC4KeyLength); + m_aPDFEncryptor.setupKeysAndCheck(m_aContext.Encryption); } // write header @@ -1470,7 +1546,7 @@ void PDFWriterImpl::setupDocInfo() { std::vector< sal_uInt8 > aId; m_aCreationDateString = PDFWriter::GetDateTime(); - computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString ); + computeDocumentIdentifier(aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString); if( m_aContext.Encryption.DocumentIdentifier.empty() ) m_aContext.Encryption.DocumentIdentifier = aId; } @@ -6022,7 +6098,7 @@ sal_Int32 PDFWriterImpl::emitEncrypt() // emit the owner password, must not be encrypted aWriter.writeString("/O", reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size())); aWriter.writeString("/U", reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size())); - aWriter.write("/P", m_nAccessPermissions); + aWriter.write("/P", m_aPDFEncryptor.getAccessPermissions()); aWriter.endDict(); aWriter.endObject(); diff --git a/vcl/source/gdi/pdfwriter_impl2.cxx b/vcl/source/gdi/pdfwriter_impl2.cxx index 88b9f6ad738e..0f309f375c20 100644 --- a/vcl/source/gdi/pdfwriter_impl2.cxx +++ b/vcl/source/gdi/pdfwriter_impl2.cxx @@ -1084,7 +1084,7 @@ void PDFWriterImpl::checkAndEnableStreamEncryption( sal_Int32 nObject ) return; m_aPDFEncryptor.m_bEncryptThisStream = true; - sal_Int32 i = m_aPDFEncryptor.m_nKeyLength; + sal_Int32 i = m_aPDFEncryptor.getKeyLength(); m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>(nObject); m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 8 ); m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 16 ); @@ -1095,7 +1095,7 @@ void PDFWriterImpl::checkAndEnableStreamEncryption( sal_Int32 nObject ) // the i+2 to take into account the generation number, always zero // initialize the RC4 with the key // key length: see algorithm 3.1, step 4: (N+5) max 16 - rtl_cipher_initARCFOUR(m_aPDFEncryptor.m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_aPDFEncryptor.m_nRC4KeyLength, nullptr, 0); + rtl_cipher_initARCFOUR(m_aPDFEncryptor.m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_aPDFEncryptor.getRC4KeyLength(), nullptr, 0); } void PDFWriterImpl::enableStringEncryption( sal_Int32 nObject ) @@ -1103,7 +1103,7 @@ void PDFWriterImpl::enableStringEncryption( sal_Int32 nObject ) if( !m_aContext.Encryption.Encrypt() ) return; - sal_Int32 i = m_aPDFEncryptor.m_nKeyLength; + sal_Int32 i = m_aPDFEncryptor.getKeyLength(); m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>(nObject); m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 8 ); m_aContext.Encryption.EncryptionKey[i++] = static_cast<sal_uInt8>( nObject >> 16 ); @@ -1114,60 +1114,7 @@ void PDFWriterImpl::enableStringEncryption( sal_Int32 nObject ) m_aContext.Encryption.EncryptionKey.data(), i+2, ::comphelper::HashType::MD5)); // initialize the RC4 with the key // key length: see algorithm 3.1, step 4: (N+5) max 16 - rtl_cipher_initARCFOUR(m_aPDFEncryptor.m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_aPDFEncryptor.m_nRC4KeyLength, nullptr, 0); -} - -/* init the encryption engine -1. init the document id, used both for building the document id and for building the encryption key(s) -2. build the encryption key following algorithms described in the PDF specification - */ -uno::Reference< beans::XMaterialHolder > PDFWriterImpl::initEncryption( const OUString& i_rOwnerPassword, - const OUString& i_rUserPassword - ) -{ - uno::Reference< beans::XMaterialHolder > xResult; - if( !i_rOwnerPassword.isEmpty() || !i_rUserPassword.isEmpty() ) - { - rtl::Reference<EncryptionHashTransporter> pTransporter = new EncryptionHashTransporter; - xResult = pTransporter; - - // get padded passwords - sal_uInt8 aPadUPW[ENCRYPTED_PWD_SIZE], aPadOPW[ENCRYPTED_PWD_SIZE]; - padPassword( i_rOwnerPassword.isEmpty() ? i_rUserPassword : i_rOwnerPassword, aPadOPW ); - padPassword( i_rUserPassword, aPadUPW ); - - if( computeODictionaryValue( aPadOPW, aPadUPW, pTransporter->getOValue(), SECUR_128BIT_KEY ) ) - { - pTransporter->getUDigest()->update(aPadUPW, ENCRYPTED_PWD_SIZE); - } - else - xResult.clear(); - - // trash temporary padded cleartext PWDs - rtl_secureZeroMemory (aPadOPW, sizeof(aPadOPW)); - rtl_secureZeroMemory (aPadUPW, sizeof(aPadUPW)); - } - return xResult; -} - -bool PDFWriterImpl::prepareEncryption( const uno::Reference< beans::XMaterialHolder >& xEnc ) -{ - bool bSuccess = false; - EncryptionHashTransporter* pTransporter = EncryptionHashTransporter::getEncHashTransporter( xEnc ); - if( pTransporter ) - { - sal_Int32 nKeyLength = 0, nRC4KeyLength = 0; - sal_Int32 nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, nKeyLength, nRC4KeyLength ); - m_aContext.Encryption.OValue = pTransporter->getOValue(); - bSuccess = computeUDictionaryValue( pTransporter, m_aContext.Encryption, nKeyLength, nAccessPermissions ); - } - if( ! bSuccess ) - { - m_aContext.Encryption.OValue.clear(); - m_aContext.Encryption.UValue.clear(); - m_aContext.Encryption.EncryptionKey.clear(); - } - return bSuccess; + rtl_cipher_initARCFOUR(m_aPDFEncryptor.m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum.data(), m_aPDFEncryptor.getRC4KeyLength(), nullptr, 0); } const tools::Long unsetRun[256] = diff --git a/vcl/source/pdf/PDFEncryptor.cxx b/vcl/source/pdf/PDFEncryptor.cxx index dc309fbbe59e..2c6d37296d42 100644 --- a/vcl/source/pdf/PDFEncryptor.cxx +++ b/vcl/source/pdf/PDFEncryptor.cxx @@ -10,16 +10,38 @@ #include <pdf/PDFEncryptor.hxx> #include <pdf/EncryptionHashTransporter.hxx> -#include <pdf/pdfwriter_impl.hxx> +#include <vcl/pdfwriter.hxx> +#include <comphelper/crypto/Crypto.hxx> #include <comphelper/hash.hxx> +#include <comphelper/random.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <array> + +using namespace css; namespace vcl::pdf { -/************************************************************* -begin i12626 methods +namespace +{ +// 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; -Implements Algorithm 3.2, step 1 only -*/ +constexpr sal_Int32 ENCRYPTED_PWD_SIZE = 32; + +/* pad string used for password in Standard security handler */ +constexpr const std::array<sal_uInt8, 32> s_nPadString + = { 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, + 0x56, 0xFF, 0xFA, 0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, + 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A }; + +/** 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 @@ -36,21 +58,19 @@ void padPassword(std::u16string_view i_rPassword, sal_uInt8* o_pPaddedPW) //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]; + o_pPaddedPW[i] = 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. - -*/ +/** 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, + vcl::PDFEncryptionProperties& io_rProperties, sal_Int32 i_nAccessPermissions) { bool bSuccess = true; @@ -106,10 +126,10 @@ bool computeEncryptionKey(EncryptionHashTransporter* i_pTransporter, 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 -*/ +/** 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) @@ -170,10 +190,8 @@ bool computeODictionaryValue(const sal_uInt8* i_pPaddedOwnerPassword, 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" + 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 } } @@ -192,12 +210,14 @@ bool computeODictionaryValue(const sal_uInt8* i_pPaddedOwnerPassword, 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) -*/ +/** 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) + vcl::PDFEncryptionProperties& io_rProperties, sal_Int32 i_nKeyLength, + sal_Int32 i_nAccessPermissions) { bool bSuccess = true; @@ -219,7 +239,7 @@ bool computeUDictionaryValue(EncryptionHashTransporter* i_pTransporter, 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(s_nPadString.data(), sizeof(s_nPadString)); aDigest.update(io_rProperties.DocumentIdentifier.data(), io_rProperties.DocumentIdentifier.size()); @@ -266,102 +286,7 @@ bool computeUDictionaryValue(EncryptionHashTransporter* i_pTransporter, 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 computeAccessPermissions(const vcl::PDFEncryptionProperties& i_rProperties, sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength) { /* @@ -388,6 +313,92 @@ sal_Int32 computeAccessPermissions(const vcl::PDFWriter::PDFEncryptionProperties return nAccessPermissions; } +} // end anonymous namespace + +PDFEncryptor::PDFEncryptor() +{ + /* prepare the cypher engine */ + m_aCipher = rtl_cipher_createARCFOUR(rtl_Cipher_ModeStream); +} + +PDFEncryptor::~PDFEncryptor() { rtl_cipher_destroyARCFOUR(m_aCipher); } + +/* init the encryption engine +1. init the document id, used both for building the document id and for building the encryption key(s) +2. build the encryption key following algorithms described in the PDF specification + */ +uno::Reference<beans::XMaterialHolder> +PDFEncryptor::initEncryption(const OUString& i_rOwnerPassword, const OUString& i_rUserPassword) +{ + uno::Reference<beans::XMaterialHolder> xResult; + if (!i_rOwnerPassword.isEmpty() || !i_rUserPassword.isEmpty()) + { + rtl::Reference<EncryptionHashTransporter> pTransporter = new EncryptionHashTransporter; + xResult = pTransporter; + + // get padded passwords + sal_uInt8 aPadUPW[ENCRYPTED_PWD_SIZE], aPadOPW[ENCRYPTED_PWD_SIZE]; + padPassword(i_rOwnerPassword.isEmpty() ? i_rUserPassword : i_rOwnerPassword, aPadOPW); + padPassword(i_rUserPassword, aPadUPW); + + if (computeODictionaryValue(aPadOPW, aPadUPW, pTransporter->getOValue(), SECUR_128BIT_KEY)) + { + pTransporter->getUDigest()->update(aPadUPW, ENCRYPTED_PWD_SIZE); + } + else + xResult.clear(); + + // trash temporary padded cleartext PWDs + rtl_secureZeroMemory(aPadOPW, sizeof(aPadOPW)); + rtl_secureZeroMemory(aPadUPW, sizeof(aPadUPW)); + } + return xResult; +} + +bool PDFEncryptor::prepareEncryption( + const uno::Reference<beans::XMaterialHolder>& xEncryptionMaterialHolder, + vcl::PDFEncryptionProperties& rProperties) +{ + bool bSuccess = false; + EncryptionHashTransporter* pTransporter + = EncryptionHashTransporter::getEncHashTransporter(xEncryptionMaterialHolder); + if (pTransporter) + { + sal_Int32 nKeyLength = 0, nRC4KeyLength = 0; + sal_Int32 nAccessPermissions + = computeAccessPermissions(rProperties, nKeyLength, nRC4KeyLength); + rProperties.OValue = pTransporter->getOValue(); + bSuccess + = computeUDictionaryValue(pTransporter, rProperties, nKeyLength, nAccessPermissions); + } + if (!bSuccess) + { + rProperties.OValue.clear(); + rProperties.UValue.clear(); + rProperties.EncryptionKey.clear(); + } + return bSuccess; +} + +void PDFEncryptor::setupKeysAndCheck(vcl::PDFEncryptionProperties& rProperties) +{ + // sanity check + if (rProperties.OValue.size() != ENCRYPTED_PWD_SIZE + || rProperties.UValue.size() != ENCRYPTED_PWD_SIZE + || rProperties.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH) + { + // the field lengths are invalid ? This was not setup by initEncryption. + // do not encrypt after all + rProperties.OValue.clear(); + rProperties.UValue.clear(); + OSL_ENSURE(false, "encryption data failed sanity check, encryption disabled"); + } + else // setup key lengths + { + m_nAccessPermissions = computeAccessPermissions(rProperties, m_nKeyLength, m_nRC4KeyLength); + } +} + } // end vcl::pdf /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
