summaryrefslogtreecommitdiff
path: root/xmlsecurity
diff options
context:
space:
mode:
authorMiklos Vajna <vmiklos@collabora.com>2020-09-24 21:06:46 +0200
committerMiklos Vajna <vmiklos@collabora.com>2020-09-25 09:02:57 +0200
commit2dd3d03b5350e18cb5f39978022620b55d3d8c5b (patch)
tree5c82e20fef4188cad18ed88be6b6a1a98613750c /xmlsecurity
parent6505f43b09fda83f26159746083b971bfab8054a (diff)
xmlsecurity: fold pdfio into pdfsignaturehelper
Most of the initial pdfio was moved to vcl as vcl::filter::PDFDocument. A small part was left here, because it depended on NSS. Then later the NSS bits were moved to svl::crypto::Signing. The rest is just a small amount of code, keeping that separate from PDFSignatureHelper, which is its only user makes little sense. With this, vcl::filter::PDFDocument is an implementation detail of PDFSignatureHelper during signature verification. Change-Id: I6230f9e46deeff7159970f88dbb3bd2de0e9ce7d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/103350 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Diffstat (limited to 'xmlsecurity')
-rw-r--r--xmlsecurity/Library_xmlsecurity.mk1
-rw-r--r--xmlsecurity/inc/pdfio/pdfdocument.hxx40
-rw-r--r--xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx5
-rw-r--r--xmlsecurity/source/helper/pdfsignaturehelper.cxx313
-rw-r--r--xmlsecurity/source/pdfio/pdfdocument.cxx335
-rw-r--r--xmlsecurity/workben/pdfverify.cxx18
6 files changed, 321 insertions, 391 deletions
diff --git a/xmlsecurity/Library_xmlsecurity.mk b/xmlsecurity/Library_xmlsecurity.mk
index 89306bf5ac27..f50140edb303 100644
--- a/xmlsecurity/Library_xmlsecurity.mk
+++ b/xmlsecurity/Library_xmlsecurity.mk
@@ -75,7 +75,6 @@ $(eval $(call gb_Library_add_exception_objects,xmlsecurity,\
xmlsecurity/source/helper/xsecparser \
xmlsecurity/source/helper/xsecsign \
xmlsecurity/source/helper/xsecverify \
- xmlsecurity/source/pdfio/pdfdocument \
))
$(eval $(call gb_Library_use_externals,xmlsecurity,\
diff --git a/xmlsecurity/inc/pdfio/pdfdocument.hxx b/xmlsecurity/inc/pdfio/pdfdocument.hxx
deleted file mode 100644
index 4e36978ee009..000000000000
--- a/xmlsecurity/inc/pdfio/pdfdocument.hxx
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is part of the LibreOffice project.
- *
- * 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/.
- *
- */
-
-#ifndef INCLUDED_XMLSECURITY_INC_PDFIO_PDFDOCUMENT_HXX
-#define INCLUDED_XMLSECURITY_INC_PDFIO_PDFDOCUMENT_HXX
-
-#include <xmlsecuritydllapi.h>
-
-namespace vcl::filter
-{
-class PDFObjectElement;
-class PDFDocument;
-}
-struct SignatureInformation;
-class SvStream;
-
-namespace xmlsecurity::pdfio
-{
-/**
- * @param rInformation The actual result.
- * @param rDocument the parsed document to see if the signature is partial.
- * @return If we can determinate a result.
- */
-XMLSECURITY_DLLPUBLIC bool ValidateSignature(SvStream& rStream,
- vcl::filter::PDFObjectElement* pSignature,
- SignatureInformation& rInformation,
- vcl::filter::PDFDocument& rDocument);
-
-} // namespace xmlsecurity::pdfio
-
-#endif // INCLUDED_XMLSECURITY_INC_PDFIO_PDFDOCUMENT_HXX
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx b/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
index 8b4a610fa56c..586ef54d3075 100644
--- a/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
+++ b/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
@@ -127,7 +127,7 @@ bool PDFSigningTest::sign(const OUString& rInURL, const OUString& rOutURL,
// Only try certificates that are already active and not expired
if ((now > aNotValidAfter) || (now < aNotValidBefore))
{
- SAL_WARN("xmlsecurity.pdfio.test",
+ SAL_WARN("xmlsecurity.qa",
"Skipping a certificate that is not yet valid or already not valid");
}
else
@@ -139,8 +139,7 @@ bool PDFSigningTest::sign(const OUString& rInURL, const OUString& rOutURL,
DWORD dwErr = GetLastError();
if (HRESULT_FROM_WIN32(dwErr) == CRYPT_E_NO_KEY_PROPERTY)
{
- SAL_WARN("xmlsecurity.pdfio.test",
- "Skipping a certificate without a private key");
+ SAL_WARN("xmlsecurity.qa", "Skipping a certificate without a private key");
continue; // The certificate does not have a private key - not a valid certificate
}
}
diff --git a/xmlsecurity/source/helper/pdfsignaturehelper.cxx b/xmlsecurity/source/helper/pdfsignaturehelper.cxx
index b49cdd3e449f..f9bb5342c626 100644
--- a/xmlsecurity/source/helper/pdfsignaturehelper.cxx
+++ b/xmlsecurity/source/helper/pdfsignaturehelper.cxx
@@ -30,8 +30,11 @@
#include <unotools/streamwrap.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <vcl/filter/pdfdocument.hxx>
-
-#include <pdfio/pdfdocument.hxx>
+#include <vcl/checksum.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <svl/cryptosign.hxx>
+#include <config_features.h>
+#include <vcl/filter/PDFiumLibrary.hxx>
using namespace ::com::sun::star;
@@ -112,6 +115,310 @@ void GetSignatureLineShape(const uno::Reference<frame::XModel>& xModel, sal_Int3
rSignatureLineShape = std::vector<sal_Int8>(aStream.GetSize());
aStream.ReadBytes(rSignatureLineShape.data(), rSignatureLineShape.size());
}
+
+/// Turns an array of floats into offset + length pairs.
+bool GetByteRangesFromPDF(vcl::filter::PDFArrayElement& rArray,
+ std::vector<std::pair<size_t, size_t>>& rByteRanges)
+{
+ size_t nByteRangeOffset = 0;
+ const std::vector<vcl::filter::PDFElement*>& rByteRangeElements = rArray.GetElements();
+ for (size_t i = 0; i < rByteRangeElements.size(); ++i)
+ {
+ auto pNumber = dynamic_cast<vcl::filter::PDFNumberElement*>(rByteRangeElements[i]);
+ if (!pNumber)
+ {
+ SAL_WARN("xmlsecurity.helper",
+ "ValidateSignature: signature offset and length has to be a number");
+ return false;
+ }
+
+ if (i % 2 == 0)
+ {
+ nByteRangeOffset = pNumber->GetValue();
+ continue;
+ }
+ size_t nByteRangeLength = pNumber->GetValue();
+ rByteRanges.emplace_back(nByteRangeOffset, nByteRangeLength);
+ }
+
+ return true;
+}
+
+/// Determines the last position that is covered by a signature.
+bool GetEOFOfSignature(vcl::filter::PDFObjectElement* pSignature, size_t& rEOF)
+{
+ vcl::filter::PDFObjectElement* pValue = pSignature->LookupObject("V");
+ if (!pValue)
+ {
+ return false;
+ }
+
+ auto pByteRange = dynamic_cast<vcl::filter::PDFArrayElement*>(pValue->Lookup("ByteRange"));
+ if (!pByteRange || pByteRange->GetElements().size() < 2)
+ {
+ return false;
+ }
+
+ std::vector<std::pair<size_t, size_t>> aByteRanges;
+ if (!GetByteRangesFromPDF(*pByteRange, aByteRanges))
+ {
+ return false;
+ }
+
+ rEOF = aByteRanges[1].first + aByteRanges[1].second;
+ return true;
+}
+
+/// Checks if there are unsigned incremental updates between the signatures or after the last one.
+bool IsCompleteSignature(SvStream& rStream, vcl::filter::PDFDocument& rDocument,
+ vcl::filter::PDFObjectElement* pSignature)
+{
+ std::set<size_t> aSignedEOFs;
+ for (const auto& i : rDocument.GetSignatureWidgets())
+ {
+ size_t nEOF = 0;
+ if (!GetEOFOfSignature(i, nEOF))
+ {
+ return false;
+ }
+
+ aSignedEOFs.insert(nEOF);
+ }
+
+ size_t nSignatureEOF = 0;
+ if (!GetEOFOfSignature(pSignature, nSignatureEOF))
+ {
+ return false;
+ }
+
+ const std::vector<size_t>& rAllEOFs = rDocument.GetEOFs();
+ bool bFoundOwn = false;
+ for (const auto& rEOF : rAllEOFs)
+ {
+ if (rEOF == nSignatureEOF)
+ {
+ bFoundOwn = true;
+ continue;
+ }
+
+ if (!bFoundOwn)
+ {
+ continue;
+ }
+
+ if (aSignedEOFs.find(rEOF) == aSignedEOFs.end())
+ {
+ // Unsigned incremental update found.
+ return false;
+ }
+ }
+
+ // Make sure we find the incremental update of the signature itself.
+ if (!bFoundOwn)
+ {
+ return false;
+ }
+
+ // No additional content after the last incremental update.
+ rStream.Seek(STREAM_SEEK_TO_END);
+ size_t nFileEnd = rStream.Tell();
+ return std::find(rAllEOFs.begin(), rAllEOFs.end(), nFileEnd) != rAllEOFs.end();
+}
+
+#if HAVE_FEATURE_PDFIUM
+/// Collects the checksum of each page of one version of the PDF.
+void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<BitmapChecksum>& rPageChecksums)
+{
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ vcl::pdf::PDFiumDocument aPdfDocument(
+ FPDF_LoadMemDocument(rStream.GetData(), rStream.GetSize(), /*password=*/nullptr));
+
+ int nPageCount = aPdfDocument.getPageCount();
+ for (int nPage = 0; nPage < nPageCount; ++nPage)
+ {
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage(aPdfDocument.openPage(nPage));
+ if (!pPdfPage)
+ {
+ return;
+ }
+
+ BitmapChecksum nPageChecksum = pPdfPage->getChecksum();
+ rPageChecksums.push_back(nPageChecksum);
+ }
+}
+#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;
+ }
+
+#if HAVE_FEATURE_PDFIUM
+ 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;
+#else
+ (void)rStream;
+ return true;
+#endif
+}
+
+/**
+ * @param rInformation The actual result.
+ * @param rDocument the parsed document to see if the signature is partial.
+ * @return If we can determinate a result.
+ */
+bool ValidateSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignature,
+ SignatureInformation& rInformation, vcl::filter::PDFDocument& rDocument)
+{
+ vcl::filter::PDFObjectElement* pValue = pSignature->LookupObject("V");
+ if (!pValue)
+ {
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: no value");
+ return false;
+ }
+
+ auto pContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(pValue->Lookup("Contents"));
+ if (!pContents)
+ {
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: no contents");
+ return false;
+ }
+
+ auto pByteRange = dynamic_cast<vcl::filter::PDFArrayElement*>(pValue->Lookup("ByteRange"));
+ if (!pByteRange || pByteRange->GetElements().size() < 2)
+ {
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: no byte range or too few elements");
+ return false;
+ }
+
+ auto pSubFilter = dynamic_cast<vcl::filter::PDFNameElement*>(pValue->Lookup("SubFilter"));
+ const bool bNonDetached = pSubFilter && pSubFilter->GetValue() == "adbe.pkcs7.sha1";
+ if (!pSubFilter
+ || (pSubFilter->GetValue() != "adbe.pkcs7.detached" && !bNonDetached
+ && pSubFilter->GetValue() != "ETSI.CAdES.detached"))
+ {
+ if (!pSubFilter)
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: missing sub-filter");
+ else
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: unsupported sub-filter: '"
+ << pSubFilter->GetValue() << "'");
+ return false;
+ }
+
+ // Reason / comment / description is optional.
+ auto pReason = dynamic_cast<vcl::filter::PDFHexStringElement*>(pValue->Lookup("Reason"));
+ if (pReason)
+ {
+ // See appendUnicodeTextString() for the export equivalent of this.
+ std::vector<unsigned char> aReason = vcl::filter::PDFDocument::DecodeHexString(pReason);
+ OUStringBuffer aBuffer;
+ sal_uInt16 nByte = 0;
+ for (size_t i = 0; i < aReason.size(); ++i)
+ {
+ if (i % 2 == 0)
+ nByte = aReason[i];
+ else
+ {
+ sal_Unicode nUnicode;
+ nUnicode = (nByte << 8);
+ nUnicode |= aReason[i];
+ aBuffer.append(nUnicode);
+ }
+ }
+
+ if (!aBuffer.isEmpty())
+ rInformation.ouDescription = aBuffer.makeStringAndClear();
+ }
+
+ // Date: used only when the time of signing is not available in the
+ // signature.
+ auto pM = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pValue->Lookup("M"));
+ if (pM)
+ {
+ // Example: "D:20161027100104".
+ const OString& rM = pM->GetValue();
+ if (rM.startsWith("D:") && rM.getLength() >= 16)
+ {
+ rInformation.stDateTime.Year = rM.copy(2, 4).toInt32();
+ rInformation.stDateTime.Month = rM.copy(6, 2).toInt32();
+ rInformation.stDateTime.Day = rM.copy(8, 2).toInt32();
+ rInformation.stDateTime.Hours = rM.copy(10, 2).toInt32();
+ rInformation.stDateTime.Minutes = rM.copy(12, 2).toInt32();
+ rInformation.stDateTime.Seconds = rM.copy(14, 2).toInt32();
+ }
+ }
+
+ // Build a list of offset-length pairs, representing the signed bytes.
+ std::vector<std::pair<size_t, size_t>> aByteRanges;
+ if (!GetByteRangesFromPDF(*pByteRange, aByteRanges))
+ {
+ return false;
+ }
+
+ // Detect if the byte ranges don't cover everything, but the signature itself.
+ if (aByteRanges.size() < 2)
+ {
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: expected 2 byte ranges");
+ return false;
+ }
+ if (aByteRanges[0].first != 0)
+ {
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: first range start is not 0");
+ return false;
+ }
+ // 2 is the leading "<" and the trailing ">" around the hex string.
+ size_t nSignatureLength = static_cast<size_t>(pContents->GetValue().getLength()) + 2;
+ if (aByteRanges[1].first != (aByteRanges[0].second + nSignatureLength))
+ {
+ SAL_WARN("xmlsecurity.helper",
+ "ValidateSignature: second range start is not the end of the signature");
+ return false;
+ }
+ rInformation.bPartialDocumentSignature = !IsCompleteSignature(rStream, rDocument, pSignature);
+ if (!IsValidSignature(rStream, pSignature))
+ {
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: invalid incremental update detected");
+ return false;
+ }
+
+ // At this point there is no obviously missing info to validate the
+ // signature.
+ std::vector<unsigned char> aSignature = vcl::filter::PDFDocument::DecodeHexString(pContents);
+ if (aSignature.empty())
+ {
+ SAL_WARN("xmlsecurity.helper", "ValidateSignature: empty contents");
+ return false;
+ }
+
+ return svl::crypto::Signing::Verify(rStream, aByteRanges, bNonDetached, aSignature,
+ rInformation);
+}
}
PDFSignatureHelper::PDFSignatureHelper() = default;
@@ -148,7 +455,7 @@ bool PDFSignatureHelper::ReadAndVerifySignatureSvStream(SvStream& rStream)
{
SignatureInformation aInfo(i);
- if (!xmlsecurity::pdfio::ValidateSignature(rStream, aSignatures[i], aInfo, aDocument))
+ if (!ValidateSignature(rStream, aSignatures[i], aInfo, aDocument))
SAL_WARN("xmlsecurity.helper", "failed to determine digest match");
m_aSignatureInfos.push_back(aInfo);
diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx
deleted file mode 100644
index b2c1cc0db77a..000000000000
--- a/xmlsecurity/source/pdfio/pdfdocument.cxx
+++ /dev/null
@@ -1,335 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is part of the LibreOffice project.
- *
- * 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/.
- */
-
-#include <pdfio/pdfdocument.hxx>
-
-#include <memory>
-#include <vector>
-
-#include <config_features.h>
-
-#include <vcl/filter/PDFiumLibrary.hxx>
-#include <rtl/string.hxx>
-#include <rtl/ustrbuf.hxx>
-#include <sal/log.hxx>
-#include <sal/types.h>
-
-#include <svl/sigstruct.hxx>
-#include <svl/cryptosign.hxx>
-#include <vcl/filter/pdfdocument.hxx>
-
-using namespace com::sun::star;
-
-namespace
-{
-/// Turns an array of floats into offset + length pairs.
-bool GetByteRangesFromPDF(vcl::filter::PDFArrayElement& rArray,
- std::vector<std::pair<size_t, size_t>>& rByteRanges)
-{
- size_t nByteRangeOffset = 0;
- const std::vector<vcl::filter::PDFElement*>& rByteRangeElements = rArray.GetElements();
- for (size_t i = 0; i < rByteRangeElements.size(); ++i)
- {
- auto pNumber = dynamic_cast<vcl::filter::PDFNumberElement*>(rByteRangeElements[i]);
- if (!pNumber)
- {
- SAL_WARN("xmlsecurity.pdfio",
- "ValidateSignature: signature offset and length has to be a number");
- return false;
- }
-
- if (i % 2 == 0)
- {
- nByteRangeOffset = pNumber->GetValue();
- continue;
- }
- size_t nByteRangeLength = pNumber->GetValue();
- rByteRanges.emplace_back(nByteRangeOffset, nByteRangeLength);
- }
-
- return true;
-}
-
-/// Determines the last position that is covered by a signature.
-bool GetEOFOfSignature(vcl::filter::PDFObjectElement* pSignature, size_t& rEOF)
-{
- vcl::filter::PDFObjectElement* pValue = pSignature->LookupObject("V");
- if (!pValue)
- {
- return false;
- }
-
- auto pByteRange = dynamic_cast<vcl::filter::PDFArrayElement*>(pValue->Lookup("ByteRange"));
- if (!pByteRange || pByteRange->GetElements().size() < 2)
- {
- return false;
- }
-
- std::vector<std::pair<size_t, size_t>> aByteRanges;
- if (!GetByteRangesFromPDF(*pByteRange, aByteRanges))
- {
- return false;
- }
-
- rEOF = aByteRanges[1].first + aByteRanges[1].second;
- return true;
-}
-
-/// Checks if there are unsigned incremental updates between the signatures or after the last one.
-bool IsCompleteSignature(SvStream& rStream, vcl::filter::PDFDocument& rDocument,
- vcl::filter::PDFObjectElement* pSignature)
-{
- std::set<size_t> aSignedEOFs;
- for (const auto& i : rDocument.GetSignatureWidgets())
- {
- size_t nEOF = 0;
- if (!GetEOFOfSignature(i, nEOF))
- {
- return false;
- }
-
- aSignedEOFs.insert(nEOF);
- }
-
- size_t nSignatureEOF = 0;
- if (!GetEOFOfSignature(pSignature, nSignatureEOF))
- {
- return false;
- }
-
- const std::vector<size_t>& rAllEOFs = rDocument.GetEOFs();
- bool bFoundOwn = false;
- for (const auto& rEOF : rAllEOFs)
- {
- if (rEOF == nSignatureEOF)
- {
- bFoundOwn = true;
- continue;
- }
-
- if (!bFoundOwn)
- {
- continue;
- }
-
- if (aSignedEOFs.find(rEOF) == aSignedEOFs.end())
- {
- // Unsigned incremental update found.
- return false;
- }
- }
-
- // Make sure we find the incremental update of the signature itself.
- if (!bFoundOwn)
- {
- return false;
- }
-
- // No additional content after the last incremental update.
- rStream.Seek(STREAM_SEEK_TO_END);
- size_t nFileEnd = rStream.Tell();
- return std::find(rAllEOFs.begin(), rAllEOFs.end(), nFileEnd) != rAllEOFs.end();
-}
-
-#if HAVE_FEATURE_PDFIUM
-/// Collects the checksum of each page of one version of the PDF.
-void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<BitmapChecksum>& rPageChecksums)
-{
- auto pPdfium = vcl::pdf::PDFiumLibrary::get();
- vcl::pdf::PDFiumDocument aPdfDocument(
- FPDF_LoadMemDocument(rStream.GetData(), rStream.GetSize(), /*password=*/nullptr));
-
- int nPageCount = aPdfDocument.getPageCount();
- for (int nPage = 0; nPage < nPageCount; ++nPage)
- {
- std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage(aPdfDocument.openPage(nPage));
- if (!pPdfPage)
- {
- return;
- }
-
- BitmapChecksum nPageChecksum = pPdfPage->getChecksum();
- rPageChecksums.push_back(nPageChecksum);
- }
-}
-#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;
- }
-
-#if HAVE_FEATURE_PDFIUM
- 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;
-#else
- (void)rStream;
- return true;
-#endif
-}
-}
-
-namespace xmlsecurity::pdfio
-{
-bool ValidateSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignature,
- SignatureInformation& rInformation, vcl::filter::PDFDocument& rDocument)
-{
- vcl::filter::PDFObjectElement* pValue = pSignature->LookupObject("V");
- if (!pValue)
- {
- SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: no value");
- return false;
- }
-
- auto pContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(pValue->Lookup("Contents"));
- if (!pContents)
- {
- SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: no contents");
- return false;
- }
-
- auto pByteRange = dynamic_cast<vcl::filter::PDFArrayElement*>(pValue->Lookup("ByteRange"));
- if (!pByteRange || pByteRange->GetElements().size() < 2)
- {
- SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: no byte range or too few elements");
- return false;
- }
-
- auto pSubFilter = dynamic_cast<vcl::filter::PDFNameElement*>(pValue->Lookup("SubFilter"));
- const bool bNonDetached = pSubFilter && pSubFilter->GetValue() == "adbe.pkcs7.sha1";
- if (!pSubFilter
- || (pSubFilter->GetValue() != "adbe.pkcs7.detached" && !bNonDetached
- && pSubFilter->GetValue() != "ETSI.CAdES.detached"))
- {
- if (!pSubFilter)
- SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: missing sub-filter");
- else
- SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: unsupported sub-filter: '"
- << pSubFilter->GetValue() << "'");
- return false;
- }
-
- // Reason / comment / description is optional.
- auto pReason = dynamic_cast<vcl::filter::PDFHexStringElement*>(pValue->Lookup("Reason"));
- if (pReason)
- {
- // See appendUnicodeTextString() for the export equivalent of this.
- std::vector<unsigned char> aReason = vcl::filter::PDFDocument::DecodeHexString(pReason);
- OUStringBuffer aBuffer;
- sal_uInt16 nByte = 0;
- for (size_t i = 0; i < aReason.size(); ++i)
- {
- if (i % 2 == 0)
- nByte = aReason[i];
- else
- {
- sal_Unicode nUnicode;
- nUnicode = (nByte << 8);
- nUnicode |= aReason[i];
- aBuffer.append(nUnicode);
- }
- }
-
- if (!aBuffer.isEmpty())
- rInformation.ouDescription = aBuffer.makeStringAndClear();
- }
-
- // Date: used only when the time of signing is not available in the
- // signature.
- auto pM = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pValue->Lookup("M"));
- if (pM)
- {
- // Example: "D:20161027100104".
- const OString& rM = pM->GetValue();
- if (rM.startsWith("D:") && rM.getLength() >= 16)
- {
- rInformation.stDateTime.Year = rM.copy(2, 4).toInt32();
- rInformation.stDateTime.Month = rM.copy(6, 2).toInt32();
- rInformation.stDateTime.Day = rM.copy(8, 2).toInt32();
- rInformation.stDateTime.Hours = rM.copy(10, 2).toInt32();
- rInformation.stDateTime.Minutes = rM.copy(12, 2).toInt32();
- rInformation.stDateTime.Seconds = rM.copy(14, 2).toInt32();
- }
- }
-
- // Build a list of offset-length pairs, representing the signed bytes.
- std::vector<std::pair<size_t, size_t>> aByteRanges;
- if (!GetByteRangesFromPDF(*pByteRange, aByteRanges))
- {
- return false;
- }
-
- // Detect if the byte ranges don't cover everything, but the signature itself.
- if (aByteRanges.size() < 2)
- {
- SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: expected 2 byte ranges");
- return false;
- }
- if (aByteRanges[0].first != 0)
- {
- SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: first range start is not 0");
- return false;
- }
- // 2 is the leading "<" and the trailing ">" around the hex string.
- size_t nSignatureLength = static_cast<size_t>(pContents->GetValue().getLength()) + 2;
- if (aByteRanges[1].first != (aByteRanges[0].second + nSignatureLength))
- {
- SAL_WARN("xmlsecurity.pdfio",
- "ValidateSignature: second range start is not the end of the signature");
- 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.
- std::vector<unsigned char> aSignature = vcl::filter::PDFDocument::DecodeHexString(pContents);
- if (aSignature.empty())
- {
- SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: empty contents");
- return false;
- }
-
- return svl::crypto::Signing::Verify(rStream, aByteRanges, bNonDetached, aSignature,
- rInformation);
-}
-
-} // namespace xmlsecurity::pdfio
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmlsecurity/workben/pdfverify.cxx b/xmlsecurity/workben/pdfverify.cxx
index 78595bae0ef9..d98a2fb1750e 100644
--- a/xmlsecurity/workben/pdfverify.cxx
+++ b/xmlsecurity/workben/pdfverify.cxx
@@ -58,7 +58,7 @@ int pdfVerify(int nArgc, char** pArgv)
{
if (nArgc < 2)
{
- SAL_WARN("xmlsecurity.pdfio", "not enough parameters");
+ SAL_WARN("xmlsecurity.workben", "not enough parameters");
return 1;
}
@@ -70,7 +70,7 @@ int pdfVerify(int nArgc, char** pArgv)
}
catch (const uno::RuntimeException&)
{
- TOOLS_WARN_EXCEPTION("xmlsecurity.pdfio",
+ TOOLS_WARN_EXCEPTION("xmlsecurity.workben",
"cppu::defaultBootstrap_InitialComponentContext() failed:");
return 1;
}
@@ -95,7 +95,7 @@ int pdfVerify(int nArgc, char** pArgv)
}
catch (const uno::DeploymentException&)
{
- TOOLS_WARN_EXCEPTION("xmlsecurity.pdfio",
+ TOOLS_WARN_EXCEPTION("xmlsecurity.workben",
"DeploymentException while creating SEInitializer:");
return 1;
}
@@ -142,7 +142,7 @@ int pdfVerify(int nArgc, char** pArgv)
vcl::filter::PDFDocument aDocument;
if (!aDocument.Read(aStream))
{
- SAL_WARN("xmlsecurity.pdfio", "failed to read the document");
+ SAL_WARN("xmlsecurity.workben", "failed to read the document");
return 1;
}
@@ -159,14 +159,14 @@ int pdfVerify(int nArgc, char** pArgv)
size_t nPosition = aSignatures.size() - 1;
if (!aDocument.RemoveSignature(nPosition))
{
- SAL_WARN("xmlsecurity.pdfio", "failed to remove signature #" << nPosition);
+ SAL_WARN("xmlsecurity.workben", "failed to remove signature #" << nPosition);
return 1;
}
SvFileStream aOutStream(aOutURL, StreamMode::WRITE | StreamMode::TRUNC);
if (!aDocument.Write(aOutStream))
{
- SAL_WARN("xmlsecurity.pdfio", "failed to write the document");
+ SAL_WARN("xmlsecurity.workben", "failed to write the document");
return 1;
}
@@ -180,19 +180,19 @@ int pdfVerify(int nArgc, char** pArgv)
= xSecurityEnvironment->getPersonalCertificates();
if (!aCertificates.hasElements())
{
- SAL_WARN("xmlsecurity.pdfio", "no signing certificates found");
+ SAL_WARN("xmlsecurity.workben", "no signing certificates found");
return 1;
}
if (!aDocument.Sign(aCertificates[0], "pdfverify", /*bAdES=*/true))
{
- SAL_WARN("xmlsecurity.pdfio", "failed to sign");
+ SAL_WARN("xmlsecurity.workben", "failed to sign");
return 1;
}
SvFileStream aOutStream(aOutURL, StreamMode::WRITE | StreamMode::TRUNC);
if (!aDocument.Write(aOutStream))
{
- SAL_WARN("xmlsecurity.pdfio", "failed to write the document");
+ SAL_WARN("xmlsecurity.workben", "failed to write the document");
return 1;
}