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 <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
;
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())
50 uno::Reference
<beans::XPropertySet
> xPage(xController
->getCurrentPage(), uno::UNO_QUERY
);
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
)
68 if (!GetSignatureLinePage(xModel
, rPage
))
73 uno::Reference
<drawing::XShapes
> xShapes(xModel
->getCurrentSelection(), uno::UNO_QUERY
);
74 if (!xShapes
.is() || xShapes
->getCount() < 1)
79 uno::Reference
<beans::XPropertySet
> xShapeProps(xShapes
->getByIndex(0), uno::UNO_QUERY
);
80 if (!xShapeProps
.is())
85 comphelper::SequenceAsHashMap
aMap(xShapeProps
->getPropertyValue(u
"InteropGrabBag"_ustr
));
86 auto it
= aMap
.find(u
"SignatureCertificate"_ustr
);
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
);
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());
113 rSignatureLineShape
= std::vector
<sal_Int8
>(aStream
.GetSize());
114 aStream
.ReadBytes(rSignatureLineShape
.data(), rSignatureLineShape
.size());
117 /// Represents a parsed 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");
136 size_t nByteRangeOffset
= 0;
137 for (size_t i
= 0; i
< aByteRange
.size(); ++i
)
141 nByteRangeOffset
= aByteRange
[i
];
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)
158 rEOF
= rSignature
.m_aByteRanges
[1].first
+ rSignature
.m_aByteRanges
[1].second
;
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
)
170 if (rSignatures
.empty())
175 for (const auto& rSignature
: rSignatures
)
177 int nPerm
= rSignature
.m_pSignature
->getDocMDPPermission();
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
))
198 bool bFoundOwn
= false;
199 for (const auto& rEOF
: rAllEOFs
)
201 if (rEOF
== nSignatureEOF
)
212 if (rSignedEOFs
.find(rEOF
) == rSignedEOFs
.end())
214 // Unsigned incremental update found.
219 // Make sure we find the incremental update of the signature itself.
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.
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
)
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
,
256 auto pPdfium
= vcl::pdf::PDFiumLibrary::get();
257 std::unique_ptr
<vcl::pdf::PDFiumDocument
> pPdfDocument
258 = pPdfium
->openDocument(rStream
.GetData(), rStream
.GetSize(), OString());
264 int nPageCount
= pPdfDocument
->getPageCount();
265 for (int nPage
= 0; nPage
< nPageCount
; ++nPage
)
267 std::unique_ptr
<vcl::pdf::PDFiumPage
> pPdfPage
= pPdfDocument
->openPage(nPage
);
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
);
280 SAL_WARN("xmlsecurity.helper", "Cannot get PDFiumAnnotation");
283 vcl::pdf::PDFAnnotationSubType eType
= pPdfAnnotation
->getSubType();
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());
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
))
312 SvMemoryStream aSignatureStream
;
313 sal_uInt64 nPos
= rStream
.Tell();
315 aSignatureStream
.WriteStream(rStream
, nSignatureEOF
);
317 aSignatureStream
.Seek(0);
318 std::vector
<PageChecksum
> aSignedPages
;
319 AnalyizeSignatureStream(aSignatureStream
, aSignedPages
, nMDPPerm
);
321 SvMemoryStream aFullStream
;
322 nPos
= rStream
.Tell();
324 aFullStream
.WriteStream(rStream
);
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
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");
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");
362 SAL_WARN("xmlsecurity.helper",
363 "ValidateSignature: unsupported sub-filter: '" << aSubFilter
<< "'");
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
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");
380 if (rSignature
.m_aByteRanges
[0].first
!= 0)
382 SAL_WARN("xmlsecurity.helper", "ValidateSignature: first range start is not 0");
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");
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");
402 // At this point there is no obviously missing info to validate the
404 return svl::crypto::Signing::Verify(rStream
, rSignature
.m_aByteRanges
, bNonDetached
, aContents
,
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");
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();
432 SvMemoryStream aStream
;
433 sal_uInt64 nPos
= rStream
.Tell();
435 aStream
.WriteStream(rStream
);
437 std::unique_ptr
<vcl::pdf::PDFiumDocument
> pPdfDocument
438 = pPdfium
->openDocument(aStream
.GetData(), aStream
.GetSize(), OString());
441 SAL_WARN("xmlsecurity.helper", "failed to read the document");
445 int nSignatureCount
= pPdfDocument
->getSignatureCount();
446 if (nSignatureCount
<= 0)
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
)
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
,
482 SAL_WARN("xmlsecurity.helper", "failed to determine digest match");
485 m_aSignatureInfos
.push_back(aInfo
);
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
;
531 rExternal
.CertificateStatus
= security::CertificateValidity::INVALID
;
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");
561 std::vector
<sal_Int8
> aSignatureLineShape
;
562 GetSignatureLineShape(xModel
, nPage
, aSignatureLineShape
);
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");
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");
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");
604 if (!aDocument
.RemoveSignature(nPosition
))
606 SAL_WARN("xmlsecurity.helper", "failed to remove signature");
610 uno::Reference
<io::XStream
> xStream(xInputStream
, uno::UNO_QUERY
);
611 uno::Reference
<io::XTruncate
> xTruncate(xStream
, uno::UNO_QUERY
);
614 SAL_WARN("xmlsecurity.helper", "failed to truncate");
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");
628 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */