1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <pdfsignaturehelper.hxx>
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 <tools/diagnose_ex.h>
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 <rtl/ustrbuf.hxx>
35 #include <svl/cryptosign.hxx>
36 #include <config_features.h>
37 #include <vcl/filter/PDFiumLibrary.hxx>
38 #if HAVE_FEATURE_PDFIUM
39 #include <fpdf_signature.h>
42 using namespace ::com::sun::star
;
46 /// Gets the current page of the current view from xModel and puts it to the 1-based rPage.
47 bool GetSignatureLinePage(const uno::Reference
<frame::XModel
>& xModel
, sal_Int32
& rPage
)
49 uno::Reference
<drawing::XDrawView
> xController(xModel
->getCurrentController(), uno::UNO_QUERY
);
50 if (!xController
.is())
55 uno::Reference
<beans::XPropertySet
> xPage(xController
->getCurrentPage(), uno::UNO_QUERY
);
61 return xPage
->getPropertyValue("Number") >>= rPage
;
64 /// If the currently selected shape is a Draw signature line, export that to PDF.
65 void GetSignatureLineShape(const uno::Reference
<frame::XModel
>& xModel
, sal_Int32
& rPage
,
66 std::vector
<sal_Int8
>& rSignatureLineShape
)
73 if (!GetSignatureLinePage(xModel
, rPage
))
78 uno::Reference
<drawing::XShapes
> xShapes(xModel
->getCurrentSelection(), uno::UNO_QUERY
);
79 if (!xShapes
.is() || xShapes
->getCount() < 1)
84 uno::Reference
<beans::XPropertySet
> xShapeProps(xShapes
->getByIndex(0), uno::UNO_QUERY
);
85 if (!xShapeProps
.is())
90 comphelper::SequenceAsHashMap
aMap(xShapeProps
->getPropertyValue("InteropGrabBag"));
91 auto it
= aMap
.find("SignatureCertificate");
97 // We know that we add a signature line shape to an existing PDF at this point.
99 uno::Reference
<frame::XStorable
> xStorable(xModel
, uno::UNO_QUERY
);
105 // Export just the signature line.
106 utl::MediaDescriptor aMediaDescriptor
;
107 aMediaDescriptor
["FilterName"] <<= OUString("draw_pdf_Export");
108 SvMemoryStream aStream
;
109 uno::Reference
<io::XOutputStream
> xStream(new utl::OStreamWrapper(aStream
));
110 aMediaDescriptor
["OutputStream"] <<= xStream
;
111 uno::Sequence
<beans::PropertyValue
> aFilterData(
112 comphelper::InitPropertySequence({ { "Selection", uno::Any(xShapes
) } }));
113 aMediaDescriptor
["FilterData"] <<= aFilterData
;
114 xStorable
->storeToURL("private:stream", aMediaDescriptor
.getAsConstPropertyValueList());
118 rSignatureLineShape
= std::vector
<sal_Int8
>(aStream
.GetSize());
119 aStream
.ReadBytes(rSignatureLineShape
.data(), rSignatureLineShape
.size());
122 #if HAVE_FEATURE_PDFIUM
123 /// Represents a parsed signature.
126 std::unique_ptr
<vcl::pdf::PDFiumSignature
> m_pSignature
;
127 /// Offset+length pairs.
128 std::vector
<std::pair
<size_t, size_t>> m_aByteRanges
;
131 /// Turns an array of floats into offset + length pairs.
132 void GetByteRangesFromPDF(std::unique_ptr
<vcl::pdf::PDFiumSignature
>& pSignature
,
133 std::vector
<std::pair
<size_t, size_t>>& rByteRanges
)
135 int nByteRangeLen
= FPDFSignatureObj_GetByteRange(pSignature
->getPointer(), nullptr, 0);
136 if (nByteRangeLen
<= 0)
138 SAL_WARN("xmlsecurity.helper", "GetByteRangesFromPDF: no byte ranges");
142 std::vector
<int> aByteRange(nByteRangeLen
);
143 FPDFSignatureObj_GetByteRange(pSignature
->getPointer(), aByteRange
.data(), aByteRange
.size());
145 size_t nByteRangeOffset
= 0;
146 for (size_t i
= 0; i
< aByteRange
.size(); ++i
)
150 nByteRangeOffset
= aByteRange
[i
];
154 size_t nLength
= aByteRange
[i
];
155 rByteRanges
.emplace_back(nByteRangeOffset
, nLength
);
159 /// Determines the last position that is covered by a signature.
160 bool GetEOFOfSignature(const Signature
& rSignature
, size_t& rEOF
)
162 if (rSignature
.m_aByteRanges
.size() < 2)
167 rEOF
= rSignature
.m_aByteRanges
[1].first
+ rSignature
.m_aByteRanges
[1].second
;
172 * Get the value of the "modification detection and prevention" permission:
173 * Valid values are 1, 2 and 3: only 3 allows annotations after signing.
175 int GetMDPPerm(const std::vector
<Signature
>& rSignatures
)
179 if (rSignatures
.empty())
184 for (const auto& rSignature
: rSignatures
)
186 int nPerm
= FPDFSignatureObj_GetDocMDPPermission(rSignature
.m_pSignature
->getPointer());
196 /// Checks if there are unsigned incremental updates between the signatures or after the last one.
197 bool IsCompleteSignature(SvStream
& rStream
, const Signature
& rSignature
,
198 const std::set
<unsigned int>& rSignedEOFs
,
199 const std::vector
<unsigned int>& rAllEOFs
)
201 size_t nSignatureEOF
= 0;
202 if (!GetEOFOfSignature(rSignature
, nSignatureEOF
))
207 bool bFoundOwn
= false;
208 for (const auto& rEOF
: rAllEOFs
)
210 if (rEOF
== nSignatureEOF
)
221 if (rSignedEOFs
.find(rEOF
) == rSignedEOFs
.end())
223 // Unsigned incremental update found.
228 // Make sure we find the incremental update of the signature itself.
234 // No additional content after the last incremental update.
235 rStream
.Seek(STREAM_SEEK_TO_END
);
236 size_t nFileEnd
= rStream
.Tell();
237 return std::find(rAllEOFs
.begin(), rAllEOFs
.end(), nFileEnd
) != rAllEOFs
.end();
241 * Contains checksums of a PDF page, which is rendered without annotations. It also contains
242 * the geometry of a few dangerous annotation types.
246 BitmapChecksum m_nPageContent
;
247 std::vector
<basegfx::B2DRectangle
> m_aAnnotations
;
248 bool operator==(const PageChecksum
& rChecksum
) const;
251 bool PageChecksum::operator==(const PageChecksum
& rChecksum
) const
253 if (m_nPageContent
!= rChecksum
.m_nPageContent
)
258 return m_aAnnotations
== rChecksum
.m_aAnnotations
;
261 /// Collects the checksum of each page of one version of the PDF.
262 void AnalyizeSignatureStream(SvMemoryStream
& rStream
, std::vector
<PageChecksum
>& rPageChecksums
,
265 auto pPdfium
= vcl::pdf::PDFiumLibrary::get();
266 std::unique_ptr
<vcl::pdf::PDFiumDocument
> pPdfDocument
267 = pPdfium
->openDocument(rStream
.GetData(), rStream
.GetSize());
273 int nPageCount
= pPdfDocument
->getPageCount();
274 for (int nPage
= 0; nPage
< nPageCount
; ++nPage
)
276 std::unique_ptr
<vcl::pdf::PDFiumPage
> pPdfPage
= pPdfDocument
->openPage(nPage
);
282 PageChecksum aPageChecksum
;
283 aPageChecksum
.m_nPageContent
= pPdfPage
->getChecksum(nMDPPerm
);
284 for (int i
= 0; i
< pPdfPage
->getAnnotationCount(); ++i
)
286 std::unique_ptr
<vcl::pdf::PDFiumAnnotation
> pPdfAnnotation
= pPdfPage
->getAnnotation(i
);
287 vcl::pdf::PDFAnnotationSubType eType
= pPdfAnnotation
->getSubType();
290 case vcl::pdf::PDFAnnotationSubType::Unknown
:
291 case vcl::pdf::PDFAnnotationSubType::FreeText
:
292 case vcl::pdf::PDFAnnotationSubType::Stamp
:
293 case vcl::pdf::PDFAnnotationSubType::Redact
:
294 aPageChecksum
.m_aAnnotations
.push_back(pPdfAnnotation
->getRectangle());
300 rPageChecksums
.push_back(aPageChecksum
);
305 * Checks if incremental updates after singing performed valid modifications only.
306 * nMDPPerm decides if annotations/commenting is OK, other changes are always not.
308 bool IsValidSignature(SvStream
& rStream
, const Signature
& rSignature
, int nMDPPerm
)
310 size_t nSignatureEOF
= 0;
311 if (!GetEOFOfSignature(rSignature
, nSignatureEOF
))
316 SvMemoryStream aSignatureStream
;
317 sal_uInt64 nPos
= rStream
.Tell();
319 aSignatureStream
.WriteStream(rStream
, nSignatureEOF
);
321 aSignatureStream
.Seek(0);
322 std::vector
<PageChecksum
> aSignedPages
;
323 AnalyizeSignatureStream(aSignatureStream
, aSignedPages
, nMDPPerm
);
325 SvMemoryStream aFullStream
;
326 nPos
= rStream
.Tell();
328 aFullStream
.WriteStream(rStream
);
331 std::vector
<PageChecksum
> aAllPages
;
332 AnalyizeSignatureStream(aFullStream
, aAllPages
, nMDPPerm
);
334 // Fail if any page looks different after signing and at the end. Annotations/commenting doesn't
336 return aSignedPages
== aAllPages
;
340 * @param rInformation The actual result.
341 * @param rDocument the parsed document to see if the signature is partial.
342 * @return If we can determinate a result.
344 bool ValidateSignature(SvStream
& rStream
, const Signature
& rSignature
,
345 SignatureInformation
& rInformation
, int nMDPPerm
,
346 const std::set
<unsigned int>& rSignatureEOFs
,
347 const std::vector
<unsigned int>& rTrailerEnds
)
350 = FPDFSignatureObj_GetContents(rSignature
.m_pSignature
->getPointer(), nullptr, 0);
351 if (nContentsLen
<= 0)
353 SAL_WARN("xmlsecurity.helper", "ValidateSignature: no contents");
356 std::vector
<unsigned char> aContents(nContentsLen
);
357 FPDFSignatureObj_GetContents(rSignature
.m_pSignature
->getPointer(), aContents
.data(),
361 = FPDFSignatureObj_GetSubFilter(rSignature
.m_pSignature
->getPointer(), nullptr, 0);
362 std::vector
<char> aSubFilterBuf(nSubFilterLen
);
363 FPDFSignatureObj_GetSubFilter(rSignature
.m_pSignature
->getPointer(), aSubFilterBuf
.data(),
364 aSubFilterBuf
.size());
365 // Buffer is NUL-terminated.
366 OString
aSubFilter(aSubFilterBuf
.data(), aSubFilterBuf
.size() - 1);
368 const bool bNonDetached
= aSubFilter
== "adbe.pkcs7.sha1";
369 if (aSubFilter
.isEmpty()
370 || (aSubFilter
!= "adbe.pkcs7.detached" && !bNonDetached
371 && aSubFilter
!= "ETSI.CAdES.detached"))
373 if (aSubFilter
.isEmpty())
374 SAL_WARN("xmlsecurity.helper", "ValidateSignature: missing sub-filter");
376 SAL_WARN("xmlsecurity.helper",
377 "ValidateSignature: unsupported sub-filter: '" << aSubFilter
<< "'");
381 // Reason / comment / description is optional.
382 int nReasonLen
= FPDFSignatureObj_GetReason(rSignature
.m_pSignature
->getPointer(), nullptr, 0);
385 std::vector
<char16_t
> aReasonBuf(nReasonLen
);
386 FPDFSignatureObj_GetReason(rSignature
.m_pSignature
->getPointer(), aReasonBuf
.data(),
388 rInformation
.ouDescription
= OUString(aReasonBuf
.data(), aReasonBuf
.size() - 1);
391 // Date: used only when the time of signing is not available in the
393 int nTimeLen
= FPDFSignatureObj_GetTime(rSignature
.m_pSignature
->getPointer(), nullptr, 0);
396 // Example: "D:20161027100104".
397 std::vector
<char> aTimeBuf(nTimeLen
);
398 FPDFSignatureObj_GetTime(rSignature
.m_pSignature
->getPointer(), aTimeBuf
.data(),
400 OString
aM(aTimeBuf
.data(), aTimeBuf
.size() - 1);
401 if (aM
.startsWith("D:") && aM
.getLength() >= 16)
403 rInformation
.stDateTime
.Year
= aM
.copy(2, 4).toInt32();
404 rInformation
.stDateTime
.Month
= aM
.copy(6, 2).toInt32();
405 rInformation
.stDateTime
.Day
= aM
.copy(8, 2).toInt32();
406 rInformation
.stDateTime
.Hours
= aM
.copy(10, 2).toInt32();
407 rInformation
.stDateTime
.Minutes
= aM
.copy(12, 2).toInt32();
408 rInformation
.stDateTime
.Seconds
= aM
.copy(14, 2).toInt32();
412 // Detect if the byte ranges don't cover everything, but the signature itself.
413 if (rSignature
.m_aByteRanges
.size() < 2)
415 SAL_WARN("xmlsecurity.helper", "ValidateSignature: expected 2 byte ranges");
418 if (rSignature
.m_aByteRanges
[0].first
!= 0)
420 SAL_WARN("xmlsecurity.helper", "ValidateSignature: first range start is not 0");
423 // Binary vs hex dump and 2 is the leading "<" and the trailing ">" around the hex string.
424 size_t nSignatureLength
= aContents
.size() * 2 + 2;
425 if (rSignature
.m_aByteRanges
[1].first
426 != (rSignature
.m_aByteRanges
[0].second
+ nSignatureLength
))
428 SAL_WARN("xmlsecurity.helper",
429 "ValidateSignature: second range start is not the end of the signature");
432 rInformation
.bPartialDocumentSignature
433 = !IsCompleteSignature(rStream
, rSignature
, rSignatureEOFs
, rTrailerEnds
);
434 if (!IsValidSignature(rStream
, rSignature
, nMDPPerm
))
436 SAL_WARN("xmlsecurity.helper", "ValidateSignature: invalid incremental update detected");
440 // At this point there is no obviously missing info to validate the
442 return svl::crypto::Signing::Verify(rStream
, rSignature
.m_aByteRanges
, bNonDetached
, aContents
,
448 PDFSignatureHelper::PDFSignatureHelper() = default;
450 bool PDFSignatureHelper::ReadAndVerifySignature(
451 const uno::Reference
<io::XInputStream
>& xInputStream
)
453 if (!xInputStream
.is())
455 SAL_WARN("xmlsecurity.helper", "input stream missing");
459 std::unique_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
460 return ReadAndVerifySignatureSvStream(*pStream
);
463 bool PDFSignatureHelper::ReadAndVerifySignatureSvStream(SvStream
& rStream
)
465 #if HAVE_FEATURE_PDFIUM
466 auto pPdfium
= vcl::pdf::PDFiumLibrary::get();
467 SvMemoryStream aStream
;
468 sal_uInt64 nPos
= rStream
.Tell();
470 aStream
.WriteStream(rStream
);
472 std::unique_ptr
<vcl::pdf::PDFiumDocument
> pPdfDocument
473 = pPdfium
->openDocument(aStream
.GetData(), aStream
.GetSize());
476 SAL_WARN("xmlsecurity.helper", "failed to read the document");
480 int nSignatureCount
= pPdfDocument
->getSignatureCount();
481 if (nSignatureCount
<= 0)
485 std::vector
<Signature
> aSignatures(nSignatureCount
);
486 for (int i
= 0; i
< nSignatureCount
; ++i
)
488 std::unique_ptr
<vcl::pdf::PDFiumSignature
> pSignature
= pPdfDocument
->getSignature(i
);
489 std::vector
<std::pair
<size_t, size_t>> aByteRanges
;
490 GetByteRangesFromPDF(pSignature
, aByteRanges
);
491 aSignatures
[i
] = Signature
{ std::move(pSignature
), aByteRanges
};
494 std::set
<unsigned int> aSignatureEOFs
;
495 for (const auto& rSignature
: aSignatures
)
498 if (GetEOFOfSignature(rSignature
, nEOF
))
500 aSignatureEOFs
.insert(nEOF
);
504 std::vector
<unsigned int> aTrailerEnds
= pPdfDocument
->getTrailerEnds();
506 m_aSignatureInfos
.clear();
508 int nMDPPerm
= GetMDPPerm(aSignatures
);
510 for (size_t i
= 0; i
< aSignatures
.size(); ++i
)
512 SignatureInformation
aInfo(i
);
514 if (!ValidateSignature(rStream
, aSignatures
[i
], aInfo
, nMDPPerm
, aSignatureEOFs
,
517 SAL_WARN("xmlsecurity.helper", "failed to determine digest match");
520 m_aSignatureInfos
.push_back(aInfo
);
529 SignatureInformations
const& PDFSignatureHelper::GetSignatureInformations() const
531 return m_aSignatureInfos
;
534 uno::Sequence
<security::DocumentSignatureInformation
>
535 PDFSignatureHelper::GetDocumentSignatureInformations(
536 const uno::Reference
<xml::crypto::XSecurityEnvironment
>& xSecEnv
) const
538 uno::Sequence
<security::DocumentSignatureInformation
> aRet(m_aSignatureInfos
.size());
540 for (size_t i
= 0; i
< m_aSignatureInfos
.size(); ++i
)
542 const SignatureInformation
& rInternal
= m_aSignatureInfos
[i
];
543 security::DocumentSignatureInformation
& rExternal
= aRet
[i
];
544 rExternal
.SignatureIsValid
545 = rInternal
.nStatus
== xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED
;
546 if (rInternal
.GetSigningCertificate()
547 && !rInternal
.GetSigningCertificate()->X509Certificate
.isEmpty())
549 rExternal
.Signer
= xSecEnv
->createCertificateFromAscii(
550 rInternal
.GetSigningCertificate()->X509Certificate
);
552 rExternal
.PartialDocumentSignature
= rInternal
.bPartialDocumentSignature
;
554 // Verify certificate.
555 if (rExternal
.Signer
.is())
559 rExternal
.CertificateStatus
= xSecEnv
->verifyCertificate(rExternal
.Signer
, {});
561 catch (const uno::SecurityException
&)
563 DBG_UNHANDLED_EXCEPTION("xmlsecurity.helper", "failed to verify certificate");
564 rExternal
.CertificateStatus
= security::CertificateValidity::INVALID
;
568 rExternal
.CertificateStatus
= security::CertificateValidity::INVALID
;
574 sal_Int32
PDFSignatureHelper::GetNewSecurityId() const { return m_aSignatureInfos
.size(); }
576 void PDFSignatureHelper::SetX509Certificate(
577 const uno::Reference
<security::XCertificate
>& xCertificate
)
579 m_xCertificate
= xCertificate
;
582 void PDFSignatureHelper::SetDescription(const OUString
& rDescription
)
584 m_aDescription
= rDescription
;
587 bool PDFSignatureHelper::Sign(const uno::Reference
<frame::XModel
>& xModel
,
588 const uno::Reference
<io::XInputStream
>& xInputStream
, bool bAdES
)
590 std::unique_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
591 vcl::filter::PDFDocument aDocument
;
592 if (!aDocument
.Read(*pStream
))
594 SAL_WARN("xmlsecurity.helper", "failed to read the document");
599 std::vector
<sal_Int8
> aSignatureLineShape
;
600 GetSignatureLineShape(xModel
, nPage
, aSignatureLineShape
);
603 // UNO page number is 1-based.
604 aDocument
.SetSignaturePage(nPage
- 1);
606 if (!aSignatureLineShape
.empty())
608 aDocument
.SetSignatureLine(aSignatureLineShape
);
611 if (!aDocument
.Sign(m_xCertificate
, m_aDescription
, bAdES
))
613 SAL_WARN("xmlsecurity.helper", "failed to sign");
617 uno::Reference
<io::XStream
> xStream(xInputStream
, uno::UNO_QUERY
);
618 std::unique_ptr
<SvStream
> pOutStream(utl::UcbStreamHelper::CreateStream(xStream
, true));
619 if (!aDocument
.Write(*pOutStream
))
621 SAL_WARN("xmlsecurity.helper", "failed to write signed data");
628 bool PDFSignatureHelper::RemoveSignature(const uno::Reference
<io::XInputStream
>& xInputStream
,
629 sal_uInt16 nPosition
)
631 std::unique_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
632 vcl::filter::PDFDocument aDocument
;
633 if (!aDocument
.Read(*pStream
))
635 SAL_WARN("xmlsecurity.helper", "failed to read the document");
639 if (!aDocument
.RemoveSignature(nPosition
))
641 SAL_WARN("xmlsecurity.helper", "failed to remove signature");
645 uno::Reference
<io::XStream
> xStream(xInputStream
, uno::UNO_QUERY
);
646 uno::Reference
<io::XTruncate
> xTruncate(xStream
, uno::UNO_QUERY
);
649 SAL_WARN("xmlsecurity.helper", "failed to truncate");
652 xTruncate
->truncate();
653 std::unique_ptr
<SvStream
> pOutStream(utl::UcbStreamHelper::CreateStream(xStream
, true));
654 if (!aDocument
.Write(*pOutStream
))
656 SAL_WARN("xmlsecurity.helper", "failed to write without signature");
663 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */