[Libreoffice-commits] core.git: include/vcl vcl/source xmlsecurity/qa xmlsecurity/source

Miklos Vajna (via logerrit) logerrit at kemper.freedesktop.org
Mon Oct 19 22:51:17 UTC 2020


 include/vcl/filter/PDFiumLibrary.hxx                |    2 
 include/vcl/filter/pdfdocument.hxx                  |    6 +
 vcl/source/filter/ipdf/pdfdocument.cxx              |   82 ++++++++++++++++++--
 vcl/source/pdf/PDFiumLibrary.cxx                    |   12 +-
 xmlsecurity/qa/unit/pdfsigning/data/bad-cert-p1.pdf |binary
 xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx       |   14 +++
 xmlsecurity/source/helper/pdfsignaturehelper.cxx    |   22 +++--
 7 files changed, 118 insertions(+), 20 deletions(-)

New commits:
commit 586f6abee92af3cdabdce034b607b9a046ed3946
Author:     Miklos Vajna <vmiklos at collabora.com>
AuthorDate: Mon Oct 19 16:50:07 2020 +0200
Commit:     Miklos Vajna <vmiklos at collabora.com>
CommitDate: Tue Oct 20 00:50:37 2020 +0200

    xmlsecurity: handle MDP permission during PDF verify
    
    Change-Id: I626fca7c03079fb0374c577dcfe024e7db6ed5b3
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/104529
    Reviewed-by: Miklos Vajna <vmiklos at collabora.com>
    Tested-by: Jenkins

diff --git a/include/vcl/filter/PDFiumLibrary.hxx b/include/vcl/filter/PDFiumLibrary.hxx
index 474f509df46b..96f258127693 100644
--- a/include/vcl/filter/PDFiumLibrary.hxx
+++ b/include/vcl/filter/PDFiumLibrary.hxx
@@ -213,7 +213,7 @@ public:
     std::unique_ptr<PDFiumTextPage> getTextPage();
 
     /// Get bitmap checksum of the page, without annotations/commenting.
-    BitmapChecksum getChecksum();
+    BitmapChecksum getChecksum(int nMDPPerm);
 
     double getWidth();
     double getHeight();
diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx
index be06db286d3e..de0a7b9365ff 100644
--- a/include/vcl/filter/pdfdocument.hxx
+++ b/include/vcl/filter/pdfdocument.hxx
@@ -393,6 +393,7 @@ public:
     size_t GetObjectOffset(size_t nIndex) const;
     const std::vector<std::unique_ptr<PDFElement>>& GetElements() const;
     std::vector<PDFObjectElement*> GetPages();
+    PDFObjectElement* GetCatalog();
     /// Remember the end location of an EOF token.
     void PushBackEOF(size_t nOffset);
     /// Look up object based on object number, possibly by parsing object streams.
@@ -420,6 +421,11 @@ public:
     bool Write(SvStream& rStream);
     /// Get a list of signatures embedded into this document.
     std::vector<PDFObjectElement*> GetSignatureWidgets();
+    /**
+     * Get the value of the "modification detection and prevention" permission:
+     * Valid values are 1, 2 and 3: only 3 allows annotations after signing.
+     */
+    int GetMDPPerm();
     /// Remove the nth signature from read document in the edit buffer.
     bool RemoveSignature(size_t nPosition);
     /// Get byte offsets of the end of incremental updates.
diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx
index 2b6c9d49de34..7c085abe3324 100644
--- a/vcl/source/filter/ipdf/pdfdocument.cxx
+++ b/vcl/source/filter/ipdf/pdfdocument.cxx
@@ -2034,10 +2034,8 @@ static void visitPages(PDFObjectElement* pPages, std::vector<PDFObjectElement*>&
     pPages->setVisiting(false);
 }
 
-std::vector<PDFObjectElement*> PDFDocument::GetPages()
+PDFObjectElement* PDFDocument::GetCatalog()
 {
-    std::vector<PDFObjectElement*> aRet;
-
     PDFReferenceElement* pRoot = nullptr;
 
     PDFTrailerElement* pTrailer = nullptr;
@@ -2057,11 +2055,18 @@ std::vector<PDFObjectElement*> PDFDocument::GetPages()
 
     if (!pRoot)
     {
-        SAL_WARN("vcl.filter", "PDFDocument::GetPages: trailer has no Root key");
-        return aRet;
+        SAL_WARN("vcl.filter", "PDFDocument::GetCatalog: trailer has no Root key");
+        return nullptr;
     }
 
-    PDFObjectElement* pCatalog = pRoot->LookupObject();
+    return pRoot->LookupObject();
+}
+
+std::vector<PDFObjectElement*> PDFDocument::GetPages()
+{
+    std::vector<PDFObjectElement*> aRet;
+
+    PDFObjectElement* pCatalog = GetCatalog();
     if (!pCatalog)
     {
         SAL_WARN("vcl.filter", "PDFDocument::GetPages: trailer has no catalog");
@@ -2134,6 +2139,71 @@ std::vector<PDFObjectElement*> PDFDocument::GetSignatureWidgets()
     return aRet;
 }
 
+int PDFDocument::GetMDPPerm()
+{
+    int nRet = 3;
+
+    std::vector<PDFObjectElement*> aSignatures = GetSignatureWidgets();
+    if (aSignatures.empty())
+    {
+        return nRet;
+    }
+
+    for (const auto& pSignature : aSignatures)
+    {
+        vcl::filter::PDFObjectElement* pSig = pSignature->LookupObject("V");
+        if (!pSig)
+        {
+            SAL_WARN("vcl.filter", "PDFDocument::GetMDPPerm: can't find signature object");
+            continue;
+        }
+
+        auto pReference = dynamic_cast<PDFArrayElement*>(pSig->Lookup("Reference"));
+        if (!pReference || pReference->GetElements().empty())
+        {
+            continue;
+        }
+
+        auto pFirstReference = dynamic_cast<PDFDictionaryElement*>(pReference->GetElements()[0]);
+        if (!pFirstReference)
+        {
+            SAL_WARN("vcl.filter",
+                     "PDFDocument::GetMDPPerm: reference array doesn't contain a dictionary");
+            continue;
+        }
+
+        PDFElement* pTransformParams = pFirstReference->LookupElement("TransformParams");
+        auto pTransformParamsDict = dynamic_cast<PDFDictionaryElement*>(pTransformParams);
+        if (!pTransformParamsDict)
+        {
+            auto pTransformParamsRef = dynamic_cast<PDFReferenceElement*>(pTransformParams);
+            if (pTransformParamsRef)
+            {
+                PDFObjectElement* pTransformParamsObj = pTransformParamsRef->LookupObject();
+                if (pTransformParamsObj)
+                {
+                    pTransformParamsDict = pTransformParamsObj->GetDictionary();
+                }
+            }
+        }
+
+        if (!pTransformParamsDict)
+        {
+            continue;
+        }
+
+        auto pP = dynamic_cast<PDFNumberElement*>(pTransformParamsDict->LookupElement("P"));
+        if (!pP)
+        {
+            return 2;
+        }
+
+        return pP->GetValue();
+    }
+
+    return nRet;
+}
+
 std::vector<unsigned char> PDFDocument::DecodeHexString(PDFHexStringElement const* pElement)
 {
     return svl::crypto::DecodeHexString(pElement->GetValue());
diff --git a/vcl/source/pdf/PDFiumLibrary.cxx b/vcl/source/pdf/PDFiumLibrary.cxx
index 0b5114830c71..41e061bb1f6c 100644
--- a/vcl/source/pdf/PDFiumLibrary.cxx
+++ b/vcl/source/pdf/PDFiumLibrary.cxx
@@ -388,7 +388,7 @@ std::unique_ptr<PDFiumBitmap> PDFiumPageObject::getImageBitmap()
     return pPDFiumBitmap;
 }
 
-BitmapChecksum PDFiumPage::getChecksum()
+BitmapChecksum PDFiumPage::getChecksum(int nMDPPerm)
 {
     size_t nPageWidth = getWidth();
     size_t nPageHeight = getHeight();
@@ -399,11 +399,15 @@ BitmapChecksum PDFiumPage::getChecksum()
         return 0;
     }
 
-    // Intentionally not using FPDF_ANNOT here, annotations/commenting is OK to not affect the
-    // checksum, signature verification wants this.
+    int nFlags = 0;
+    if (nMDPPerm != 3)
+    {
+        // Annotations/commenting should affect the checksum, signature verification wants this.
+        nFlags = FPDF_ANNOT;
+    }
     FPDF_RenderPageBitmap(pPdfBitmap->getPointer(), mpPage, /*start_x=*/0, /*start_y=*/0,
                           nPageWidth, nPageHeight,
-                          /*rotate=*/0, /*flags=*/0);
+                          /*rotate=*/0, nFlags);
     Bitmap aBitmap(Size(nPageWidth, nPageHeight), 24);
     {
         BitmapScopedWriteAccess pWriteAccess(aBitmap);
diff --git a/xmlsecurity/qa/unit/pdfsigning/data/bad-cert-p1.pdf b/xmlsecurity/qa/unit/pdfsigning/data/bad-cert-p1.pdf
new file mode 100644
index 000000000000..04d9950582b0
Binary files /dev/null and b/xmlsecurity/qa/unit/pdfsigning/data/bad-cert-p1.pdf differ
diff --git a/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx b/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
index 586ef54d3075..78c564b26e28 100644
--- a/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
+++ b/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
@@ -388,6 +388,20 @@ CPPUNIT_TEST_FIXTURE(PDFSigningTest, testPartialInBetween)
     CPPUNIT_ASSERT(rInformation.bPartialDocumentSignature);
 }
 
