include/vcl/pdfwriter.hxx                      |   22 ++++
 vcl/inc/pdf/EncryptionHashTransporter.hxx      |   29 +++++
 vcl/inc/pdf/IPDFEncryptor.hxx                  |    6 -
 vcl/inc/pdf/PDFEncryptorR6.hxx                 |   49 +++++++++
 vcl/qa/cppunit/pdfexport/PDFEncryptionTest.cxx |  136 +++++++++++++++++++++++--
 vcl/qa/cppunit/pdfexport/pdfexport2.cxx        |   21 ---
 vcl/source/gdi/pdfwriter_impl.cxx              |   88 +++++++++++-----
 vcl/source/pdf/PDFEncryptionInitialization.cxx |    1 
 vcl/source/pdf/PDFEncryptor.cxx                |   25 ----
 vcl/source/pdf/PDFEncryptorR6.cxx              |  127 +++++++++++++++++++++++
 10 files changed, 423 insertions(+), 81 deletions(-)

New commits:
commit 2b7b8106ecdab2f62f5239462b65ed6a2a12395e
Author:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
AuthorDate: Thu Nov 21 11:33:47 2024 +0900
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Sat Dec 21 14:10:35 2024 +0100

    pdf: implement PDFEncryptor for PDF (2.0) encryption V5R6
    
    PDFEncryptorR6 implements PDF 2.0 encryption (ver. 5 rev. 6) using
    AESv3 (256 bit key length). The PDFEncryptorR6 is enabled when the
    PDF output is PDF 2.0 (other versions have been deprecated, so it
    is the only one available in PDF 2.0).
    
    Change-Id: I27f270197910405b5c2378a3873d2495f52f3206
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176885
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/178759
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>
    Tested-by: Jenkins

diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx
index 9c6432203817..73ba0fcb9742 100644
--- a/include/vcl/pdfwriter.hxx
+++ b/include/vcl/pdfwriter.hxx
@@ -95,7 +95,11 @@ struct PDFEncryptionProperties
     // 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> OE; // needed by R6 algorithm
+
     std::vector<sal_uInt8> UValue;
+    std::vector<sal_uInt8> UE; // needed by R6 algorithm
+
     std::vector<sal_uInt8> EncryptionKey;
     std::vector<sal_uInt8> DocumentIdentifier;
 
@@ -107,7 +111,9 @@ struct PDFEncryptionProperties
     void clear()
     {
         OValue.clear();
+        OE.clear();
         UValue.clear();
+        UE.clear();
         EncryptionKey.clear();
     }
 
diff --git a/vcl/inc/pdf/EncryptionHashTransporter.hxx 
b/vcl/inc/pdf/EncryptionHashTransporter.hxx
index 596b6490570a..9b523f3f90f0 100644
--- a/vcl/inc/pdf/EncryptionHashTransporter.hxx
+++ b/vcl/inc/pdf/EncryptionHashTransporter.hxx
@@ -5,7 +5,6 @@
  * 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/.
- *
  */
 
 #pragma once
@@ -28,8 +27,18 @@ namespace vcl::pdf
 */
 class EncryptionHashTransporter : public 
