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: */

Reply via email to