LibreOffice Module xmlsecurity (master) 1
pdfsignaturehelper.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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 */
9
11
12#include <memory>
13
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>
25
27#include <sal/log.hxx>
33#include <vcl/checksum.hxx>
34#include <svl/cryptosign.hxx>
36
37using namespace ::com::sun::star;
38
39namespace
40{
42bool GetSignatureLinePage(const uno::Reference<frame::XModel>& xModel, sal_Int32& rPage)
43{
44 uno::Reference<drawing::XDrawView> xController(xModel->getCurrentController(), uno::UNO_QUERY);
45 if (!xController.is())
46 {
47 return false;
48 }
49
50 uno::Reference<beans::XPropertySet> xPage(xController->getCurrentPage(), uno::UNO_QUERY);
51 if (!xPage.is())
52 {
53 return false;
54 }
55
56 return xPage->getPropertyValue("Number") >>= rPage;
57}
58
60void GetSignatureLineShape(const uno::Reference<frame::XModel>& xModel, sal_Int32& rPage,
61 std::vector<sal_Int8>& rSignatureLineShape)
62{
63 if (!xModel.is())
64 {
65 return;
66 }
67
68 if (!GetSignatureLinePage(xModel, rPage))
69 {
70 return;
71 }
72
73 uno::Reference<drawing::XShapes> xShapes(xModel->getCurrentSelection(), uno::UNO_QUERY);
74 if (!xShapes.is() || xShapes->getCount() < 1)
75 {
76 return;
77 }
78
79 uno::Reference<beans::XPropertySet> xShapeProps(xShapes->getByIndex(0), uno::UNO_QUERY);
80 if (!xShapeProps.is())
81 {
82 return;
83 }
84
85 comphelper::SequenceAsHashMap aMap(xShapeProps->getPropertyValue("InteropGrabBag"));
86 auto it = aMap.find("SignatureCertificate");
87 if (it == aMap.end())
88 {
89 return;
90 }
91
92 // We know that we add a signature line shape to an existing PDF at this point.
93
94 uno::Reference<frame::XStorable> xStorable(xModel, uno::UNO_QUERY);
95 if (!xStorable.is())
96 {
97 return;
98 }
99
100 // Export just the signature line.
101 utl::MediaDescriptor aMediaDescriptor;
102 aMediaDescriptor["FilterName"] <<= OUString("draw_pdf_Export");
103 SvMemoryStream aStream;
104 uno::Reference<io::XOutputStream> xStream(new utl::OStreamWrapper(aStream));
105 aMediaDescriptor["OutputStream"] <<= xStream;
106 uno::Sequence<beans::PropertyValue> aFilterData(
107 comphelper::InitPropertySequence({ { "Selection", uno::Any(xShapes) } }));
108 aMediaDescriptor["FilterData"] <<= aFilterData;
109 xStorable->storeToURL("private:stream", aMediaDescriptor.getAsConstPropertyValueList());
110 xStream->flush();
111
112 aStream.Seek(0);
113 rSignatureLineShape = std::vector<sal_Int8>(aStream.GetSize());
114 aStream.ReadBytes(rSignatureLineShape.data(), rSignatureLineShape.size());
115}
116
118struct Signature
119{
120 std::unique_ptr<vcl::pdf::PDFiumSignature> m_pSignature;
122 std::vector<std::pair<size_t, size_t>> m_aByteRanges;
123};
124
126void GetByteRangesFromPDF(const std::unique_ptr<vcl::pdf::PDFiumSignature>& pSignature,
127 std::vector<std::pair<size_t, size_t>>& rByteRanges)
128{
129 std::vector<int> aByteRange = pSignature->getByteRange();
130 if (aByteRange.empty())
131 {
132 SAL_WARN("xmlsecurity.helper", "GetByteRangesFromPDF: no byte ranges");
133 return;
134 }
135
136 size_t nByteRangeOffset = 0;
137 for (size_t i = 0; i < aByteRange.size(); ++i)
138 {
139 if (i % 2 == 0)
140 {
141 nByteRangeOffset = aByteRange[i];
142 continue;
143 }
144
145 size_t nLength = aByteRange[i];
146 rByteRanges.emplace_back(nByteRangeOffset, nLength);
147 }
148}
149
151bool GetEOFOfSignature(const Signature& rSignature, size_t& rEOF)
152{
153 if (rSignature.m_aByteRanges.size() < 2)
154 {
155 return false;
156 }
157
158 rEOF = rSignature.m_aByteRanges[1].first + rSignature.m_aByteRanges[1].second;
159 return true;
160}
161
166int GetMDPPerm(const std::vector<Signature>& rSignatures)
167{
168 int nRet = 3;
169
170 if (rSignatures.empty())
171 {
172 return nRet;
173 }
174
175 for (const auto& rSignature : rSignatures)
176 {
177 int nPerm = rSignature.m_pSignature->getDocMDPPermission();
178 if (nPerm != 0)
179 {
180 return nPerm;
181 }
182 }
183
184 return nRet;
185}
186
188bool IsCompleteSignature(SvStream& rStream, const Signature& rSignature,
189 const std::set<unsigned int>& rSignedEOFs,
190 const std::vector<unsigned int>& rAllEOFs)
191{
192 size_t nSignatureEOF = 0;
193 if (!GetEOFOfSignature(rSignature, nSignatureEOF))
194 {
195 return false;
196 }
197
198 bool bFoundOwn = false;
199 for (const auto& rEOF : rAllEOFs)
200 {
201 if (rEOF == nSignatureEOF)
202 {
203 bFoundOwn = true;
204 continue;
205 }
206
207 if (!bFoundOwn)
208 {
209 continue;
210 }
211
212 if (rSignedEOFs.find(rEOF) == rSignedEOFs.end())
213 {
214 // Unsigned incremental update found.
215 return false;
216 }
217 }
218
219 // Make sure we find the incremental update of the signature itself.
220 if (!bFoundOwn)
221 {
222 return false;
223 }
224
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();
229}
230
235struct PageChecksum
236{
237 BitmapChecksum m_nPageContent;
238 std::vector<basegfx::B2DRectangle> m_aAnnotations;
239 bool operator==(const PageChecksum& rChecksum) const;
240};
241
242bool PageChecksum::operator==(const PageChecksum& rChecksum) const
243{
244 if (m_nPageContent != rChecksum.m_nPageContent)
245 {
246 return false;
247 }
248
249 return m_aAnnotations == rChecksum.m_aAnnotations;
250}
251
253void AnalyizeSignatureStream(SvMemoryStream& rStream, std::vector<PageChecksum>& rPageChecksums,
254 int nMDPPerm)
255{
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)
260 {
261 return;
262 }
263
264 int nPageCount = pPdfDocument->getPageCount();
265 for (int nPage = 0; nPage < nPageCount; ++nPage)
266 {
267 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(nPage);
268 if (!pPdfPage)
269 {
270 return;
271 }
272
273 PageChecksum aPageChecksum;
274 aPageChecksum.m_nPageContent = pPdfPage->getChecksum(nMDPPerm);
275 for (int i = 0; i < pPdfPage->getAnnotationCount(); ++i)
276 {
277 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pPdfAnnotation = pPdfPage->getAnnotation(i);
278 if (!pPdfAnnotation)
279 {
280 SAL_WARN("xmlsecurity.helper", "Cannot get PDFiumAnnotation");
281 continue;
282 }
283 vcl::pdf::PDFAnnotationSubType eType = pPdfAnnotation->getSubType();
284 switch (eType)
285 {
290 aPageChecksum.m_aAnnotations.push_back(pPdfAnnotation->getRectangle());
291 break;
292 default:
293 break;
294 }
295 }
296 rPageChecksums.push_back(aPageChecksum);
297 }
298}
299
304bool IsValidSignature(SvStream& rStream, const Signature& rSignature, int nMDPPerm)
305{
306 size_t nSignatureEOF = 0;
307 if (!GetEOFOfSignature(rSignature, nSignatureEOF))
308 {
309 return false;
310 }
311
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);
320
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);
329
330 // Fail if any page looks different after signing and at the end. Annotations/commenting doesn't
331 // count, though.
332 return aSignedPages == aAllPages;
333}
334
340bool 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)
344{
345 std::vector<unsigned char> aContents = rSignature.m_pSignature->getContents();
346 if (aContents.empty())
347 {
348 SAL_WARN("xmlsecurity.helper", "ValidateSignature: no contents");
349 return false;
350 }
351
352 OString aSubFilter = rSignature.m_pSignature->getSubFilter();
353
354 const bool bNonDetached = aSubFilter == "adbe.pkcs7.sha1";
355 if (aSubFilter.isEmpty()
356 || (aSubFilter != "adbe.pkcs7.detached" && !bNonDetached
357 && aSubFilter != "ETSI.CAdES.detached"))
358 {
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;
365 }
366
367 // Reason / comment / description is optional.
368 rInformation.ouDescription = rSignature.m_pSignature->getReason();
369
370 // Date: used only when the time of signing is not available in the
371 // signature.
372 rInformation.stDateTime = rSignature.m_pSignature->getTime();
373
374 // Detect if the byte ranges don't cover everything, but the signature itself.
375 if (rSignature.m_aByteRanges.size() < 2)
376 {
377 SAL_WARN("xmlsecurity.helper", "ValidateSignature: expected 2 byte ranges");
378 return false;
379 }
380 if (rSignature.m_aByteRanges[0].first != 0)
381 {
382 SAL_WARN("xmlsecurity.helper", "ValidateSignature: first range start is not 0");
383 return false;
384 }
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))
389 {
390 SAL_WARN("xmlsecurity.helper",
391 "ValidateSignature: second range start is not the end of the signature");
392 return false;
393 }
394 rInformation.bPartialDocumentSignature
395 = !IsCompleteSignature(rStream, rSignature, rSignatureEOFs, rTrailerEnds);
396 if (!IsValidSignature(rStream, rSignature, nMDPPerm))
397 {
398 SAL_WARN("xmlsecurity.helper", "ValidateSignature: invalid incremental update detected");
399 return false;
400 }
401
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);
406}
407}
408
410
412 const uno::Reference<io::XInputStream>& xInputStream)
413{
414 if (!xInputStream.is())
415 {
416 SAL_WARN("xmlsecurity.helper", "input stream missing");
417 return false;
418 }
419
420 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
421 return ReadAndVerifySignatureSvStream(*pStream);
422}
423
425{
426 auto pPdfium = vcl::pdf::PDFiumLibrary::get();
427 if (!pPdfium)
428 {
429 return true;
430 }
431
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)
440 {
441 SAL_WARN("xmlsecurity.helper", "failed to read the document");
442 return false;
443 }
444
445 int nSignatureCount = pPdfDocument->getSignatureCount();
446 if (nSignatureCount <= 0)
447 {
448 return true;
449 }
450 std::vector<Signature> aSignatures(nSignatureCount);
451 for (int i = 0; i < nSignatureCount; ++i)
452 {
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), aByteRanges };
457 }
458
459 std::set<unsigned int> aSignatureEOFs;
460 for (const auto& rSignature : aSignatures)
461 {
462 size_t nEOF = 0;
463 if (GetEOFOfSignature(rSignature, nEOF))
464 {
465 aSignatureEOFs.insert(nEOF);
466 }
467 }
468
469 std::vector<unsigned int> aTrailerEnds = pPdfDocument->getTrailerEnds();
470
471 m_aSignatureInfos.clear();
472
473 int nMDPPerm = GetMDPPerm(aSignatures);
474
475 for (size_t i = 0; i < aSignatures.size(); ++i)
476 {
477 SignatureInformation aInfo(i);
478
479 if (!ValidateSignature(rStream, aSignatures[i], aInfo, nMDPPerm, aSignatureEOFs,
480 aTrailerEnds))
481 {
482 SAL_WARN("xmlsecurity.helper", "failed to determine digest match");
483 }
484
485 m_aSignatureInfos.push_back(aInfo);
486 }
487
488 return true;
489}
490
492{
493 return m_aSignatureInfos;
494}
495
496uno::Sequence<security::DocumentSignatureInformation>
498 const uno::Reference<xml::crypto::XSecurityEnvironment>& xSecEnv) const
499{
500 uno::Sequence<security::DocumentSignatureInformation> aRet(m_aSignatureInfos.size());
501 auto aRetRange = asNonConstRange(aRet);
502
503 for (size_t i = 0; i < m_aSignatureInfos.size(); ++i)
504 {
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())
511 {
512 rExternal.Signer = xSecEnv->createCertificateFromAscii(
514 }
515 rExternal.PartialDocumentSignature = rInternal.bPartialDocumentSignature;
516
517 // Verify certificate.
518 if (rExternal.Signer.is())
519 {
520 try
521 {
522 rExternal.CertificateStatus = xSecEnv->verifyCertificate(rExternal.Signer, {});
523 }
524 catch (const uno::SecurityException&)
525 {
526 DBG_UNHANDLED_EXCEPTION("xmlsecurity.helper", "failed to verify certificate");
527 rExternal.CertificateStatus = security::CertificateValidity::INVALID;
528 }
529 }
530 else
531 rExternal.CertificateStatus = security::CertificateValidity::INVALID;
532 }
533
534 return aRet;
535}
536
537sal_Int32 PDFSignatureHelper::GetNewSecurityId() const { return m_aSignatureInfos.size(); }
538
540 const uno::Reference<security::XCertificate>& xCertificate)
541{
542 m_xCertificate = xCertificate;
543}
544
545void PDFSignatureHelper::SetDescription(const OUString& rDescription)
546{
547 m_aDescription = rDescription;
548}
549
550bool PDFSignatureHelper::Sign(const uno::Reference<frame::XModel>& xModel,
551 const uno::Reference<io::XInputStream>& xInputStream, bool bAdES)
552{
553 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
555 if (!aDocument.Read(*pStream))
556 {
557 SAL_WARN("xmlsecurity.helper", "failed to read the document");
558 return false;
559 }
560
561 sal_Int32 nPage = 0;
562 std::vector<sal_Int8> aSignatureLineShape;
563 GetSignatureLineShape(xModel, nPage, aSignatureLineShape);
564 if (nPage > 0)
565 {
566 // UNO page number is 1-based.
567 aDocument.SetSignaturePage(nPage - 1);
568 }
569 if (!aSignatureLineShape.empty())
570 {
571 aDocument.SetSignatureLine(std::move(aSignatureLineShape));
572 }
573
574 if (!aDocument.Sign(m_xCertificate, m_aDescription, bAdES))
575 {
576 SAL_WARN("xmlsecurity.helper", "failed to sign");
577 return false;
578 }
579
580 uno::Reference<io::XStream> xStream(xInputStream, uno::UNO_QUERY);
581 std::unique_ptr<SvStream> pOutStream(utl::UcbStreamHelper::CreateStream(xStream, true));
582 if (!aDocument.Write(*pOutStream))
583 {
584 SAL_WARN("xmlsecurity.helper", "failed to write signed data");
585 return false;
586 }
587
588 return true;
589}
590
591bool PDFSignatureHelper::RemoveSignature(const uno::Reference<io::XInputStream>& xInputStream,
592 sal_uInt16 nPosition)
593{
594 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
596 if (!aDocument.Read(*pStream))
597 {
598 SAL_WARN("xmlsecurity.helper", "failed to read the document");
599 return false;
600 }
601
602 if (!aDocument.RemoveSignature(nPosition))
603 {
604 SAL_WARN("xmlsecurity.helper", "failed to remove signature");
605 return false;
606 }
607
608 uno::Reference<io::XStream> xStream(xInputStream, uno::UNO_QUERY);
609 uno::Reference<io::XTruncate> xTruncate(xStream, uno::UNO_QUERY);
610 if (!xTruncate.is())
611 {
612 SAL_WARN("xmlsecurity.helper", "failed to truncate");
613 return false;
614 }
615 xTruncate->truncate();
616 std::unique_ptr<SvStream> pOutStream(utl::UcbStreamHelper::CreateStream(xStream, true));
617 if (!aDocument.Write(*pOutStream))
618 {
619 SAL_WARN("xmlsecurity.helper", "failed to write without signature");
620 return false;
621 }
622
623 return true;
624}
625
626/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Reference< XInputStream > xStream
ScriptDocument aDocument
sal_uInt64 BitmapChecksum
bool ReadAndVerifySignatureSvStream(SvStream &rStream)
css::uno::Reference< css::security::XCertificate > m_xCertificate
static bool RemoveSignature(const css::uno::Reference< css::io::XInputStream > &xInputStream, sal_uInt16 nPosition)
Remove the signature at nPosition (and all dependent signatures) from xInputStream.
bool ReadAndVerifySignature(const css::uno::Reference< css::io::XInputStream > &xInputStream)
void SetDescription(const OUString &rDescription)
Comment / reason to be used next time signing is performed.
SignatureInformations const & GetSignatureInformations() const
sal_Int32 GetNewSecurityId() const
Return the ID of the next created signature.
SignatureInformations m_aSignatureInfos
css::uno::Sequence< css::security::DocumentSignatureInformation > GetDocumentSignatureInformations(const css::uno::Reference< css::xml::crypto::XSecurityEnvironment > &xSecEnv) const
void SetX509Certificate(const css::uno::Reference< css::security::XCertificate > &xCertificate)
Certificate to be used next time signing is performed.
bool Sign(const css::uno::Reference< css::frame::XModel > &xModel, const css::uno::Reference< css::io::XInputStream > &xInputStream, bool bAdES)
Append a new signature at the end of xInputStream.
const void * GetData()
sal_uInt64 GetSize()
sal_uInt64 Tell() const
sal_uInt64 Seek(sal_uInt64 nPos)
std::size_t ReadBytes(void *pData, std::size_t nSize)
SvStream & WriteStream(SvStream &rStream)
static bool Verify(const std::vector< unsigned char > &aData, const bool bNonDetached, const std::vector< unsigned char > &aSignature, SignatureInformation &rInformation)
static std::unique_ptr< SvStream > CreateStream(const OUString &rFileName, StreamMode eOpenMode, css::uno::Reference< css::awt::XWindow > xParentWin=nullptr)
#define DBG_UNHANDLED_EXCEPTION(...)
DocumentType eType
sal_uInt16 nPos
#define SAL_WARN(area, stream)
css::uno::Sequence< css::beans::PropertyValue > InitPropertySequence(::std::initializer_list< ::std::pair< OUString, css::uno::Any > > vInit)
int i
PDFAnnotationSubType
HashMap_OWString_Interface aMap
::std::vector< SignatureInformation > SignatureInformations
css::xml::crypto::SecurityOperationStatus nStatus
X509CertInfo const * GetSigningCertificate() const
css::util::DateTime stDateTime
static std::shared_ptr< PDFium > & get()
Reference< XController > xController
Reference< XModel > xModel
bool operator==(const XclFontData &rLeft, const XclFontData &rRight)
sal_Int32 nLength