cppu::WeakImplHelper<css::beans::XMaterialHolder>
 {
+    // V2R3
     std::unique_ptr<comphelper::Hash> m_pDigest;
     std::vector<sal_uInt8> maOValue;
+
+    // V5R6
+    std::vector<sal_uInt8> mU;
+    std::vector<sal_uInt8> mUE;
+    std::vector<sal_uInt8> mO;
+    std::vector<sal_uInt8> mOE;
+    std::vector<sal_uInt8> maEncryptionKey;
+
+    // ID
     sal_IntPtr maID;
 
 public:
@@ -43,6 +52,24 @@ public:
 
     void invalidate() { m_pDigest.reset(); }
 
+    std::vector<sal_uInt8> getU() { return mU; }
+    void setU(std::vector<sal_uInt8> const& rU) { mU = rU; }
+
+    std::vector<sal_uInt8> getUE() { return mUE; }
+    void setUE(std::vector<sal_uInt8> const& rUE) { mUE = rUE; }
+
+    std::vector<sal_uInt8> getO() { return mO; }
+    void setO(std::vector<sal_uInt8> const& rO) { mO = rO; }
+
+    std::vector<sal_uInt8> getOE() { return mOE; }
+    void setOE(std::vector<sal_uInt8> const& rOE) { mOE = rOE; }
+
+    std::vector<sal_uInt8> getEncryptionKey() { return maEncryptionKey; }
+    void setEncryptionKey(std::vector<sal_uInt8> const& rEncryptionKey)
+    {
+        maEncryptionKey = rEncryptionKey;
+    }
+
     // XMaterialHolder
     virtual css::uno::Any SAL_CALL getMaterial() override { return 
css::uno::Any(sal_Int64(maID)); }
 
diff --git a/vcl/inc/pdf/IPDFEncryptor.hxx b/vcl/inc/pdf/IPDFEncryptor.hxx
index 706eb82c71ed..61ef8676da46 100644
--- a/vcl/inc/pdf/IPDFEncryptor.hxx
+++ b/vcl/inc/pdf/IPDFEncryptor.hxx
@@ -5,7 +5,6 @@
  * 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/.
- *
  */
 
 #pragma once
@@ -59,7 +58,7 @@ public:
      * Depending on the encryption revision this may not be available. In that
      * case we can expect empty content.
      */
-    virtual std::vector<sal_uInt8> getEncryptedAccessPermissions()
+    virtual std::vector<sal_uInt8> 
getEncryptedAccessPermissions(std::vector<sal_uInt8>& /*rKey*/)
     {
         return std::vector<sal_uInt8>();
     }
@@ -79,6 +78,9 @@ public:
     /** Setup before we start encrypting - remembers the key */
     virtual void setupEncryption(std::vector<sal_uInt8>& rEncryptionKey, 
sal_Int32 nObject) = 0;
 
+    /** Calculate the size of the output (by default the same as input) */
+    virtual sal_uInt64 calculateSizeIncludingHeader(sal_uInt64 nSize) { return 
nSize; }
+
     /** Encrypts the input and stores into the output */
     virtual void encrypt(const void* pInput, sal_uInt64 nInputSize, 
std::vector<sal_uInt8>& rOutput,
                          sal_uInt64 nOutputSize)
diff --git a/vcl/inc/pdf/PDFEncryptorR6.hxx b/vcl/inc/pdf/PDFEncryptorR6.hxx
index 219796ccf0a5..a8812fefcd21 100644
--- a/vcl/inc/pdf/PDFEncryptorR6.hxx
+++ b/vcl/inc/pdf/PDFEncryptorR6.hxx
@@ -13,9 +13,12 @@
 #include <string_view>
 #include <vector>
 #include <vcl/dllapi.h>
+#include <pdf/IPDFEncryptor.hxx>
 
 namespace vcl::pdf
 {
+class EncryptionHashTransporter;
+
 /** Algorithm 2.B: Computing a hash (revision 6 and later)
  *
  * Described in ISO 32000-2:2020(E) - 7.6.4.3.4
@@ -107,6 +110,52 @@ VCL_DLLPUBLIC std::vector<sal_uInt8> createPerms(sal_Int32 
nAccessPermissions,
  */
 VCL_DLLPUBLIC size_t addPaddingToVector(std::vector<sal_uInt8>& rVector, 
size_t nBlockSize);
 
+class EncryptionContext;
+
+/** IPDFEncryptor implementation of PDF encryption version 5 revision 6 added 
in PDF 2.0
+ *
+ * The complete algorithm is defined in PDF 2.0 specification ISO 
32000-2:2020(E)
+ */
+class VCL_DLLPUBLIC PDFEncryptorR6 : public IPDFEncryptor
+{
+    std::unique_ptr<EncryptionContext> m_pEncryptionContext;
+    sal_Int32 m_nAccessPermissions = 0;
+
+public:
+    PDFEncryptorR6();
+    ~PDFEncryptorR6();
+
+    sal_Int32 getVersion() override { return 5; }
+    sal_Int32 getRevision() override { return 6; }
+    sal_Int32 getAccessPermissions() override { return m_nAccessPermissions; }
+    /** Key length - AES 256 bit */
+    sal_Int32 getKeyLength() override { return 256 / 8; }
+
+    std::vector<sal_uInt8> 
getEncryptedAccessPermissions(std::vector<sal_uInt8>& rKey) override;
+
+    static void initEncryption(EncryptionHashTransporter& 
rEncryptionHashTransporter,
+                               const OUString& i_rOwnerPassword, const 
OUString& i_rUserPassword);
+
+    bool prepareEncryption(
+        const css::uno::Reference<css::beans::XMaterialHolder>& 
xEncryptionMaterialHolder,
+        PDFEncryptionProperties& rProperties) override;
+
+    void setupKeysAndCheck(PDFEncryptionProperties& rProperties) override;
+
+    sal_uInt64 calculateSizeIncludingHeader(sal_uInt64 nSize) override;
+
+    void setupEncryption(std::vector<sal_uInt8>& rEncryptionKey, sal_Int32 
nObject) override;
+
+    void setupEncryptionWithIV(std::vector<sal_uInt8>& rInitvector, 
std::vector<sal_uInt8>& rIV);
+
+    /** Encrypts using Algorithm 1.A: Encryption of data using the AES 
algorithms
+     *
+     * Described in ISO 32000-2:2020(E) - 7.6.3.3
+     */
+    void encrypt(const void* pInput, sal_uInt64 nInputSize, 
std::vector<sal_uInt8>& rOutput,
+                 sal_uInt64 nOutputsSize) override;
+};
+
 } // end vcl::pdf
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qa/cppunit/pdfexport/PDFEncryptionTest.cxx 
b/vcl/qa/cppunit/pdfexport/PDFEncryptionTest.cxx
index 40a4eb94c1b4..f4eeb2a9fd05 100644
--- a/vcl/qa/cppunit/pdfexport/PDFEncryptionTest.cxx
+++ b/vcl/qa/cppunit/pdfexport/PDFEncryptionTest.cxx
@@ -10,21 +10,25 @@
 #include <sal/config.h>
 #include <config_oox.h>
 
-#include <algorithm>
-#include <memory>
-#include <string_view>
-
 #include <test/unoapi_test.hxx>
 #include <o3tl/string_view.hxx>
 
-#include <vcl/filter/PDFiumLibrary.hxx>
-#include <vcl/pdfread.hxx>
-#include <comphelper/propertyvalue.hxx>
-#include <cmath>
-
+#include <unotools/mediadescriptor.hxx>
 #include <comphelper/crypto/Crypto.hxx>
 #include <comphelper/hash.hxx>
 #include <comphelper/random.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
+#include <vcl/pdfread.hxx>
+
+#include <com/sun/star/frame/XStorable.hpp>
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+#include <cmath>
 
 #include <pdf/PDFEncryptorR6.hxx>
 
@@ -38,6 +42,9 @@ namespace
 {
 class PDFEncryptionTest : public UnoApiTest
 {
+protected:
+    utl::MediaDescriptor maMediaDescriptor;
+
 public:
     PDFEncryptionTest()
         : UnoApiTest("/vcl/qa/cppunit/pdfexport/data/")
@@ -79,6 +86,48 @@ std::vector<sal_uInt8> parseHex(std::string_view rString)
     return aResult;
 }
 
+CPPUNIT_TEST_FIXTURE(PDFEncryptionTest, testEncryptionRoundtrip_PDF_1_7)
+{
+    loadFromFile(u"BrownFoxLazyDog.odt");
+
+    // Save PDF
+    uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+    maMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+    uno::Sequence<beans::PropertyValue> aFilterData = 
comphelper::InitPropertySequence(
+        { { "SelectPdfVersion", uno::Any(sal_Int32(17)) },
+          { "EncryptFile", uno::Any(true) },
+          { "DocumentOpenPassword", uno::Any(u"secret"_ustr) } });
+    maMediaDescriptor["FilterData"] <<= aFilterData;
+    xStorable->storeToURL(maTempFile.GetURL(), 
maMediaDescriptor.getAsConstPropertyValueList());
+
+    // Load the exported result in PDFium
+    std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = 
parsePDFExport("secret"_ostr);
+    CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+    int nFileVersion = pPdfDocument->getFileVersion();
+    CPPUNIT_ASSERT_EQUAL(17, nFileVersion);
+}
+
+CPPUNIT_TEST_FIXTURE(PDFEncryptionTest, testEncryptionRoundtrip_PDF_2_0)
+{
+    loadFromFile(u"BrownFoxLazyDog.odt");
+
+    // Save PDF
+    uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+    maMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+    uno::Sequence<beans::PropertyValue> aFilterData = 
comphelper::InitPropertySequence(
+        { { "SelectPdfVersion", uno::Any(sal_Int32(20)) },
+          { "EncryptFile", uno::Any(true) },
+          { "DocumentOpenPassword", uno::Any(u"secret"_ustr) } });
+    maMediaDescriptor["FilterData"] <<= aFilterData;
+    xStorable->storeToURL(maTempFile.GetURL(), 
maMediaDescriptor.getAsConstPropertyValueList());
+
+    // Load the exported result in PDFium
+    std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = 
parsePDFExport("secret"_ostr);
+    CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+    int nFileVersion = pPdfDocument->getFileVersion();
+    CPPUNIT_ASSERT_EQUAL(20, nFileVersion);
+}
+
 CPPUNIT_TEST_FIXTURE(PDFEncryptionTest, testComputeHashForR6)
 {
     const sal_uInt8 pOwnerPass[] = { 'T', 'e', 's', 't' };
@@ -257,6 +306,75 @@ CPPUNIT_TEST_FIXTURE(PDFEncryptionTest, testPadding)
         CPPUNIT_ASSERT_EQUAL(sal_uInt8(0x0B), aVector[i]);
 }
 
+CPPUNIT_TEST_FIXTURE(PDFEncryptionTest, testFileDecryption)
+{
+    std::vector<sal_uInt8> aData
+        = 
parseHex("d07efca5cce3c18fd8e344d45d826886d1774c5e1e310c971f8578924f848fc6");
+
+    std::vector<sal_uInt8> iv(aData.begin(), aData.begin() + 16);
+
+    CPPUNIT_ASSERT_EQUAL(std::string("d07efca5cce3c18fd8e344d45d826886"),
+                         comphelper::hashToString(iv));
+
+    std::vector<sal_uInt8> aEncryptedString(aData.begin() + 16, aData.end());
+
+    std::vector<sal_uInt8> U = 
parseHex("7BD210807A0277FECC52C261C442F02E1AD62C1A23553348B8F8AF7320"
+                                        
"DC9978FAB7E65E1BF4CA76F4BE5E6D2AA8C7D5");
+
+    std::vector<sal_uInt8> UE
+        = 
parseHex("67022D91A6BDF3179F488DC9658E54B78A0AD05C6A9C419DCD17A6941C151197");
+
+    const sal_uInt8 pUserPass[] = { 'T', 'e', 's', 't' };
+
+    CPPUNIT_ASSERT_EQUAL(true, vcl::pdf::validateUserPassword(pUserPass, 4, 
U));
+
+    std::vector<sal_uInt8> aDecryptedKey = vcl::pdf::decryptKey(pUserPass, 4, 
U, UE);
+
+    CPPUNIT_ASSERT_EQUAL(
+        
std::string("90e657b78c0315610f3f421bd396ff635fa8fe3cf2ea399e7e1ae23e6185b4fc"),
+        comphelper::hashToString(aDecryptedKey));
+
+    comphelper::Decrypt aDecrypt(aDecryptedKey, iv, 
comphelper::CryptoType::AES_256_CBC);
+
+    std::vector<sal_uInt8> aOutput(aEncryptedString.size(), 0);
+
+    aDecrypt.update(aOutput, aEncryptedString);
+
+    CPPUNIT_ASSERT_EQUAL(
+        std::string("656e2d47420b0b0b0b0b0b0b0b0b0b0b"), // 'en-GB' + padding 
0x0B = 11 chars
+        comphelper::hashToString(aOutput));
+}
+
+CPPUNIT_TEST_FIXTURE(PDFEncryptionTest, testFileEncryption)
+{
+    std::vector<sal_uInt8> aKey
+        = 
parseHex("90e657b78c0315610f3f421bd396ff635fa8fe3cf2ea399e7e1ae23e6185b4fc");
+    std::vector<sal_uInt8> aIV = parseHex("d07efca5cce3c18fd8e344d45d826886");
+
+    static constexpr const auto aData = std::to_array<sal_uInt8>({ 'e', 'n', 
'-', 'G', 'B' });
+
+    std::vector<sal_uInt8> aEncryptedBuffer;
+
+    vcl::pdf::PDFEncryptorR6 aEncryptor;
+    aEncryptor.setupEncryptionWithIV(aKey, aIV);
+    aEncryptor.encrypt(aData.data(), aData.size(), aEncryptedBuffer, 
aData.size());
+
+    CPPUNIT_ASSERT_EQUAL(
+        
std::string("d07efca5cce3c18fd8e344d45d826886d1774c5e1e310c971f8578924f848fc6"),
+        comphelper::hashToString(aEncryptedBuffer));
+
+    // Decrypt
+    std::vector<sal_uInt8> aEncryptedIV(aEncryptedBuffer.begin(), 
aEncryptedBuffer.begin() + 16);
+    std::vector<sal_uInt8> aEncryptedString(aEncryptedBuffer.begin() + 16, 
aEncryptedBuffer.end());
+    comphelper::Decrypt aDecrypt(aKey, aEncryptedIV, 
comphelper::CryptoType::AES_256_CBC);
+    std::vector<sal_uInt8> aOutputString(aEncryptedString.size(), 0);
+    aDecrypt.update(aOutputString, aEncryptedString);
+
+    CPPUNIT_ASSERT_EQUAL(
+        std::string("656e2d47420b0b0b0b0b0b0b0b0b0b0b"), // 'en-GB' + padding 
0x0B = 11 chars
+        comphelper::hashToString(aOutputString));
+}
+
 } // end anonymous namespace
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx 
b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
index 20acdc856aeb..548036654222 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
@@ -750,27 +750,6 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testVersion20)
     CPPUNIT_ASSERT_EQUAL(20, nFileVersion);
 }
 