+CPPUNIT_TEST_FIXTURE(PDFSigningTest, testBadCertP1)
+{
+    std::vector<SignatureInformation> aInfos
+        = verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "bad-cert-p1.pdf", 1);
+    CPPUNIT_ASSERT(!aInfos.empty());
+    SignatureInformation& rInformation = aInfos[0];
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 0 (SecurityOperationStatus_UNKNOWN)
+    // - Actual  : 1 (SecurityOperationStatus_OPERATION_SUCCEEDED)
+    // i.e. annotation after a P1 signature was not considered as a bad modification.
+    CPPUNIT_ASSERT_EQUAL(xml::crypto::SecurityOperationStatus::SecurityOperationStatus_UNKNOWN,
+                         rInformation.nStatus);
+}
+
 /// Test writing a PAdES signature.
 CPPUNIT_TEST_FIXTURE(PDFSigningTest, testSigningCertificateAttribute)
 {
diff --git a/xmlsecurity/source/helper/pdfsignaturehelper.cxx b/xmlsecurity/source/helper/pdfsignaturehelper.cxx
index 3942163e970c..a1dde44aaeb7 100644
--- a/xmlsecurity/source/helper/pdfsignaturehelper.cxx
+++ b/xmlsecurity/source/helper/pdfsignaturehelper.cxx
@@ -227,7 +227,8 @@ bool IsCompleteSignature(SvStream& rStream, vcl::filter::PDFDocument& rDocument,
 
 #if HAVE_FEATURE_PDFIUM
 /// Collects the checksum of each page of one version of the PDF.
-void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<BitmapChecksum>& rPageChecksums)
+void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<BitmapChecksum>& rPageChecksums,
+                             int nMDPPerm)
 {
     auto pPdfium = vcl::pdf::PDFiumLibrary::get();
     vcl::pdf::PDFiumDocument aPdfDocument(
@@ -242,7 +243,7 @@ void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<BitmapChecksum
             return;
         }
 
-        BitmapChecksum nPageChecksum = pPdfPage->getChecksum();
+        BitmapChecksum nPageChecksum = pPdfPage->getChecksum(nMDPPerm);
         rPageChecksums.push_back(nPageChecksum);
     }
 }
@@ -250,9 +251,9 @@ void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<BitmapChecksum
 
 /**
  * Checks if incremental updates after singing performed valid modifications only.
- * Annotations/commenting is OK, other changes are not.
+ * nMDPPerm decides if annotations/commenting is OK, other changes are always not.
  */
-bool IsValidSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignature)
+bool IsValidSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignature, int nMDPPerm)
 {
     size_t nSignatureEOF = 0;
     if (!GetEOFOfSignature(pSignature, nSignatureEOF))
@@ -268,7 +269,7 @@ bool IsValidSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignatu
     rStream.Seek(nPos);
     aSignatureStream.Seek(0);
     std::vector<BitmapChecksum> aSignedPages;
-    AnalyizeSignatureStream(aSignatureStream, aSignedPages);
+    AnalyizeSignatureStream(aSignatureStream, aSignedPages, nMDPPerm);
 
     SvMemoryStream aFullStream;
     nPos = rStream.Tell();
@@ -277,7 +278,7 @@ bool IsValidSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignatu
     rStream.Seek(nPos);
     aFullStream.Seek(0);
     std::vector<BitmapChecksum> aAllPages;
-    AnalyizeSignatureStream(aFullStream, aAllPages);
+    AnalyizeSignatureStream(aFullStream, aAllPages, nMDPPerm);
 
     // Fail if any page looks different after signing and at the end. Annotations/commenting doesn't
     // count, though.
@@ -294,7 +295,8 @@ bool IsValidSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignatu
  * @return If we can determinate a result.
  */
 bool ValidateSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignature,
-                       SignatureInformation& rInformation, vcl::filter::PDFDocument& rDocument)
+                       SignatureInformation& rInformation, vcl::filter::PDFDocument& rDocument,
+                       int nMDPPerm)
 {
     vcl::filter::PDFObjectElement* pValue = pSignature->LookupObject("V");
     if (!pValue)
@@ -401,7 +403,7 @@ bool ValidateSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignat
         return false;
     }
     rInformation.bPartialDocumentSignature = !IsCompleteSignature(rStream, rDocument, pSignature);
-    if (!IsValidSignature(rStream, pSignature))
+    if (!IsValidSignature(rStream, pSignature, nMDPPerm))
     {
         SAL_WARN("xmlsecurity.helper", "ValidateSignature: invalid incremental update detected");
         return false;
@@ -451,11 +453,13 @@ bool PDFSignatureHelper::ReadAndVerifySignatureSvStream(SvStream& rStream)
 
     m_aSignatureInfos.clear();
 
+    int nMDPPerm = aDocument.GetMDPPerm();
+
     for (size_t i = 0; i < aSignatures.size(); ++i)
     {
         SignatureInformation aInfo(i);
 
-        if (!ValidateSignature(rStream, aSignatures[i], aInfo, aDocument))
+        if (!ValidateSignature(rStream, aSignatures[i], aInfo, aDocument, nMDPPerm))
             SAL_WARN("xmlsecurity.helper", "failed to determine digest match");
 
         m_aSignatureInfos.push_back(aInfo);


More information about the Libreoffice-commits mailing list