diff options
-rw-r--r-- | include/vcl/filter/PDFiumLibrary.hxx | 2 | ||||
-rw-r--r-- | include/vcl/filter/pdfdocument.hxx | 6 | ||||
-rw-r--r-- | vcl/source/filter/ipdf/pdfdocument.cxx | 82 | ||||
-rw-r--r-- | vcl/source/pdf/PDFiumLibrary.cxx | 12 | ||||
-rw-r--r-- | xmlsecurity/qa/unit/pdfsigning/data/bad-cert-p1.pdf | bin | 0 -> 29646 bytes | |||
-rw-r--r-- | xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx | 14 | ||||
-rw-r--r-- | xmlsecurity/source/helper/pdfsignaturehelper.cxx | 22 |
7 files changed, 118 insertions, 20 deletions
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 Binary files differnew file mode 100644 index 000000000000..04d9950582b0 --- /dev/null +++ b/xmlsecurity/qa/unit/pdfsigning/data/bad-cert-p1.pdf 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); |