-CPPUNIT_TEST_FIXTURE(PdfExportTest2, testEncryptionRoundtrip)
-{
-    mxComponent = loadFromDesktop("private:factory/swriter");
-
-    // Save PDF
-    uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
-    aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
-    uno::Sequence<beans::PropertyValue> aFilterData = 
comphelper::InitPropertySequence(
-        { { "SelectPdfVersion", uno::Any(sal_Int32(20)) },
-          { "EncryptFile", uno::Any(true) },
-          { "DocumentOpenPassword", uno::Any(u"secret"_ustr) } });
-    aMediaDescriptor["FilterData"] <<= aFilterData;
-    xStorable->storeToURL(maTempFile.GetURL(), 
aMediaDescriptor.getAsConstPropertyValueList());
-
-    // Load the exported result in PDFium
-    std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = 
parsePDFExport("secret"_ostr);
-    CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
-    int nFileVersion = pPdfDocument->getFileVersion();
-    CPPUNIT_ASSERT_EQUAL(20, nFileVersion);
-}
-
 // Check round-trip of importing and exporting the PDF with PDFium filter,
 // which imports the PDF document as multiple PDFs as graphic object.
 // Each page in the document has one PDF graphic object which content is
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx 
b/vcl/source/gdi/pdfwriter_impl.cxx
index aa841973df17..56311e7f2791 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -96,7 +96,7 @@
 #include <pdf/objectcopier.hxx>
 #include <pdf/pdfwriter_impl.hxx>
 #include <pdf/PdfConfig.hxx>
