tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / xmlsecurity / source / helper / pdfsignaturehelper.cxx
blob79ee45c0e3c3fb657b7d328993b73b07eda1a2f0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <pdfsignaturehelper.hxx>
12 #include <memory>
14 #include <com/sun/star/io/XTruncate.hpp>
15 #include <com/sun/star/io/XStream.hpp>
16 #include <com/sun/star/security/CertificateValidity.hpp>
17 #include <com/sun/star/uno/SecurityException.hpp>
18 #include <com/sun/star/security/DocumentSignatureInformation.hpp>
19 #include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp>
20 #include <com/sun/star/drawing/XShapes.hpp>
21 #include <com/sun/star/frame/XModel.hpp>
22 #include <com/sun/star/frame/XStorable.hpp>
23 #include <com/sun/star/beans/XPropertySet.hpp>
24 #include <com/sun/star/drawing/XDrawView.hpp>
26 #include <comphelper/propertysequence.hxx>
27 #include <sal/log.hxx>
28 #include <comphelper/diagnose_ex.hxx>
29 #include <unotools/mediadescriptor.hxx>
30 #include <unotools/streamwrap.hxx>
31 #include <unotools/ucbstreamhelper.hxx>
32 #include <vcl/filter/pdfdocument.hxx>
33 #include <vcl/checksum.hxx>
34 #include <svl/cryptosign.hxx>
35 #include <vcl/filter/PDFiumLibrary.hxx>
37 using namespace ::com::sun::star;
39 namespace
41 /// Gets the current page of the current view from xModel and puts it to the 1-based rPage.
42 bool GetSignatureLinePage(const uno::Reference<frame::XModel>& xModel, sal_Int32& rPage)
44 uno::Reference<drawing::XDrawView> xController(xModel->getCurrentController(), uno::UNO_QUERY);
45 if (!xController.is())
47 return false;
50 uno::Reference<beans::XPropertySet> xPage(xController->getCurrentPage(), uno::UNO_QUERY);
51 if (!xPage.is())
53 return false;
56 return xPage->getPropertyValue(u"Number"_ustr) >>= rPage;
59 /// If the currently selected shape is a Draw signature line, export that to PDF.
60 void GetSignatureLineShape(const uno::Reference<frame::XModel>& xModel, sal_Int32& rPage,
61 std::vector<sal_Int8>& rSignatureLineShape)
63 if (!xModel.is())
65 return;
68 if (!GetSignatureLinePage(xModel, rPage))
70 return;
73 uno::Reference<drawing::XShapes> xShapes(xModel->getCurrentSelection(), uno::UNO_QUERY);
74 if (!xShapes.is() || xShapes->getCount() < 1)
76 return;
79 uno::Reference<beans::XPropertySet> xShapeProps(xShapes->getByIndex(0), uno::UNO_QUERY);
80 if (!xShapeProps.is())
82 return;
85 comphelper::SequenceAsHashMap aMap(xShapeProps->getPropertyValue(u"InteropGrabBag"_ustr));
86 auto it = aMap.find(u"SignatureCertificate"_ustr);
87 if (it == aMap.end())
89 return;
92 // We know that we add a signature line shape to an existing PDF at this point.
94 uno::Reference<frame::XStorable> xStorable(xModel, uno::UNO_QUERY);
95 if (!xStorable.is())
97 return;
100 // Export just the signature line.
101 utl::MediaDescriptor aMediaDescriptor;
102 aMediaDescriptor[u"FilterName"_ustr] <<= u"draw_pdf_Export"_ustr;
103 SvMemoryStream aStream;
104 uno::Reference<io::XOutputStream> xStream(new utl::OStreamWrapper(aStream));
105 aMediaDescriptor[u"OutputStream"_ustr] <<= xStream;
106 uno::Sequence<beans::PropertyValue> aFilterData(
107 comphelper::InitPropertySequence({ { "Selection", uno::Any(xShapes) } }));
108 aMediaDescriptor[u"FilterData"_ustr] <<= aFilterData;
109 xStorable->storeToURL(u"private:stream"_ustr, aMediaDescriptor.getAsConstPropertyValueList());
110 xStream->flush();
112 aStream.Seek(0);
113 rSignatureLineShape = std::vector<sal_Int8>(aStream.GetSize());
114 aStream.ReadBytes(rSignatureLineShape.data(), rSignatureLineShape.size());
117 /// Represents a parsed signature.
118 struct Signature
120 std::unique_ptr<vcl::pdf::PDFiumSignature> m_pSignature;
121 /// Offset+length pairs.
122 std::vector<std::pair<size_t, size_t>> m_aByteRanges;
125 /// Turns an array of floats into offset + length pairs.
126 void GetByteRangesFromPDF(const std::unique_ptr<vcl::pdf::PDFiumSignature>& pSignature,
127 std::vector<std::pair<size_t, size_t>>& rByteRanges)
129 std::vector<int> aByteRange = pSignature->getByteRange();
130 if (aByteRange.empty())
132 SAL_WARN("xmlsecurity.helper", "GetByteRangesFromPDF: no byte ranges");
133 return;
136 size_t nByteRangeOffset = 0;
137 for (size_t i = 0; i < aByteRange.size(); ++i)
139 if (i % 2 == 0)
141 nByteRangeOffset = aByteRange[i];
142 continue;
145 size_t nLength = aByteRange[i];
146 rByteRanges.emplace_back(nByteRangeOffset, nLength);
150 /// Determines the last position that is covered by a signature.
151 bool GetEOFOfSignature(const Signature& rSignature, size_t& rEOF)
153 if (rSignature.m_aByteRanges.size() < 2)
155 return false;
158 rEOF = rSignature.m_aByteRanges[1].first + rSignature.m_aByteRanges[1].second;
159 return true;
163 * Get the value of the "modification detection and prevention" permission:
164 * Valid values are 1, 2 and 3: only 3 allows annotations after signing.
166 int GetMDPPerm(const std::vector<Signature>& rSignatures)
168 int nRet = 3;
170 if (rSignatures.empty())
172 return nRet;
175 for (const auto& rSignature : rSignatures)
177 int nPerm = rSignature.m_pSignature->getDocMDPPermission();
178 if (nPerm != 0)
180 return nPerm;
184 return nRet;
187 /// Checks if there are unsigned incremental updates between the signatures or after the last one.
188 bool IsCompleteSignature(SvStream& rStream, const Signature& rSignature,
189 const std::set<unsigned int>& rSignedEOFs,
190 const std::vector<unsigned int>& rAllEOFs)
192 size_t nSignatureEOF = 0;
193 if (!GetEOFOfSignature(rSignature, nSignatureEOF))
195 return false;
198 bool bFoundOwn = false;
199 for (const auto& rEOF : rAllEOFs)
201 if (rEOF == nSignatureEOF)
203 bFoundOwn = true;
204 continue;
207 if (!bFoundOwn)
209 continue;
212 if (rSignedEOFs.find(rEOF) == rSignedEOFs.end())
214 // Unsigned incremental update found.
215 return false;
219 // Make sure we find the incremental update of the signature itself.
220 if (!bFoundOwn)
222 return false;
225 // No additional content after the last incremental update.
226 rStream.Seek(STREAM_SEEK_TO_END);
227 size_t nFileEnd = rStream.Tell();
228 return std::find(rAllEOFs.begin(), rAllEOFs.end(), nFileEnd) != rAllEOFs.end();
232 * Contains checksums of a PDF page, which is rendered without annotations. It also contains
233 * the geometry of a few dangerous annotation types.
235 struct PageChecksum
237 BitmapChecksum m_nPageContent;
238 std::vector<basegfx::B2DRectangle> m_aAnnotations;
239 bool operator==(const PageChecksum& rChecksum) const;
242 bool PageChecksum::operator==(const PageChecksum& rChecksum) const
244 if (m_nPageContent != rChecksum.m_nPageContent)
246 return false;
249 return m_aAnnotations == rChecksum.m_aAnnotations;
252 /// Collects the checksum of each page of one version of the PDF.
253 void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<PageChecksum>& rPageChecksums,
254 int nMDPPerm)
256 auto pPdfium = vcl::pdf::PDFiumLibrary::get();
257 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
258 = pPdfium->openDocument(rStream.GetData(), rStream.GetSize(), OString());
259 if (!pPdfDocument)
261 return;
264 int nPageCount = pPdfDocument->getPageCount();
265 for (int nPage = 0; nPage < nPageCount; ++nPage)
267 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(nPage);
268 if (!pPdfPage)
270 return;
273 PageChecksum aPageChecksum;
274 aPageChecksum.m_nPageContent = pPdfPage->getChecksum(nMDPPerm);
275 for (int i = 0; i < pPdfPage->getAnnotationCount(); ++i)
277 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pPdfAnnotation = pPdfPage->getAnnotation(i);
278 if (!pPdfAnnotation)
280 SAL_WARN("xmlsecurity.helper", "Cannot get PDFiumAnnotation");
281 continue;
283 vcl::pdf::PDFAnnotationSubType eType = pPdfAnnotation->getSubType();
284 switch (eType)
286 case vcl::pdf::PDFAnnotationSubType::Unknown:
287 case vcl::pdf::PDFAnnotationSubType::FreeText:
288 case vcl::pdf::PDFAnnotationSubType::Stamp:
289 case vcl::pdf::PDFAnnotationSubType::Redact:
290 aPageChecksum.m_aAnnotations.push_back(pPdfAnnotation->getRectangle());
291 break;
292 default:
293 break;
296 rPageChecksums.push_back(aPageChecksum);
301 * Checks if incremental updates after singing performed valid modifications only.
302 * nMDPPerm decides if annotations/commenting is OK, other changes are always not.
304 bool IsValidSignature(SvStream& rStream, const Signature& rSignature, int nMDPPerm)
306 size_t nSignatureEOF = 0;
307 if (!GetEOFOfSignature(rSignature, nSignatureEOF))
309 return false;
312 SvMemoryStream aSignatureStream;
313 sal_uInt64 nPos = rStream.Tell();
314 rStream.Seek(0);
315 aSignatureStream.WriteStream(rStream, nSignatureEOF);
316 rStream.Seek(nPos);
317 aSignatureStream.Seek(0);
318 std::vector<PageChecksum> aSignedPages;
319 AnalyizeSignatureStream(aSignatureStream, aSignedPages, nMDPPerm);
321 SvMemoryStream aFullStream;
322 nPos = rStream.Tell();
323 rStream.Seek(0);
324 aFullStream.WriteStream(rStream);
325 rStream.Seek(nPos);
326 aFullStream.Seek(0);
327 std::vector<PageChecksum> aAllPages;
328 AnalyizeSignatureStream(aFullStream, aAllPages, nMDPPerm);
330 // Fail if any page looks different after signing and at the end. Annotations/commenting doesn't
331 // count, though.
332 return aSignedPages == aAllPages;
336 * @param rInformation The actual result.
337 * @param rDocument the parsed document to see if the signature is partial.
338 * @return If we can determinate a result.
340 bool ValidateSignature(SvStream& rStream, const Signature& rSignature,
341 SignatureInformation& rInformation, int nMDPPerm,
342 const std::set<unsigned int>& rSignatureEOFs,
343 const std::vector<unsigned int>& rTrailerEnds)
345 std::vector<unsigned char> aContents = rSignature.m_pSignature->getContents();
346 if (aContents.empty())
348 SAL_WARN("xmlsecurity.helper", "ValidateSignature: no contents");
349 return false;
352 OString aSubFilter = rSignature.m_pSignature->getSubFilter();
354 const bool bNonDetached = aSubFilter == "adbe.pkcs7.sha1";
355 if (aSubFilter.isEmpty()
356 || (aSubFilter != "adbe.pkcs7.detached" && !bNonDetached
357 && aSubFilter != "ETSI.CAdES.detached"))
359 if (aSubFilter.isEmpty())
360 SAL_WARN("xmlsecurity.helper", "ValidateSignature: missing sub-filter");
361 else
362 SAL_WARN("xmlsecurity.helper",
363 "ValidateSignature: unsupported sub-filter: '" << aSubFilter << "'");
364 return false;
367 // Reason / comment / description is optional.
368 rInformation.ouDescription = rSignature.m_pSignature->getReason();
370 // Date: used only when the time of signing is not available in the
371 // signature.
372 rInformation.stDateTime = rSignature.m_pSignature->getTime();
374 // Detect if the byte ranges don't cover everything, but the signature itself.
375 if (rSignature.m_aByteRanges.size() < 2)
377 SAL_WARN("xmlsecurity.helper", "ValidateSignature: expected 2 byte ranges");
378 return false;
380 if (rSignature.m_aByteRanges[0].first != 0)
382 SAL_WARN("xmlsecurity.helper", "ValidateSignature: first range start is not 0");
383 return false;
385 // Binary vs hex dump and 2 is the leading "<" and the trailing ">" around the hex string.
386 size_t nSignatureLength = aContents.size() * 2 + 2;
387 if (rSignature.m_aByteRanges[1].first
388 != (rSignature.m_aByteRanges[0].second + nSignatureLength))
390 SAL_WARN("xmlsecurity.helper",
391 "ValidateSignature: second range start is not the end of the signature");
392 return false;
394 rInformation.bPartialDocumentSignature
395 = !IsCompleteSignature(rStream, rSignature, rSignatureEOFs, rTrailerEnds);
396 if (!IsValidSignature(rStream, rSignature, nMDPPerm))
398 SAL_WARN("xmlsecurity.helper", "ValidateSignature: invalid incremental update detected");
399 return false;
402 // At this point there is no obviously missing info to validate the
403 // signature.
404 return svl::crypto::Signing::Verify(rStream, rSignature.m_aByteRanges, bNonDetached, aContents,
405 rInformation);
409 PDFSignatureHelper::PDFSignatureHelper() = default;
411 bool PDFSignatureHelper::ReadAndVerifySignature(
412 const uno::Reference<io::XInputStream>& xInputStream)
414 if (!xInputStream.is())
416 SAL_WARN("xmlsecurity.helper", "input stream missing");
417 return false;
420 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
421 return ReadAndVerifySignatureSvStream(*pStream);
424 bool PDFSignatureHelper::ReadAndVerifySignatureSvStream(SvStream& rStream)
426 auto pPdfium = vcl::pdf::PDFiumLibrary::get();
427 if (!pPdfium)
429 return true;
432 SvMemoryStream aStream;
433 sal_uInt64 nPos = rStream.Tell();
434 rStream.Seek(0);
435 aStream.WriteStream(rStream);
436 rStream.Seek(nPos);
437 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
438 = pPdfium->openDocument(aStream.GetData(), aStream.GetSize(), OString());
439 if (!pPdfDocument)
441 SAL_WARN("xmlsecurity.helper", "failed to read the document");
442 return false;
445 int nSignatureCount = pPdfDocument->getSignatureCount();
446 if (nSignatureCount <= 0)
448 return true;
450 std::vector<Signature> aSignatures(nSignatureCount);
451 for (int i = 0; i < nSignatureCount; ++i)
453 std::unique_ptr<vcl::pdf::PDFiumSignature> pSignature = pPdfDocument->getSignature(i);
454 std::vector<std::pair<size_t, size_t>> aByteRanges;
455 GetByteRangesFromPDF(pSignature, aByteRanges);
456 aSignatures[i] = Signature{ std::move(pSignature), std::move(aByteRanges) };
459 std::set<unsigned int> aSignatureEOFs;
460 for (const auto& rSignature : aSignatures)
462 size_t nEOF = 0;
463 if (GetEOFOfSignature(rSignature, nEOF))
465 aSignatureEOFs.insert(nEOF);
469 std::vector<unsigned int> aTrailerEnds = pPdfDocument->getTrailerEnds();
471 m_aSignatureInfos.clear();
473 int nMDPPerm = GetMDPPerm(aSignatures);
475 for (size_t i = 0; i < aSignatures.size(); ++i)
477 SignatureInformation aInfo(i);
479 if (!ValidateSignature(rStream, aSignatures[i], aInfo, nMDPPerm, aSignatureEOFs,
480 aTrailerEnds))
482 SAL_WARN("xmlsecurity.helper", "failed to determine digest match");
485 m_aSignatureInfos.push_back(aInfo);
488 return true;
491 SignatureInformations const& PDFSignatureHelper::GetSignatureInformations() const
493 return m_aSignatureInfos;
496 uno::Sequence<security::DocumentSignatureInformation>
497 PDFSignatureHelper::GetDocumentSignatureInformations(
498 const uno::Reference<xml::crypto::XSecurityEnvironment>& xSecEnv) const
500 uno::Sequence<security::DocumentSignatureInformation> aRet(m_aSignatureInfos.size());
501 auto aRetRange = asNonConstRange(aRet);
503 for (size_t i = 0; i < m_aSignatureInfos.size(); ++i)
505 const SignatureInformation& rInternal = m_aSignatureInfos[i];
506 security::DocumentSignatureInformation& rExternal = aRetRange[i];
507 rExternal.SignatureIsValid
508 = rInternal.nStatus == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
509 if (rInternal.GetSigningCertificate()
510 && !rInternal.GetSigningCertificate()->X509Certificate.isEmpty())
512 rExternal.Signer = xSecEnv->createCertificateFromAscii(
513 rInternal.GetSigningCertificate()->X509Certificate);
515 rExternal.PartialDocumentSignature = rInternal.bPartialDocumentSignature;
517 // Verify certificate.
518 if (rExternal.Signer.is())
522 rExternal.CertificateStatus = xSecEnv->verifyCertificate(rExternal.Signer, {});
524 catch (const uno::SecurityException&)
526 DBG_UNHANDLED_EXCEPTION("xmlsecurity.helper", "failed to verify certificate");
527 rExternal.CertificateStatus = security::CertificateValidity::INVALID;
530 else
531 rExternal.CertificateStatus = security::CertificateValidity::INVALID;
534 return aRet;
537 sal_Int32 PDFSignatureHelper::GetNewSecurityId() const { return m_aSignatureInfos.size(); }
539 void PDFSignatureHelper::SetX509Certificate(svl::crypto::SigningContext& rSigningContext)
541 m_pSigningContext = &rSigningContext;
544 void PDFSignatureHelper::SetDescription(const OUString& rDescription)
546 m_aDescription = rDescription;
549 bool PDFSignatureHelper::Sign(const uno::Reference<frame::XModel>& xModel,
550 const uno::Reference<io::XInputStream>& xInputStream, bool bAdES)
552 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
553 vcl::filter::PDFDocument aDocument;
554 if (!aDocument.Read(*pStream))
556 SAL_WARN("xmlsecurity.helper", "failed to read the document");
557 return false;
560 sal_Int32 nPage = 0;
561 std::vector<sal_Int8> aSignatureLineShape;
562 GetSignatureLineShape(xModel, nPage, aSignatureLineShape);
563 if (nPage > 0)
565 // UNO page number is 1-based.
566 aDocument.SetSignaturePage(nPage - 1);
568 if (!aSignatureLineShape.empty())
570 aDocument.SetSignatureLine(std::move(aSignatureLineShape));
573 if (!m_pSigningContext || !aDocument.Sign(*m_pSigningContext, m_aDescription, bAdES))
575 if (m_pSigningContext && m_pSigningContext->m_xCertificate.is())
577 SAL_WARN("xmlsecurity.helper", "failed to sign");
579 return false;
582 uno::Reference<io::XStream> xStream(xInputStream, uno::UNO_QUERY);
583 std::unique_ptr<SvStream> pOutStream(utl::UcbStreamHelper::CreateStream(xStream, true));
584 if (!aDocument.Write(*pOutStream))
586 SAL_WARN("xmlsecurity.helper", "failed to write signed data");
587 return false;
590 return true;
593 bool PDFSignatureHelper::RemoveSignature(const uno::Reference<io::XInputStream>& xInputStream,
594 sal_uInt16 nPosition)
596 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
597 vcl::filter::PDFDocument aDocument;
598 if (!aDocument.Read(*pStream))
600 SAL_WARN("xmlsecurity.helper", "failed to read the document");
601 return false;
604 if (!aDocument.RemoveSignature(nPosition))
606 SAL_WARN("xmlsecurity.helper", "failed to remove signature");
607 return false;
610 uno::Reference<io::XStream> xStream(xInputStream, uno::UNO_QUERY);
611 uno::Reference<io::XTruncate> xTruncate(xStream, uno::UNO_QUERY);
612 if (!xTruncate.is())
614 SAL_WARN("xmlsecurity.helper", "failed to truncate");
615 return false;
617 xTruncate->truncate();
618 std::unique_ptr<SvStream> pOutStream(utl::UcbStreamHelper::CreateStream(xStream, true));
619 if (!aDocument.Write(*pOutStream))
621 SAL_WARN("xmlsecurity.helper", "failed to write without signature");
622 return false;
625 return true;
628 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */