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

Reply via email to