-#include <pdf/IPDFEncryptor.hxx>
+#include <pdf/PDFEncryptorR6.hxx>
 #include <pdf/PDFEncryptor.hxx>
 #include <o3tl/sorted_vector.hxx>
 #include <frozen/bits/defines.h>
@@ -143,6 +143,12 @@ void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer)
     rBuffer.append( pHexDigits[ nInt & 15 ] );
 }
 
+void appendHexArray(sal_uInt8* pArray, size_t nSize, OStringBuffer& rBuffer)
+{
+    for (size_t i = 0; i < nSize; i++)
+        appendHex(pArray[i], rBuffer);
+}
+
 void appendName( std::u16string_view rStr, OStringBuffer& rBuffer )
 {
 // FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, 
appendix C.1
@@ -1459,7 +1465,10 @@ PDFWriterImpl::PDFWriterImpl( const 
PDFWriter::PDFWriterContext& rContext,
 
     if (xEncryptionMaterialHolder.is())
     {
-        m_pPDFEncryptor.reset(new PDFEncryptor);
+        if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0 || 
m_aContext.Version == PDFWriter::PDFVersion::PDF_A_4)
+            m_pPDFEncryptor.reset(new PDFEncryptorR6);
+        else
+            m_pPDFEncryptor.reset(new PDFEncryptor);
         m_pPDFEncryptor->prepareEncryption(xEncryptionMaterialHolder, 
m_aContext.Encryption);
     }
 
@@ -1618,10 +1627,10 @@ inline void 
PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInSt
             *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
         }
         //encrypt in place
-        m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChars, 
m_vEncryptionBuffer, nChars);
+        std::vector<sal_uInt8> aNewBuffer(nChars);
+        m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChars, 
aNewBuffer, nChars);
         //now append, hexadecimal (appendHex), the encrypted result
-        for(int i = 0; i < nChars; i++)
-            appendHex( m_vEncryptionBuffer[i], rOutBuffer );
+        appendHexArray(aNewBuffer.data(), aNewBuffer.size(), rOutBuffer);
     }
     else
         PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
@@ -1639,7 +1648,7 @@ inline void PDFWriterImpl::appendLiteralStringEncrypt( 
std::string_view rInStrin
         //encrypt the string in a buffer, then append it
         enableStringEncryption(nInObjectNumber);
         m_pPDFEncryptor->encrypt(rInString.data(), nChars, 
m_vEncryptionBuffer, nChars);
-        appendLiteralString( 
reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
+        
appendLiteralString(reinterpret_cast<char*>(m_vEncryptionBuffer.data()), 
m_vEncryptionBuffer.size(), rOutBuffer);
     }
     else
         appendLiteralString( rInString.data(), nChars , rOutBuffer );
@@ -1738,34 +1747,42 @@ bool PDFWriterImpl::writeBufferBytes( const void* 
pBuffer, sal_uInt64 nBytes )
     }
 
     sal_uInt64 nWritten;
-    if( m_pCodec )
+    sal_uInt64 nActualSize = nBytes;
+
+    // we are compressing the stream
+    if (m_pCodec)
     {
         m_pCodec->Write( *m_pMemStream, static_cast<const 
sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
         nWritten = nBytes;
     }
     else
     {
+        // is it encrypted?
         bool bStreamEncryption = m_pPDFEncryptor && 
m_pPDFEncryptor->isStreamEncryptionEnabled();
         if (bStreamEncryption)
         {
-            m_vEncryptionBuffer.resize(nBytes);
-            m_pPDFEncryptor->encrypt(pBuffer, nBytes, m_vEncryptionBuffer, 
nBytes);
+            nActualSize = 
m_pPDFEncryptor->calculateSizeIncludingHeader(nActualSize);
+            m_vEncryptionBuffer.resize(nActualSize);
+            m_pPDFEncryptor->encrypt(pBuffer, nBytes, m_vEncryptionBuffer, 
nActualSize);
         }
 
         const void* pWriteBuffer = bStreamEncryption ? 
m_vEncryptionBuffer.data() : pBuffer;
-        m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), 
static_cast<sal_uInt32>(nBytes));
+        m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), 
sal_uInt32(nActualSize));
 
-        if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
+
+        if (m_aFile.write(pWriteBuffer, nActualSize, nWritten) != 
osl::File::E_None)
             nWritten = 0;
 
-        if( nWritten != nBytes )
+        if (nWritten != nActualSize)
         {
             m_aFile.close();
             m_bOpen = false;
+            return false;
         }
+        return true;
     }
 
-    return nWritten == nBytes;
+    return nWritten == nActualSize;
 }
 
 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, 
PDFWriter::Orientation eOrientation )
@@ -6160,12 +6177,34 @@ sal_Int32 PDFWriterImpl::emitEncrypt()
         aWriter.startObject(nObject);
         aWriter.startDict();
         aWriter.write("/Filter", "/Standard");
-        aWriter.write("/V", 2);
-        aWriter.write("/Length", 128);
-        aWriter.write("/R", 3);
-        // emit the owner password, must not be encrypted
-        aWriter.writeHexArray("/O", rProperties.OValue.data(), 
rProperties.OValue.size());
-        aWriter.writeHexArray("/U", rProperties.UValue.data(), 
rProperties.UValue.size());
+        aWriter.write("/V", m_pPDFEncryptor->getVersion());
+        aWriter.write("/Length", m_pPDFEncryptor->getKeyLength() * 8);
+        aWriter.write("/R", m_pPDFEncryptor->getRevision());
+
+        if (m_pPDFEncryptor->getVersion() == 5 && 
m_pPDFEncryptor->getRevision() == 6)
+        {
+            // emit the owner password, must not be encrypted
+            aWriter.writeHexArray("/U", rProperties.UValue.data(), 
rProperties.UValue.size());
+            aWriter.writeHexArray("/UE", rProperties.UE.data(), 
rProperties.UE.size());
+            aWriter.writeHexArray("/O", rProperties.OValue.data(), 
rProperties.OValue.size());
+            aWriter.writeHexArray("/OE", rProperties.OE.data(), 
rProperties.OE.size());
+
+            // Encrypted perms
+            std::vector<sal_uInt8> aEncryptedPermissions = 
m_pPDFEncryptor->getEncryptedAccessPermissions(rProperties.EncryptionKey);
+            aWriter.writeHexArray("/Perms", aEncryptedPermissions.data(), 
aEncryptedPermissions.size());
+
+            // Write content filter stuff - to select we want AESv3 256bit
+            aWriter.write("/CF", "<</StdCF <</CFM /AESV3 /Length 256>>>>");
+            aWriter.write("/StmF", "/StdCF");
+            aWriter.write("/StrF", "/StdCF");
+            aWriter.write("/EncryptMetadata", " false ");
+        }
+        else
+        {
+            // emit the owner password, must not be encrypted
+            aWriter.writeHexArray("/U", rProperties.UValue.data(), 
rProperties.UValue.size());
+            aWriter.writeHexArray("/O", rProperties.OValue.data(), 
rProperties.OValue.size());
+        }
         aWriter.write("/P", m_pPDFEncryptor->getAccessPermissions());
         aWriter.endDict();
         aWriter.endObject();
@@ -9823,15 +9862,10 @@ bool PDFWriterImpl::writeBitmapObject( const 
BitmapEmit& rObject, bool bMask )
                     m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
                 }
                 //encrypt the colorspace lookup table
-                m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChar, 
m_vEncryptionBuffer, nChar);
+                std::vector<sal_uInt8> aOutputBuffer(nChar);
+                m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChar, 
aOutputBuffer, nChar);
                 //now queue the data for output
-                nChar = 0;
-                for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); 
i++ )
-                {
-                    appendHex(m_vEncryptionBuffer[nChar++], aLine );
-                    appendHex(m_vEncryptionBuffer[nChar++], aLine );
-                    appendHex(m_vEncryptionBuffer[nChar++], aLine );
-                }
+                appendHexArray(aOutputBuffer.data(), aOutputBuffer.size(), 
aLine);
             }
             else //no encryption requested (PDF/A-1a program flow drops here)
             {
diff --git a/vcl/source/pdf/PDFEncryptionInitialization.cxx 
b/vcl/source/pdf/PDFEncryptionInitialization.cxx
index df06968fc290..c58898329eac 100644
--- a/vcl/source/pdf/PDFEncryptionInitialization.cxx
+++ b/vcl/source/pdf/PDFEncryptionInitialization.cxx
@@ -24,6 +24,7 @@ css::uno::Reference<css::beans::XMaterialHolder> 
initEncryption(const OUString&
 {
     rtl::Reference<EncryptionHashTransporter> pTransporter = new 
EncryptionHashTransporter;
     PDFEncryptor::initEncryption(*pTransporter, i_rOwnerPassword, 
i_rUserPassword);
+    PDFEncryptorR6::initEncryption(*pTransporter, i_rOwnerPassword, 
i_rUserPassword);
     return pTransporter;
 }
 
diff --git a/vcl/source/pdf/PDFEncryptorR6.cxx 
b/vcl/source/pdf/PDFEncryptorR6.cxx
index b3e6f9e3059e..c16d7cb1a276 100644
--- a/vcl/source/pdf/PDFEncryptorR6.cxx
+++ b/vcl/source/pdf/PDFEncryptorR6.cxx
@@ -14,11 +14,14 @@
 #include <comphelper/hash.hxx>
 #include <comphelper/random.hxx>
 
+using namespace css;
+
 namespace vcl::pdf
 {
 namespace
 {
 constexpr size_t IV_SIZE = 16;
+constexpr size_t BLOCK_SIZE = 16;
 constexpr size_t KEY_SIZE = 32;
 constexpr size_t SALT_SIZE = 8;
 
@@ -155,7 +158,7 @@ std::vector<sal_uInt8> encryptPerms(std::vector<sal_uInt8>& 
rPerms,
 std::vector<sal_uInt8> createPerms(sal_Int32 nAccessPermissions, bool 
bEncryptMetadata)
 {
     std::vector<sal_uInt8> aPermsCreated;
-    generateBytes(aPermsCreated, 16);
+    generateBytes(aPermsCreated, 16); // fill with random data - mainly for 
last 4 bytes
     aPermsCreated[0] = sal_uInt8(nAccessPermissions);
     aPermsCreated[1] = sal_uInt8(nAccessPermissions >> 8);
     aPermsCreated[2] = sal_uInt8(nAccessPermissions >> 16);
@@ -249,6 +252,128 @@ size_t addPaddingToVector(std::vector<sal_uInt8>& 
rVector, size_t nBlockSize)
     return nPaddedSize;
 }
 
+class VCL_DLLPUBLIC EncryptionContext
+{
+private:
+    std::vector<sal_uInt8> maKey;
+    std::vector<sal_uInt8> maInitVector;
+
+public:
+    EncryptionContext(std::vector<sal_uInt8> const& rKey, 
std::vector<sal_uInt8> const& rIV)
+        : maKey(rKey)
+        , maInitVector(rIV)
+    {
+    }
+
+    /** Algorithm 1.A: Encryption of data using the AES algorithms
+     *
+     **/
+    void encrypt(const void* pInput, sal_uInt64 nInputSize, 
std::vector<sal_uInt8>& rOutput)
+    {
+        comphelper::Encrypt aEncrypt(maKey, maInitVector, 
comphelper::CryptoType::AES_256_CBC);
+        const sal_uInt8* pInputBytes = static_cast<const sal_uInt8*>(pInput);
+        std::vector<sal_uInt8> aInput(pInputBytes, pInputBytes + nInputSize);
+        size_t nPaddedSize = addPaddingToVector(aInput, BLOCK_SIZE);
+        std::vector<sal_uInt8> aOutput(nPaddedSize);
+        aEncrypt.update(aOutput, aInput);
+        rOutput.resize(nPaddedSize + IV_SIZE);
+        std::copy(maInitVector.begin(), maInitVector.end(), rOutput.begin());
+        std::copy(aOutput.begin(), aOutput.end(), rOutput.begin() + IV_SIZE);
+    }
+};
+
+PDFEncryptorR6::PDFEncryptorR6() = default;
+PDFEncryptorR6::~PDFEncryptorR6() = default;
+
+std::vector<sal_uInt8> 
PDFEncryptorR6::getEncryptedAccessPermissions(std::vector<sal_uInt8>& rKey)
+{
+    std::vector<sal_uInt8> aPerms = createPerms(m_nAccessPermissions, false);
+    return encryptPerms(aPerms, rKey);
+}
+
+void PDFEncryptorR6::initEncryption(EncryptionHashTransporter& 
rEncryptionHashTransporter,
+                                    const OUString& rOwnerPassword, const 
OUString& rUserPassword)
+{
+    if (rUserPassword.isEmpty())
+        return;
+
+    std::vector<sal_uInt8> aEncryptionKey = vcl::pdf::generateKey();
+    rEncryptionHashTransporter.setEncryptionKey(aEncryptionKey);
+
+    std::vector<sal_uInt8> U;
+    std::vector<sal_uInt8> UE;
+
+    OString pUserPasswordUtf8 = OUStringToOString(rUserPassword, 
RTL_TEXTENCODING_UTF8);
+    vcl::pdf::generateUandUE(reinterpret_cast<const 
sal_uInt8*>(pUserPasswordUtf8.getStr()),
+                             pUserPasswordUtf8.getLength(), aEncryptionKey, U, 
UE);
+
+    rEncryptionHashTransporter.setU(U);
+    rEncryptionHashTransporter.setUE(UE);
+
+    OUString aOwnerPasswordToUse = rOwnerPassword.isEmpty() ? rUserPassword : 
rOwnerPassword;
+
+    std::vector<sal_uInt8> O;
+    std::vector<sal_uInt8> OE;
+
+    OString pOwnerPasswordUtf8 = OUStringToOString(aOwnerPasswordToUse, 
RTL_TEXTENCODING_UTF8);
+    vcl::pdf::generateOandOE(reinterpret_cast<const 
sal_uInt8*>(pOwnerPasswordUtf8.getStr()),
+                             pOwnerPasswordUtf8.getLength(), aEncryptionKey, 
U, O, OE);
+
+    rEncryptionHashTransporter.setO(O);
+    rEncryptionHashTransporter.setOE(OE);
+}
+
+bool PDFEncryptorR6::prepareEncryption(
+    const uno::Reference<beans::XMaterialHolder>& xEncryptionMaterialHolder,
+    vcl::PDFEncryptionProperties& rProperties)
+{
+    auto* pTransporter
+        = 
EncryptionHashTransporter::getEncHashTransporter(xEncryptionMaterialHolder);
+
+    if (!pTransporter)
+    {
+        rProperties.clear();
+        return false;
+    }
+
+    rProperties.UValue = pTransporter->getU();
+    rProperties.UE = pTransporter->getUE();
+    rProperties.OValue = pTransporter->getO();
+    rProperties.OE = pTransporter->getOE();
+    rProperties.EncryptionKey = pTransporter->getEncryptionKey();
+
+    return true;
+}
+
+void PDFEncryptorR6::setupKeysAndCheck(vcl::PDFEncryptionProperties& 
rProperties)
+{
+    m_nAccessPermissions = rProperties.getAccessPermissions();
+}
+
+sal_uInt64 PDFEncryptorR6::calculateSizeIncludingHeader(sal_uInt64 nSize)
+{
+    return IV_SIZE + comphelper::roundUp<sal_uInt64>(nSize, BLOCK_SIZE);
+}
+
+void PDFEncryptorR6::setupEncryption(std::vector<sal_uInt8>& rEncryptionKey, 
sal_Int32 /*nObject*/)
+{
+    std::vector<sal_uInt8> aInitVector;
+    generateBytes(aInitVector, IV_SIZE);
+    m_pEncryptionContext = std::make_unique<EncryptionContext>(rEncryptionKey, 
aInitVector);
+}
+
+void PDFEncryptorR6::setupEncryptionWithIV(std::vector<sal_uInt8>& 
rEncryptionKey,
+                                           std::vector<sal_uInt8>& rInitvector)
+{
+    m_pEncryptionContext = std::make_unique<EncryptionContext>(rEncryptionKey, 
rInitvector);
+}
+
+void PDFEncryptorR6::encrypt(const void* pInput, sal_uInt64 nInputSize,
+                             std::vector<sal_uInt8>& rOutput, sal_uInt64 
/*nOutputSize*/)
+{
+    m_pEncryptionContext->encrypt(pInput, nInputSize, rOutput);
+}
+
 } // end vcl::pdf
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 7ea66a327fd8d66350dbb931325104ad19097ec1
Author:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
AuthorDate: Wed Nov 20 22:41:28 2024 +0900
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Sat Dec 21 14:10:26 2024 +0100

    pdf: move creating access permissions to common place
    
    The access permission are needed independent to the kind of PDF
    encryption so they should be in a common place so they can be
    reused. PDFEncryptionProperties is the best place to create the
    value anyway.
    
    Change-Id: Ic6e6c3d9a8cb314523c0305eba9e64f3734d52b5
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176884
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/178758
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>

diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx
index 14c6265f3d88..9c6432203817 100644
--- a/include/vcl/pdfwriter.hxx
+++ b/include/vcl/pdfwriter.hxx
@@ -110,6 +110,22 @@ struct PDFEncryptionProperties
         UValue.clear();
         EncryptionKey.clear();
     }
+
+    sal_Int32 getAccessPermissions() const
+    {
+        sal_Int32 nAccessPermissions = 0xfffff0c0;
+
+        nAccessPermissions |= CanPrintTheDocument ? 1 << 2 : 0;
+        nAccessPermissions |= CanModifyTheContent ? 1 << 3 : 0;
+        nAccessPermissions |= CanCopyOrExtract ? 1 << 4 : 0;
+        nAccessPermissions |= CanAddOrModify ? 1 << 5 : 0;
+        nAccessPermissions |= CanFillInteractive ? 1 << 8 : 0;
+        nAccessPermissions |= CanExtractForAccessibility ? 1 << 9 : 0;
+        nAccessPermissions |= CanAssemble ? 1 << 10 : 0;
+        nAccessPermissions |= CanPrintFull ? 1 << 11 : 0;
+
+        return nAccessPermissions;
+    }
 };
 
 class PDFWriter
diff --git a/vcl/source/pdf/PDFEncryptor.cxx b/vcl/source/pdf/PDFEncryptor.cxx
index a5425f014ae0..315a2c6e27cc 100644
--- a/vcl/source/pdf/PDFEncryptor.cxx
+++ b/vcl/source/pdf/PDFEncryptor.cxx
@@ -289,28 +289,13 @@ bool computeUDictionaryValue(EncryptionHashTransporter* 
i_pTransporter,
 sal_Int32 computeAccessPermissions(const vcl::PDFEncryptionProperties& 
i_rProperties,
                                    sal_Int32& o_rKeyLength, sal_Int32& 
o_rRC4KeyLength)
 {
-    /*
-    2) compute the access permissions, in numerical form
+    o_rKeyLength = SECUR_128BIT_KEY;
 
-    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;
+    // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16,
+    // thus maximum permitted value is 16
+    o_rRC4KeyLength = 16;
 
-    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;
+    return i_rProperties.getAccessPermissions();
 }
 
 } // end anonymous namespace

Reply via email to