[Libreoffice-commits] core.git: Branch 'distro/collabora/cp-6.0' - include/vcl vcl/source xmlsecurity/Library_xmlsecurity.mk xmlsecurity/qa xmlsecurity/source xmlsecurity/workben
Miklos Vajna (via logerrit)
logerrit at kemper.freedesktop.org
Fri Dec 18 12:55:00 UTC 2020
include/vcl/filter/PDFiumLibrary.hxx | 30 ++++
vcl/source/pdf/PDFiumLibrary.cxx | 31 ++++
xmlsecurity/Library_xmlsecurity.mk | 5
xmlsecurity/qa/unit/signing/data/hide-and-replace-shadow-file-signed-2.pdf |binary
xmlsecurity/qa/unit/signing/signing.cxx | 18 ++
xmlsecurity/source/pdfio/pdfdocument.cxx | 70 ++++++++++
xmlsecurity/workben/pdfverify.cxx | 5
7 files changed, 156 insertions(+), 3 deletions(-)
New commits:
commit 3bd3fbe77ba0fb86e82f157b15f9626d586e96fa
Author: Miklos Vajna <vmiklos at collabora.com>
AuthorDate: Fri Sep 4 17:17:48 2020 +0200
Commit: Miklos Vajna <vmiklos at collabora.com>
CommitDate: Fri Dec 18 13:54:19 2020 +0100
xmlsecurity: pdf incremental updates that are non-commenting are invalid
I.e. it's OK to add incremental updates for annotation/commenting
purposes and that doesn't invalite existing signatures. Everything else
does.
(cherry picked from commit 61834cd574568613f0b0a2ee099a60fa5a8d9804)
Conflicts:
include/vcl/filter/PDFiumLibrary.hxx
vcl/source/pdf/PDFiumLibrary.cxx
xmlsecurity/qa/unit/signing/signing.cxx
xmlsecurity/source/pdfio/pdfdocument.cxx
xmlsecurity/workben/pdfverify.cxx
Change-Id: I4607c242b3c6f6b01517b02407e9e7a095e2e069
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/107944
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
Reviewed-by: Miklos Vajna <vmiklos at collabora.com>
diff --git a/include/vcl/filter/PDFiumLibrary.hxx b/include/vcl/filter/PDFiumLibrary.hxx
index 1f6d97045088..60ece6b7cbc9 100644
--- a/include/vcl/filter/PDFiumLibrary.hxx
+++ b/include/vcl/filter/PDFiumLibrary.hxx
@@ -17,6 +17,9 @@
#include <memory>
#include <rtl/instance.hxx>
#include <vcl/dllapi.h>
+#include <vcl/checksum.hxx>
+
+#include <fpdfview.h>
namespace vcl
{
@@ -33,6 +36,33 @@ public:
~PDFium();
};
+class VCL_DLLPUBLIC PDFiumPage final
+{
+private:
+ FPDF_PAGE mpPage;
+
+private:
+ PDFiumPage(const PDFiumPage&) = delete;
+ PDFiumPage& operator=(const PDFiumPage&) = delete;
+
+public:
+ PDFiumPage(FPDF_PAGE pPage)
+ : mpPage(pPage)
+ {
+ }
+
+ ~PDFiumPage()
+ {
+ if (mpPage)
+ FPDF_ClosePage(mpPage);
+ }
+
+ FPDF_PAGE getPointer() { return mpPage; }
+
+ /// Get bitmap checksum of the page, without annotations/commenting.
+ BitmapChecksum getChecksum();
+};
+
struct PDFiumLibrary : public rtl::StaticWithInit<std::shared_ptr<PDFium>, PDFiumLibrary>
{
std::shared_ptr<PDFium> operator()() { return std::make_shared<PDFium>(); }
diff --git a/vcl/source/pdf/PDFiumLibrary.cxx b/vcl/source/pdf/PDFiumLibrary.cxx
index 9d822c34642c..a8774b15bb4e 100644
--- a/vcl/source/pdf/PDFiumLibrary.cxx
+++ b/vcl/source/pdf/PDFiumLibrary.cxx
@@ -15,6 +15,9 @@
#include <vcl/filter/PDFiumLibrary.hxx>
#include <fpdf_doc.h>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapaccess.hxx>
+
namespace vcl
{
namespace pdf
@@ -31,6 +34,34 @@ PDFium::PDFium()
PDFium::~PDFium() { FPDF_DestroyLibrary(); }
+BitmapChecksum PDFiumPage::getChecksum()
+{
+ size_t nPageWidth = FPDF_GetPageWidth(mpPage);
+ size_t nPageHeight = FPDF_GetPageHeight(mpPage);
+ FPDF_BITMAP pPdfBitmap = FPDFBitmap_Create(nPageWidth, nPageHeight, /*alpha=*/1);
+ if (!pPdfBitmap)
+ {
+ return 0;
+ }
+
+ // Intentionally not using FPDF_ANNOT here, annotations/commenting is OK to not affect the
+ // checksum, signature verification wants this.
+ FPDF_RenderPageBitmap(pPdfBitmap, mpPage, /*start_x=*/0, /*start_y=*/0, nPageWidth, nPageHeight,
+ /*rotate=*/0, /*flags=*/0);
+ Bitmap aBitmap(Size(nPageWidth, nPageHeight), 24);
+ {
+ Bitmap::ScopedWriteAccess pWriteAccess(aBitmap);
+ const auto pPdfBuffer = static_cast<const sal_uInt8*>(FPDFBitmap_GetBuffer(pPdfBitmap));
+ const int nStride = FPDFBitmap_GetStride(pPdfBitmap);
+ for (size_t nRow = 0; nRow < nPageHeight; ++nRow)
+ {
+ const sal_uInt8* pPdfLine = pPdfBuffer + (nStride * nRow);
+ pWriteAccess->CopyScanline(nRow, pPdfLine, ScanlineFormat::N32BitTcBgra, nStride);
+ }
+ }
+ return aBitmap.GetChecksum();
+}
+
} // end pdf
} // end vcl
diff --git a/xmlsecurity/Library_xmlsecurity.mk b/xmlsecurity/Library_xmlsecurity.mk
index 22d27c717155..26d1549089c2 100644
--- a/xmlsecurity/Library_xmlsecurity.mk
+++ b/xmlsecurity/Library_xmlsecurity.mk
@@ -20,7 +20,10 @@ $(eval $(call gb_Library_add_defs,xmlsecurity,\
-DXMLSECURITY_DLLIMPLEMENTATION \
))
-$(eval $(call gb_Library_use_externals,xmlsecurity,boost_headers))
+$(eval $(call gb_Library_use_externals,xmlsecurity,\
+ boost_headers \
+ $(if $(filter PDFIUM,$(BUILD_TYPE)),pdfium) \
+))
$(eval $(call gb_Library_set_precompiled_header,xmlsecurity,$(SRCDIR)/xmlsecurity/inc/pch/precompiled_xmlsecurity))
diff --git a/xmlsecurity/qa/unit/signing/data/hide-and-replace-shadow-file-signed-2.pdf b/xmlsecurity/qa/unit/signing/data/hide-and-replace-shadow-file-signed-2.pdf
new file mode 100644
index 000000000000..f2b1a71096b2
Binary files /dev/null and b/xmlsecurity/qa/unit/signing/data/hide-and-replace-shadow-file-signed-2.pdf differ
diff --git a/xmlsecurity/qa/unit/signing/signing.cxx b/xmlsecurity/qa/unit/signing/signing.cxx
index 265f0e5acbb0..6ea6275aaee6 100644
--- a/xmlsecurity/qa/unit/signing/signing.cxx
+++ b/xmlsecurity/qa/unit/signing/signing.cxx
@@ -93,6 +93,7 @@ public:
void testPDFGood();
/// Test a typical PDF where the signature is bad.
void testPDFBad();
+ void testPDFHideAndReplace();
/// Test a typical PDF which is not signed.
void testPDFNo();
#endif
@@ -133,6 +134,7 @@ public:
#if HAVE_FEATURE_PDFIMPORT
CPPUNIT_TEST(testPDFGood);
CPPUNIT_TEST(testPDFBad);
+ CPPUNIT_TEST(testPDFHideAndReplace);
CPPUNIT_TEST(testPDFNo);
#endif
CPPUNIT_TEST(test96097Calc);
@@ -497,6 +499,22 @@ void SigningTest::testPDFBad()
CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN), static_cast<int>(pObjectShell->GetDocumentSignatureState()));
}
+void SigningTest::testPDFHideAndReplace()
+{
+ createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY)
+ + "hide-and-replace-shadow-file-signed-2.pdf");
+ SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
+ CPPUNIT_ASSERT(pBaseModel);
+ SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
+ CPPUNIT_ASSERT(pObjectShell);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2 (BROKEN)
+ // - Actual : 6 (NOTVALIDATED_PARTIAL_OK)
+ // i.e. a non-commenting update after a signature was not marked as invalid.
+ CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN),
+ static_cast<int>(pObjectShell->GetDocumentSignatureState()));
+}
+
void SigningTest::testPDFNo()
{
createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "no.pdf");
diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx
index 8d6aeafdd9f3..21ea3c324d1a 100644
--- a/xmlsecurity/source/pdfio/pdfdocument.cxx
+++ b/xmlsecurity/source/pdfio/pdfdocument.cxx
@@ -13,6 +13,9 @@
#include <memory>
#include <vector>
+#include <config_features.h>
+
+#include <vcl/filter/PDFiumLibrary.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <comphelper/processfactory.hxx>
@@ -34,6 +37,7 @@
#include <svl/sigstruct.hxx>
#include <svl/cryptosign.hxx>
+#include <vcl/bitmap.hxx>
#ifdef XMLSEC_CRYPTO_NSS
#include <cert.h>
@@ -162,6 +166,67 @@ bool IsCompleteSignature(SvStream& rStream, vcl::filter::PDFDocument& rDocument,
size_t nFileEnd = rStream.Tell();
return std::find(rAllEOFs.begin(), rAllEOFs.end(), nFileEnd) != rAllEOFs.end();
}
+
+/// Collects the checksum of each page of one version of the PDF.
+void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<BitmapChecksum>& rPageChecksums)
+{
+#if HAVE_FEATURE_PDFIUM
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ FPDF_DOCUMENT pPdfDocument(
+ FPDF_LoadMemDocument(rStream.GetData(), rStream.GetSize(), /*password=*/nullptr));
+
+ int nPageCount = FPDF_GetPageCount(pPdfDocument);
+ for (int nPage = 0; nPage < nPageCount; ++nPage)
+ {
+ auto pPdfPage = std::make_unique<vcl::pdf::PDFiumPage>(FPDF_LoadPage(pPdfDocument, nPage));
+ if (!pPdfPage)
+ {
+ return;
+ }
+
+ BitmapChecksum nPageChecksum = pPdfPage->getChecksum();
+ rPageChecksums.push_back(nPageChecksum);
+ }
+ FPDF_CloseDocument(pPdfDocument);
+#else
+ (void)rStream;
+#endif
+}
+
+/**
+ * Checks if incremental updates after singing performed valid modifications only.
+ * Annotations/commenting is OK, other changes are not.
+ */
+bool IsValidSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignature)
+{
+ size_t nSignatureEOF = 0;
+ if (!GetEOFOfSignature(pSignature, nSignatureEOF))
+ {
+ return false;
+ }
+
+ SvMemoryStream aSignatureStream;
+ sal_uInt64 nPos = rStream.Tell();
+ rStream.Seek(0);
+ aSignatureStream.WriteStream(rStream, nSignatureEOF);
+ rStream.Seek(nPos);
+ aSignatureStream.Seek(0);
+ std::vector<BitmapChecksum> aSignedPages;
+ AnalyizeSignatureStream(aSignatureStream, aSignedPages);
+
+ SvMemoryStream aFullStream;
+ nPos = rStream.Tell();
+ rStream.Seek(0);
+ aFullStream.WriteStream(rStream);
+ rStream.Seek(nPos);
+ aFullStream.Seek(0);
+ std::vector<BitmapChecksum> aAllPages;
+ AnalyizeSignatureStream(aFullStream, aAllPages);
+
+ // Fail if any page looks different after signing and at the end. Annotations/commenting doesn't
+ // count, though.
+ return aSignedPages == aAllPages;
+}
}
namespace xmlsecurity
@@ -272,6 +337,11 @@ bool ValidateSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignat
return false;
}
rInformation.bPartialDocumentSignature = !IsCompleteSignature(rStream, rDocument, pSignature);
+ if (!IsValidSignature(rStream, pSignature))
+ {
+ SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: invalid incremental update detected");
+ return false;
+ }
// At this point there is no obviously missing info to validate the
// signature.
diff --git a/xmlsecurity/workben/pdfverify.cxx b/xmlsecurity/workben/pdfverify.cxx
index 7ddb2623944c..92429864080b 100644
--- a/xmlsecurity/workben/pdfverify.cxx
+++ b/xmlsecurity/workben/pdfverify.cxx
@@ -20,6 +20,7 @@
#include <vcl/pngwrite.hxx>
#include <vcl/svapp.hxx>
#include <vcl/graphicfilter.hxx>
+#include <comphelper/scopeguard.hxx>
#include <pdfio/pdfdocument.hxx>
@@ -72,11 +73,11 @@ int pdfVerify(int nArgc, char** pArgv)
uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xMultiComponentFactory, uno::UNO_QUERY);
comphelper::setProcessServiceFactory(xMultiServiceFactory);
+ InitVCL();
+ comphelper::ScopeGuard g([] { DeInitVCL(); });
if (nArgc > 3 && OString(pArgv[3]) == "-p")
{
- InitVCL();
generatePreview(pArgv[1], pArgv[2]);
- DeInitVCL();
return 0;
}
More information about the Libreoffice-commits
mailing list