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 <config_features.h>
11 #include <config_gpgme.h>
13 #include <sal/config.h>
17 #include <test/bootstrapfixture.hxx>
18 #include <unotest/macros_test.hxx>
19 #include <test/xmltesttools.hxx>
21 #include <com/sun/star/beans/XPropertySet.hpp>
22 #include <com/sun/star/document/MacroExecMode.hpp>
23 #include <com/sun/star/embed/XStorage.hpp>
24 #include <com/sun/star/embed/XTransactedObject.hpp>
25 #include <com/sun/star/frame/Desktop.hpp>
26 #include <com/sun/star/frame/XStorable.hpp>
27 #include <com/sun/star/security/DocumentDigitalSignatures.hpp>
28 #include <com/sun/star/security/XDocumentDigitalSignatures.hpp>
29 #include <com/sun/star/xml/crypto/SEInitializer.hpp>
31 #include <comphelper/processfactory.hxx>
32 #include <comphelper/propertysequence.hxx>
33 #include <unotools/mediadescriptor.hxx>
34 #include <unotools/tempfile.hxx>
35 #include <unotools/ucbstreamhelper.hxx>
36 #include <comphelper/storagehelper.hxx>
37 #include <sfx2/sfxbasemodel.hxx>
38 #include <sfx2/objsh.hxx>
39 #include <osl/file.hxx>
40 #include <osl/process.h>
41 #include <osl/thread.hxx>
42 #include <comphelper/ofopxmlhelper.hxx>
43 #include <unotools/streamwrap.hxx>
45 #include <documentsignaturehelper.hxx>
46 #include <xmlsignaturehelper.hxx>
47 #include <documentsignaturemanager.hxx>
48 #include <certificate.hxx>
49 #include <xsecctl.hxx>
50 #include <sfx2/docfile.hxx>
51 #include <sfx2/docfilt.hxx>
52 #include <officecfg/Office/Common.hxx>
53 #include <comphelper/configuration.hxx>
55 using namespace com::sun::star
;
59 char const DATA_DIRECTORY
[] = "/xmlsecurity/qa/unit/signing/data/";
62 /// Testsuite for the document signing feature.
63 class SigningTest
: public test::BootstrapFixture
, public unotest::MacrosTest
, public XmlTestTools
66 uno::Reference
<uno::XComponentContext
> mxComponentContext
;
67 uno::Reference
<lang::XComponent
> mxComponent
;
68 uno::Reference
<xml::crypto::XSEInitializer
> mxSEInitializer
;
69 uno::Reference
<xml::crypto::XXMLSecurityContext
> mxSecurityContext
;
71 #if HAVE_GPGCONF_SOCKETDIR
72 OString m_gpgconfCommandPrefix
;
77 virtual void setUp() override
;
78 virtual void tearDown() override
;
79 void registerNamespaces(xmlXPathContextPtr
& pXmlXpathCtx
) override
;
82 void createDoc(const OUString
& rURL
);
83 void createCalc(const OUString
& rURL
);
84 uno::Reference
<security::XCertificate
>
85 getCertificate(DocumentSignatureManager
& rSignatureManager
,
86 svl::crypto::SignatureMethodAlgorithm eAlgo
);
87 #if HAVE_FEATURE_GPGVERIFY
88 SfxObjectShell
* assertDocument(const ::CppUnit::SourceLine aSrcLine
,
89 const OUString
& rFilterName
, const SignatureState nDocSign
,
90 const SignatureState nMacroSign
, const OUString
& sVersion
);
94 SigningTest::SigningTest() {}
96 void SigningTest::setUp()
98 test::BootstrapFixture::setUp();
100 OUString aSourceDir
= m_directories
.getURLFromSrc(DATA_DIRECTORY
);
102 = m_directories
.getURLFromWorkdir("CppunitTest/xmlsecurity_signing.test.user");
104 // Set up cert8.db in workdir/CppunitTest/
105 osl::File::copy(aSourceDir
+ "cert8.db", aTargetDir
+ "/cert8.db");
106 osl::File::copy(aSourceDir
+ "key3.db", aTargetDir
+ "/key3.db");
108 // Make gpg use our own defined setup & keys
109 osl::File::copy(aSourceDir
+ "pubring.gpg", aTargetDir
+ "/pubring.gpg");
110 osl::File::copy(aSourceDir
+ "random_seed", aTargetDir
+ "/random_seed");
111 osl::File::copy(aSourceDir
+ "secring.gpg", aTargetDir
+ "/secring.gpg");
112 osl::File::copy(aSourceDir
+ "trustdb.gpg", aTargetDir
+ "/trustdb.gpg");
114 OUString aTargetPath
;
115 osl::FileBase::getSystemPathFromFileURL(aTargetDir
, aTargetPath
);
117 OUString
mozCertVar("MOZILLA_CERTIFICATE_FOLDER");
118 osl_setEnvironment(mozCertVar
.pData
, aTargetPath
.pData
);
119 OUString
gpgHomeVar("GNUPGHOME");
120 osl_setEnvironment(gpgHomeVar
.pData
, aTargetPath
.pData
);
122 #if HAVE_GPGCONF_SOCKETDIR
123 auto const ldPath
= std::getenv("LIBO_LD_PATH");
124 m_gpgconfCommandPrefix
125 = ldPath
== nullptr ? OString() : OStringLiteral("LD_LIBRARY_PATH=") + ldPath
+ " ";
127 bool ok
= aTargetPath
.convertToString(&path
, osl_getThreadTextEncoding(),
128 RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR
129 | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
);
130 // if conversion fails, at least provide a best-effort conversion in the message here, for
132 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(aTargetPath
, RTL_TEXTENCODING_UTF8
).getStr(), ok
);
133 m_gpgconfCommandPrefix
+= "GNUPGHOME=" + path
+ " " GPGME_GPGCONF
;
134 // HAVE_GPGCONF_SOCKETDIR is only defined in configure.ac for Linux for now, so (a) std::system
135 // behavior will conform to POSIX (and the relevant env var to set is named LD_LIBRARY_PATH), and
136 // (b) gpgconf --create-socketdir should return zero:
137 OString cmd
= m_gpgconfCommandPrefix
+ " --create-socketdir";
138 int res
= std::system(cmd
.getStr());
139 CPPUNIT_ASSERT_EQUAL_MESSAGE(cmd
.getStr(), 0, res
);
142 // Initialize crypto after setting up the environment variables.
143 mxComponentContext
.set(comphelper::getComponentContext(getMultiServiceFactory()));
144 mxDesktop
.set(frame::Desktop::create(mxComponentContext
));
145 mxSEInitializer
= xml::crypto::SEInitializer::create(mxComponentContext
);
146 mxSecurityContext
= mxSEInitializer
->createSecurityContext(OUString());
149 void SigningTest::tearDown()
151 if (mxComponent
.is())
152 mxComponent
->dispose();
154 #if HAVE_GPGCONF_SOCKETDIR
155 // HAVE_GPGCONF_SOCKETDIR is only defined in configure.ac for Linux for now, so (a) std::system
156 // behavior will conform to POSIX, and (b) gpgconf --remove-socketdir should return zero:
157 OString cmd
= m_gpgconfCommandPrefix
+ " --remove-socketdir";
158 int res
= std::system(cmd
.getStr());
159 CPPUNIT_ASSERT_EQUAL_MESSAGE(cmd
.getStr(), 0, res
);
162 test::BootstrapFixture::tearDown();
165 void SigningTest::createDoc(const OUString
& rURL
)
167 if (mxComponent
.is())
168 mxComponent
->dispose();
170 mxComponent
= loadFromDesktop("private:factory/swriter", "com.sun.star.text.TextDocument");
172 mxComponent
= loadFromDesktop(rURL
, "com.sun.star.text.TextDocument");
175 void SigningTest::createCalc(const OUString
& rURL
)
177 if (mxComponent
.is())
178 mxComponent
->dispose();
181 = loadFromDesktop("private:factory/swriter", "com.sun.star.sheet.SpreadsheetDocument");
183 mxComponent
= loadFromDesktop(rURL
, "com.sun.star.sheet.SpreadsheetDocument");
186 uno::Reference
<security::XCertificate
>
187 SigningTest::getCertificate(DocumentSignatureManager
& rSignatureManager
,
188 svl::crypto::SignatureMethodAlgorithm eAlgo
)
190 uno::Reference
<xml::crypto::XSecurityEnvironment
> xSecurityEnvironment
191 = rSignatureManager
.getSecurityEnvironment();
192 const uno::Sequence
<uno::Reference
<security::XCertificate
>> aCertificates
193 = xSecurityEnvironment
->getPersonalCertificates();
195 for (const auto& xCertificate
: aCertificates
)
197 auto pCertificate
= dynamic_cast<xmlsecurity::Certificate
*>(xCertificate
.get());
198 CPPUNIT_ASSERT(pCertificate
);
199 if (pCertificate
->getSignatureMethodAlgorithm() == eAlgo
)
202 return uno::Reference
<security::XCertificate
>();
205 CPPUNIT_TEST_FIXTURE(SigningTest
, testDescription
)
207 // Create an empty document and store it to a tempfile, finally load it as a storage.
210 utl::TempFile aTempFile
;
211 aTempFile
.EnableKillingFile();
212 uno::Reference
<frame::XStorable
> xStorable(mxComponent
, uno::UNO_QUERY
);
213 utl::MediaDescriptor aMediaDescriptor
;
214 aMediaDescriptor
["FilterName"] <<= OUString("writer8");
215 xStorable
->storeAsURL(aTempFile
.GetURL(), aMediaDescriptor
.getAsConstPropertyValueList());
217 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
218 CPPUNIT_ASSERT(aManager
.init());
219 uno::Reference
<embed::XStorage
> xStorage
220 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
221 ZIP_STORAGE_FORMAT_STRING
, aTempFile
.GetURL(), embed::ElementModes::READWRITE
);
222 CPPUNIT_ASSERT(xStorage
.is());
223 aManager
.setStore(xStorage
);
224 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
226 // Then add a signature document.
227 uno::Reference
<security::XCertificate
> xCertificate
228 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::RSA
);
229 if (!xCertificate
.is())
231 OUString
aDescription("SigningTest::testDescription");
232 sal_Int32 nSecurityId
;
233 aManager
.add(xCertificate
, mxSecurityContext
, aDescription
, nSecurityId
, false);
235 // Read back the signature and make sure that the description survives the roundtrip.
236 aManager
.read(/*bUseTempStream=*/true);
237 std::vector
<SignatureInformation
>& rInformations
= aManager
.getCurrentSignatureInformations();
238 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), rInformations
.size());
239 CPPUNIT_ASSERT_EQUAL(aDescription
, rInformations
[0].ouDescription
);
242 CPPUNIT_TEST_FIXTURE(SigningTest
, testECDSA
)
244 // Create an empty document and store it to a tempfile, finally load it as a storage.
247 utl::TempFile aTempFile
;
248 aTempFile
.EnableKillingFile();
249 uno::Reference
<frame::XStorable
> xStorable(mxComponent
, uno::UNO_QUERY
);
250 utl::MediaDescriptor aMediaDescriptor
;
251 aMediaDescriptor
["FilterName"] <<= OUString("writer8");
252 xStorable
->storeAsURL(aTempFile
.GetURL(), aMediaDescriptor
.getAsConstPropertyValueList());
254 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
255 CPPUNIT_ASSERT(aManager
.init());
256 uno::Reference
<embed::XStorage
> xStorage
257 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
258 ZIP_STORAGE_FORMAT_STRING
, aTempFile
.GetURL(), embed::ElementModes::READWRITE
);
259 CPPUNIT_ASSERT(xStorage
.is());
260 aManager
.setStore(xStorage
);
261 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
263 // Then add a signature.
264 uno::Reference
<security::XCertificate
> xCertificate
265 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::ECDSA
);
266 if (!xCertificate
.is())
268 OUString aDescription
;
269 sal_Int32 nSecurityId
;
270 aManager
.add(xCertificate
, mxSecurityContext
, aDescription
, nSecurityId
, false);
272 // Read back the signature and make sure that it's valid.
273 aManager
.read(/*bUseTempStream=*/true);
274 std::vector
<SignatureInformation
>& rInformations
= aManager
.getCurrentSignatureInformations();
275 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), rInformations
.size());
276 // This was SecurityOperationStatus_UNKNOWN, signing with an ECDSA key was
278 CPPUNIT_ASSERT_EQUAL(css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED
,
279 rInformations
[0].nStatus
);
282 CPPUNIT_TEST_FIXTURE(SigningTest
, testECDSAOOXML
)
284 // Create an empty document and store it to a tempfile, finally load it as a storage.
287 utl::TempFile aTempFile
;
288 aTempFile
.EnableKillingFile();
289 uno::Reference
<frame::XStorable
> xStorable(mxComponent
, uno::UNO_QUERY
);
290 utl::MediaDescriptor aMediaDescriptor
;
291 aMediaDescriptor
["FilterName"] <<= OUString("MS Word 2007 XML");
292 xStorable
->storeAsURL(aTempFile
.GetURL(), aMediaDescriptor
.getAsConstPropertyValueList());
294 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
295 CPPUNIT_ASSERT(aManager
.init());
296 uno::Reference
<embed::XStorage
> xStorage
297 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
298 ZIP_STORAGE_FORMAT_STRING
, aTempFile
.GetURL(), embed::ElementModes::READWRITE
);
299 CPPUNIT_ASSERT(xStorage
.is());
300 aManager
.setStore(xStorage
);
301 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
303 // Then add a document signature.
304 uno::Reference
<security::XCertificate
> xCertificate
305 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::ECDSA
);
306 if (!xCertificate
.is())
308 OUString aDescription
;
309 sal_Int32 nSecurityId
;
310 aManager
.add(xCertificate
, mxSecurityContext
, aDescription
, nSecurityId
,
311 /*bAdESCompliant=*/false);
313 // Read back the signature and make sure that it's valid.
314 aManager
.read(/*bUseTempStream=*/true);
315 std::vector
<SignatureInformation
>& rInformations
= aManager
.getCurrentSignatureInformations();
316 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), rInformations
.size());
317 // This was SecurityOperationStatus_UNKNOWN, signing with an ECDSA key was
319 CPPUNIT_ASSERT_EQUAL(css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED
,
320 rInformations
[0].nStatus
);
323 CPPUNIT_TEST_FIXTURE(SigningTest
, testECDSAPDF
)
325 // Create an empty document and store it to a tempfile, finally load it as
329 utl::TempFile aTempFile
;
330 aTempFile
.EnableKillingFile();
331 uno::Reference
<frame::XStorable
> xStorable(mxComponent
, uno::UNO_QUERY
);
332 utl::MediaDescriptor aMediaDescriptor
;
333 aMediaDescriptor
["FilterName"] <<= OUString("writer_pdf_Export");
334 xStorable
->storeToURL(aTempFile
.GetURL(), aMediaDescriptor
.getAsConstPropertyValueList());
336 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
337 CPPUNIT_ASSERT(aManager
.init());
338 std::unique_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(
339 aTempFile
.GetURL(), StreamMode::READ
| StreamMode::WRITE
));
340 uno::Reference
<io::XStream
> xStream(new utl::OStreamWrapper(*pStream
));
341 CPPUNIT_ASSERT(xStream
.is());
342 aManager
.setSignatureStream(xStream
);
344 // Then add a document signature.
345 uno::Reference
<security::XCertificate
> xCertificate
346 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::ECDSA
);
347 if (!xCertificate
.is())
349 OUString aDescription
;
350 sal_Int32 nSecurityId
;
351 aManager
.add(xCertificate
, mxSecurityContext
, aDescription
, nSecurityId
,
352 /*bAdESCompliant=*/true);
354 // Read back the signature and make sure that it's valid.
355 aManager
.read(/*bUseTempStream=*/false);
356 std::vector
<SignatureInformation
>& rInformations
= aManager
.getCurrentSignatureInformations();
357 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), rInformations
.size());
358 // This was SecurityOperationStatus_UNKNOWN, signing with an ECDSA key was
360 CPPUNIT_ASSERT_EQUAL(css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED
,
361 rInformations
[0].nStatus
);
364 CPPUNIT_TEST_FIXTURE(SigningTest
, testOOXMLDescription
)
366 // Create an empty document and store it to a tempfile, finally load it as a storage.
369 utl::TempFile aTempFile
;
370 aTempFile
.EnableKillingFile();
371 uno::Reference
<frame::XStorable
> xStorable(mxComponent
, uno::UNO_QUERY
);
372 utl::MediaDescriptor aMediaDescriptor
;
373 aMediaDescriptor
["FilterName"] <<= OUString("MS Word 2007 XML");
374 xStorable
->storeAsURL(aTempFile
.GetURL(), aMediaDescriptor
.getAsConstPropertyValueList());
376 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
377 CPPUNIT_ASSERT(aManager
.init());
378 uno::Reference
<embed::XStorage
> xStorage
379 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
380 ZIP_STORAGE_FORMAT_STRING
, aTempFile
.GetURL(), embed::ElementModes::READWRITE
);
381 CPPUNIT_ASSERT(xStorage
.is());
382 aManager
.setStore(xStorage
);
383 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
385 // Then add a document signature.
386 uno::Reference
<security::XCertificate
> xCertificate
387 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::RSA
);
388 if (!xCertificate
.is())
390 OUString
aDescription("SigningTest::testDescription");
391 sal_Int32 nSecurityId
;
392 aManager
.add(xCertificate
, mxSecurityContext
, aDescription
, nSecurityId
, false);
394 // Read back the signature and make sure that the description survives the roundtrip.
395 aManager
.read(/*bUseTempStream=*/true);
396 std::vector
<SignatureInformation
>& rInformations
= aManager
.getCurrentSignatureInformations();
397 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), rInformations
.size());
398 CPPUNIT_ASSERT_EQUAL(aDescription
, rInformations
[0].ouDescription
);
401 /// Test appending a new signature next to an existing one.
402 CPPUNIT_TEST_FIXTURE(SigningTest
, testOOXMLAppend
)
404 // Copy the test document to a temporary file, as it'll be modified.
405 utl::TempFile aTempFile
;
406 aTempFile
.EnableKillingFile();
407 OUString aURL
= aTempFile
.GetURL();
408 CPPUNIT_ASSERT_EQUAL(
409 osl::File::RC::E_None
,
410 osl::File::copy(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "partial.docx", aURL
));
411 // Load the test document as a storage and read its single signature.
412 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
413 CPPUNIT_ASSERT(aManager
.init());
414 uno::Reference
<embed::XStorage
> xStorage
415 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(ZIP_STORAGE_FORMAT_STRING
, aURL
,
416 embed::ElementModes::READWRITE
);
417 CPPUNIT_ASSERT(xStorage
.is());
418 aManager
.setStore(xStorage
);
419 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
420 aManager
.read(/*bUseTempStream=*/false);
421 std::vector
<SignatureInformation
>& rInformations
= aManager
.getCurrentSignatureInformations();
422 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), rInformations
.size());
424 // Then add a second document signature.
425 uno::Reference
<security::XCertificate
> xCertificate
426 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::RSA
);
427 if (!xCertificate
.is())
429 sal_Int32 nSecurityId
;
430 aManager
.add(xCertificate
, mxSecurityContext
, OUString(), nSecurityId
, false);
432 // Read back the signatures and make sure that we have the expected amount.
433 aManager
.read(/*bUseTempStream=*/true);
434 // This was 1: the original signature was lost.
435 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(2), rInformations
.size());
438 /// Test removing a signature from existing ones.
439 CPPUNIT_TEST_FIXTURE(SigningTest
, testOOXMLRemove
)
441 // Load the test document as a storage and read its signatures: purpose1 and purpose2.
442 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
443 CPPUNIT_ASSERT(aManager
.init());
444 utl::TempFile aTempFile
;
445 aTempFile
.EnableKillingFile();
446 OUString aURL
= aTempFile
.GetURL();
447 CPPUNIT_ASSERT_EQUAL(
448 osl::File::RC::E_None
,
449 osl::File::copy(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "multi.docx", aURL
));
450 uno::Reference
<embed::XStorage
> xStorage
451 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(ZIP_STORAGE_FORMAT_STRING
, aURL
,
452 embed::ElementModes::READWRITE
);
453 CPPUNIT_ASSERT(xStorage
.is());
454 aManager
.setStore(xStorage
);
455 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
456 aManager
.read(/*bUseTempStream=*/false);
457 std::vector
<SignatureInformation
>& rInformations
= aManager
.getCurrentSignatureInformations();
458 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(2), rInformations
.size());
460 // Then remove the last added signature.
461 uno::Reference
<security::XCertificate
> xCertificate
462 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::RSA
);
463 if (!xCertificate
.is())
467 // Read back the signatures and make sure that only purpose1 is left.
468 aManager
.read(/*bUseTempStream=*/true);
469 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), rInformations
.size());
470 CPPUNIT_ASSERT_EQUAL(OUString("purpose1"), rInformations
[0].ouDescription
);
473 /// Test removing all signatures from a document.
474 CPPUNIT_TEST_FIXTURE(SigningTest
, testOOXMLRemoveAll
)
476 // Copy the test document to a temporary file, as it'll be modified.
477 utl::TempFile aTempFile
;
478 aTempFile
.EnableKillingFile();
479 OUString aURL
= aTempFile
.GetURL();
480 CPPUNIT_ASSERT_EQUAL(
481 osl::File::RC::E_None
,
482 osl::File::copy(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "partial.docx", aURL
));
483 // Load the test document as a storage and read its single signature.
484 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
485 CPPUNIT_ASSERT(aManager
.init());
486 uno::Reference
<embed::XStorage
> xStorage
487 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(ZIP_STORAGE_FORMAT_STRING
, aURL
,
488 embed::ElementModes::READWRITE
);
489 CPPUNIT_ASSERT(xStorage
.is());
490 aManager
.setStore(xStorage
);
491 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
492 aManager
.read(/*bUseTempStream=*/false);
493 std::vector
<SignatureInformation
>& rInformations
= aManager
.getCurrentSignatureInformations();
494 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), rInformations
.size());
496 // Then remove the only signature in the document.
497 uno::Reference
<security::XCertificate
> xCertificate
498 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::RSA
);
499 if (!xCertificate
.is())
502 aManager
.read(/*bUseTempStream=*/true);
503 aManager
.write(/*bXAdESCompliantIfODF=*/false);
505 // Make sure that the signature count is zero and the whole signature storage is removed completely.
506 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(0), rInformations
.size());
507 CPPUNIT_ASSERT(!xStorage
->hasByName("_xmlsignatures"));
509 // And that content types no longer contains signature types.
510 uno::Reference
<io::XStream
> xStream
511 = xStorage
->openStreamElement("[Content_Types].xml", embed::ElementModes::READWRITE
);
512 uno::Reference
<io::XInputStream
> xInputStream
= xStream
->getInputStream();
513 uno::Sequence
<uno::Sequence
<beans::StringPair
>> aContentTypeInfo
514 = comphelper::OFOPXMLHelper::ReadContentTypeSequence(xInputStream
, mxComponentContext
);
515 uno::Sequence
<beans::StringPair
>& rOverrides
= aContentTypeInfo
[1];
517 std::none_of(rOverrides
.begin(), rOverrides
.end(), [](const beans::StringPair
& rPair
) {
518 return rPair
.First
.startsWith("/_xmlsignatures/sig");
522 /// Test a typical ODF where all streams are signed.
523 CPPUNIT_TEST_FIXTURE(SigningTest
, testODFGood
)
525 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "good.odt");
526 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
527 CPPUNIT_ASSERT(pBaseModel
);
528 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
529 CPPUNIT_ASSERT(pObjectShell
);
530 // We expect NOTVALIDATED in case the root CA is not imported on the system, and OK otherwise, so accept both.
531 SignatureState nActual
= pObjectShell
->GetDocumentSignatureState();
532 CPPUNIT_ASSERT_MESSAGE(
533 (OString::number(o3tl::underlyingEnumValue(nActual
)).getStr()),
534 (nActual
== SignatureState::NOTVALIDATED
|| nActual
== SignatureState::OK
));
537 /// Test a typical broken ODF signature where one stream is corrupted.
538 CPPUNIT_TEST_FIXTURE(SigningTest
, testODFBroken
)
540 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "bad.odt");
541 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
542 CPPUNIT_ASSERT(pBaseModel
);
543 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
544 CPPUNIT_ASSERT(pObjectShell
);
545 CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN
),
546 static_cast<int>(pObjectShell
->GetDocumentSignatureState()));
549 // Document has a signature stream, but no actual signatures.
550 CPPUNIT_TEST_FIXTURE(SigningTest
, testODFNo
)
552 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "no.odt");
553 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
554 CPPUNIT_ASSERT(pBaseModel
);
555 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
556 CPPUNIT_ASSERT(pObjectShell
);
557 CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::NOSIGNATURES
),
558 static_cast<int>(pObjectShell
->GetDocumentSignatureState()));
561 /// Test a typical OOXML where a number of (but not all) streams are signed.
562 CPPUNIT_TEST_FIXTURE(SigningTest
, testOOXMLPartial
)
564 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "partial.docx");
565 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
566 CPPUNIT_ASSERT(pBaseModel
);
567 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
568 CPPUNIT_ASSERT(pObjectShell
);
569 // This was SignatureState::BROKEN due to missing RelationshipTransform and SHA-256 support.
570 // We expect NOTVALIDATED_PARTIAL_OK in case the root CA is not imported on the system, and PARTIAL_OK otherwise, so accept both.
571 // But reject NOTVALIDATED, hiding incompleteness is not OK.
572 SignatureState nActual
= pObjectShell
->GetDocumentSignatureState();
573 CPPUNIT_ASSERT_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual
)).getStr()),
574 (nActual
== SignatureState::NOTVALIDATED_PARTIAL_OK
575 || nActual
== SignatureState::PARTIAL_OK
));
578 /// Test a typical broken OOXML signature where one stream is corrupted.
579 CPPUNIT_TEST_FIXTURE(SigningTest
, testOOXMLBroken
)
581 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "bad.docx");
582 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
583 CPPUNIT_ASSERT(pBaseModel
);
584 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
585 CPPUNIT_ASSERT(pObjectShell
);
586 // This was SignatureState::NOTVALIDATED/PARTIAL_OK as we did not validate manifest references.
587 CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN
),
588 static_cast<int>(pObjectShell
->GetDocumentSignatureState()));
591 #if HAVE_FEATURE_PDFIMPORT
593 /// Test a typical PDF where the signature is good.
594 CPPUNIT_TEST_FIXTURE(SigningTest
, testPDFGood
)
596 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "good.pdf");
597 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
598 CPPUNIT_ASSERT(pBaseModel
);
599 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
600 CPPUNIT_ASSERT(pObjectShell
);
601 // We expect NOTVALIDATED in case the root CA is not imported on the system, and OK otherwise, so accept both.
602 SignatureState nActual
= pObjectShell
->GetDocumentSignatureState();
603 CPPUNIT_ASSERT_MESSAGE(
604 (OString::number(o3tl::underlyingEnumValue(nActual
)).getStr()),
605 (nActual
== SignatureState::NOTVALIDATED
|| nActual
== SignatureState::OK
));
608 /// Test a typical PDF where the signature is bad.
609 CPPUNIT_TEST_FIXTURE(SigningTest
, testPDFBad
)
611 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "bad.pdf");
612 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
613 CPPUNIT_ASSERT(pBaseModel
);
614 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
615 CPPUNIT_ASSERT(pObjectShell
);
616 CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN
),
617 static_cast<int>(pObjectShell
->GetDocumentSignatureState()));
620 /// Test a typical PDF which is not signed.
621 CPPUNIT_TEST_FIXTURE(SigningTest
, testPDFNo
)
623 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "no.pdf");
624 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
625 CPPUNIT_ASSERT(pBaseModel
);
626 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
627 CPPUNIT_ASSERT(pObjectShell
);
628 CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::NOSIGNATURES
),
629 static_cast<int>(pObjectShell
->GetDocumentSignatureState()));
634 CPPUNIT_TEST_FIXTURE(SigningTest
, test96097Calc
)
636 createCalc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "tdf96097.ods");
637 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
638 CPPUNIT_ASSERT_MESSAGE("Failed to access document base model", pBaseModel
);
640 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
641 CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pObjectShell
);
643 SignatureState nActual
= pObjectShell
->GetScriptingSignatureState();
644 CPPUNIT_ASSERT_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual
)).getStr()),
645 (nActual
== SignatureState::OK
|| nActual
== SignatureState::NOTVALIDATED
646 || nActual
== SignatureState::INVALID
));
648 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY_THROW
);
651 utl::TempFile aTempFileSaveCopy
;
652 aTempFileSaveCopy
.EnableKillingFile();
653 uno::Sequence
<beans::PropertyValue
> descSaveACopy(comphelper::InitPropertySequence(
654 { { "SaveACopy", uno::Any(true) }, { "FilterName", uno::Any(OUString("calc8")) } }));
655 xDocStorable
->storeToURL(aTempFileSaveCopy
.GetURL(), descSaveACopy
);
660 utl::TempFile aTempFileSaveAs
;
661 aTempFileSaveAs
.EnableKillingFile();
662 uno::Sequence
<beans::PropertyValue
> descSaveAs(
663 comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("calc8")) } }));
664 xDocStorable
->storeAsURL(aTempFileSaveAs
.GetURL(), descSaveAs
);
668 CPPUNIT_FAIL("Fail to save as the document");
672 CPPUNIT_TEST_FIXTURE(SigningTest
, test96097Doc
)
674 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "tdf96097.odt");
675 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
676 CPPUNIT_ASSERT(pBaseModel
);
677 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
678 CPPUNIT_ASSERT(pObjectShell
);
680 SignatureState nActual
= pObjectShell
->GetScriptingSignatureState();
681 CPPUNIT_ASSERT_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual
)).getStr()),
682 (nActual
== SignatureState::OK
|| nActual
== SignatureState::NOTVALIDATED
683 || nActual
== SignatureState::INVALID
));
685 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY_THROW
);
688 utl::TempFile aTempFileSaveCopy
;
689 aTempFileSaveCopy
.EnableKillingFile();
690 uno::Sequence
<beans::PropertyValue
> descSaveACopy(comphelper::InitPropertySequence(
691 { { "SaveACopy", uno::Any(true) }, { "FilterName", uno::Any(OUString("writer8")) } }));
692 xDocStorable
->storeToURL(aTempFileSaveCopy
.GetURL(), descSaveACopy
);
697 utl::TempFile aTempFileSaveAs
;
698 aTempFileSaveAs
.EnableKillingFile();
699 uno::Sequence
<beans::PropertyValue
> descSaveAs(
700 comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer8")) } }));
701 xDocStorable
->storeAsURL(aTempFileSaveAs
.GetURL(), descSaveAs
);
705 CPPUNIT_FAIL("Fail to save as the document");
709 CPPUNIT_TEST_FIXTURE(SigningTest
, testXAdESNotype
)
711 // Create a working copy.
712 utl::TempFile aTempFile
;
713 aTempFile
.EnableKillingFile();
714 OUString aURL
= aTempFile
.GetURL();
715 CPPUNIT_ASSERT_EQUAL(
716 osl::File::RC::E_None
,
717 osl::File::copy(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "notype-xades.odt", aURL
));
719 // Read existing signature.
720 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
721 CPPUNIT_ASSERT(aManager
.init());
722 uno::Reference
<embed::XStorage
> xStorage
723 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
724 ZIP_STORAGE_FORMAT_STRING
, aTempFile
.GetURL(), embed::ElementModes::READWRITE
);
725 CPPUNIT_ASSERT(xStorage
.is());
726 aManager
.setStore(xStorage
);
727 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
728 aManager
.read(/*bUseTempStream=*/false);
730 // Create a new signature.
731 uno::Reference
<security::XCertificate
> xCertificate
732 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::RSA
);
733 if (!xCertificate
.is())
735 sal_Int32 nSecurityId
;
736 aManager
.add(xCertificate
, mxSecurityContext
, /*rDescription=*/OUString(), nSecurityId
,
737 /*bAdESCompliant=*/true);
740 aManager
.read(/*bUseTempStream=*/true);
741 aManager
.write(/*bXAdESCompliantIfODF=*/true);
742 uno::Reference
<embed::XTransactedObject
> xTransactedObject(xStorage
, uno::UNO_QUERY
);
743 xTransactedObject
->commit();
745 // Parse the resulting XML.
746 uno::Reference
<embed::XStorage
> xMetaInf
747 = xStorage
->openStorageElement("META-INF", embed::ElementModes::READ
);
748 uno::Reference
<io::XInputStream
> xInputStream(
749 xMetaInf
->openStreamElement("documentsignatures.xml", embed::ElementModes::READ
),
751 std::shared_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
752 xmlDocPtr pXmlDoc
= parseXmlStream(pStream
.get());
754 // Without the accompanying fix in place, this test would have failed with "unexpected 'Type'
755 // attribute", i.e. the signature without such an attribute was not preserved correctly.
756 assertXPathNoAttribute(pXmlDoc
,
757 "/odfds:document-signatures/dsig:Signature[1]/dsig:SignedInfo/"
758 "dsig:Reference[@URI='#idSignedProperties']",
761 // New signature always has the Type attribute.
763 "/odfds:document-signatures/dsig:Signature[2]/dsig:SignedInfo/"
764 "dsig:Reference[@URI='#idSignedProperties']",
765 "Type", "http://uri.etsi.org/01903#SignedProperties");
768 /// Creates a XAdES signature from scratch.
769 CPPUNIT_TEST_FIXTURE(SigningTest
, testXAdES
)
771 // Create an empty document, store it to a tempfile and load it as a storage.
772 createDoc(OUString());
774 utl::TempFile aTempFile
;
775 aTempFile
.EnableKillingFile();
776 uno::Reference
<frame::XStorable
> xStorable(mxComponent
, uno::UNO_QUERY
);
777 utl::MediaDescriptor aMediaDescriptor
;
778 aMediaDescriptor
["FilterName"] <<= OUString("writer8");
779 xStorable
->storeAsURL(aTempFile
.GetURL(), aMediaDescriptor
.getAsConstPropertyValueList());
781 DocumentSignatureManager
aManager(mxComponentContext
, DocumentSignatureMode::Content
);
782 CPPUNIT_ASSERT(aManager
.init());
783 uno::Reference
<embed::XStorage
> xStorage
784 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
785 ZIP_STORAGE_FORMAT_STRING
, aTempFile
.GetURL(), embed::ElementModes::READWRITE
);
786 CPPUNIT_ASSERT(xStorage
.is());
787 aManager
.setStore(xStorage
);
788 aManager
.getSignatureHelper().SetStorage(xStorage
, "1.2");
790 // Create a signature.
791 uno::Reference
<security::XCertificate
> xCertificate
792 = getCertificate(aManager
, svl::crypto::SignatureMethodAlgorithm::RSA
);
793 if (!xCertificate
.is())
795 sal_Int32 nSecurityId
;
796 aManager
.add(xCertificate
, mxSecurityContext
, /*rDescription=*/OUString(), nSecurityId
,
797 /*bAdESCompliant=*/true);
800 aManager
.read(/*bUseTempStream=*/true);
801 aManager
.write(/*bXAdESCompliantIfODF=*/true);
802 uno::Reference
<embed::XTransactedObject
> xTransactedObject(xStorage
, uno::UNO_QUERY
);
803 xTransactedObject
->commit();
805 // Parse the resulting XML.
806 uno::Reference
<embed::XStorage
> xMetaInf
807 = xStorage
->openStorageElement("META-INF", embed::ElementModes::READ
);
808 uno::Reference
<io::XInputStream
> xInputStream(
809 xMetaInf
->openStreamElement("documentsignatures.xml", embed::ElementModes::READ
),
811 std::shared_ptr
<SvStream
> pStream(utl::UcbStreamHelper::CreateStream(xInputStream
, true));
812 xmlDocPtr pXmlDoc
= parseXmlStream(pStream
.get());
814 // Assert that the digest algorithm is SHA-256 in the bAdESCompliant case, not SHA-1.
816 "/odfds:document-signatures/dsig:Signature/dsig:SignedInfo/"
817 "dsig:Reference[@URI='content.xml']/dsig:DigestMethod",
818 "Algorithm", ALGO_XMLDSIGSHA256
);
820 // Assert that the digest of the signing certificate is included.
821 assertXPath(pXmlDoc
, "//xd:CertDigest", 1);
823 // Assert that the Type attribute on the idSignedProperties reference is
826 "/odfds:document-signatures/dsig:Signature/dsig:SignedInfo/"
827 "dsig:Reference[@URI='#idSignedProperties']",
828 "Type", "http://uri.etsi.org/01903#SignedProperties");
831 /// Works with an existing good XAdES signature.
832 CPPUNIT_TEST_FIXTURE(SigningTest
, testXAdESGood
)
834 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "good-xades.odt");
835 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
836 CPPUNIT_ASSERT(pBaseModel
);
837 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
838 CPPUNIT_ASSERT(pObjectShell
);
839 // We expect NOTVALIDATED in case the root CA is not imported on the system, and OK otherwise, so accept both.
840 SignatureState nActual
= pObjectShell
->GetDocumentSignatureState();
841 CPPUNIT_ASSERT_MESSAGE(
842 (OString::number(o3tl::underlyingEnumValue(nActual
)).getStr()),
843 (nActual
== SignatureState::NOTVALIDATED
|| nActual
== SignatureState::OK
));
846 /// Test importing of signature line
847 CPPUNIT_TEST_FIXTURE(SigningTest
, testSignatureLineOOXML
)
849 // Given: A document (docx) with a signature line and a valid signature
850 uno::Reference
<security::XDocumentDigitalSignatures
> xSignatures(
851 security::DocumentDigitalSignatures::createWithVersion(
852 comphelper::getProcessComponentContext(), "1.2"));
854 uno::Reference
<embed::XStorage
> xStorage
855 = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
856 ZIP_STORAGE_FORMAT_STRING
,
857 m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "signatureline.docx",
858 embed::ElementModes::READ
);
859 CPPUNIT_ASSERT(xStorage
.is());
861 uno::Sequence
<security::DocumentSignatureInformation
> xSignatureInfo
862 = xSignatures
->verifyScriptingContentSignatures(xStorage
,
863 uno::Reference
<io::XInputStream
>());
865 // The signature should have a valid signature, and signature line with two valid images
866 CPPUNIT_ASSERT(xSignatureInfo
[0].SignatureIsValid
);
867 CPPUNIT_ASSERT_EQUAL(OUString("{DEE0514B-13E8-4674-A831-46E3CDB18BB4}"),
868 xSignatureInfo
[0].SignatureLineId
);
869 CPPUNIT_ASSERT(xSignatureInfo
[0].ValidSignatureLineImage
.is());
870 CPPUNIT_ASSERT(xSignatureInfo
[0].InvalidSignatureLineImage
.is());
873 CPPUNIT_TEST_FIXTURE(SigningTest
, testSignatureLineODF
)
875 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "signatureline.odt");
876 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
877 CPPUNIT_ASSERT(pBaseModel
);
878 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
879 CPPUNIT_ASSERT(pObjectShell
);
881 uno::Sequence
<security::DocumentSignatureInformation
> xSignatureInfo
882 = pObjectShell
->GetDocumentSignatureInformation(false);
884 CPPUNIT_ASSERT(xSignatureInfo
[0].SignatureIsValid
);
885 CPPUNIT_ASSERT_EQUAL(OUString("{41CF56EE-331B-4125-97D8-2F5669DD3AAC}"),
886 xSignatureInfo
[0].SignatureLineId
);
887 CPPUNIT_ASSERT(xSignatureInfo
[0].ValidSignatureLineImage
.is());
888 CPPUNIT_ASSERT(xSignatureInfo
[0].InvalidSignatureLineImage
.is());
891 #if HAVE_FEATURE_GPGVERIFY
892 /// Test a typical ODF where all streams are GPG-signed.
893 CPPUNIT_TEST_FIXTURE(SigningTest
, testODFGoodGPG
)
895 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "goodGPG.odt");
896 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
897 CPPUNIT_ASSERT(pBaseModel
);
898 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
899 CPPUNIT_ASSERT(pObjectShell
);
900 // Our local gpg config fully trusts the signing cert, so in
901 // contrast to the X509 test we can fail on NOTVALIDATED here
902 SignatureState nActual
= pObjectShell
->GetDocumentSignatureState();
903 CPPUNIT_ASSERT_EQUAL_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual
)).getStr()),
904 SignatureState::OK
, nActual
);
907 /// Test a typical ODF where all streams are GPG-signed, but we don't trust the signature.
908 CPPUNIT_TEST_FIXTURE(SigningTest
, testODFUntrustedGoodGPG
)
910 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "untrustedGoodGPG.odt");
911 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
912 CPPUNIT_ASSERT(pBaseModel
);
913 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
914 CPPUNIT_ASSERT(pObjectShell
);
915 // Our local gpg config does _not_ trust the signing cert, so in
916 // contrast to the X509 test we can fail everything but
918 SignatureState nActual
= pObjectShell
->GetDocumentSignatureState();
919 CPPUNIT_ASSERT_EQUAL_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual
)).getStr()),
920 SignatureState::NOTVALIDATED
, nActual
);
923 /// Test a typical broken ODF signature where one stream is corrupted.
924 CPPUNIT_TEST_FIXTURE(SigningTest
, testODFBrokenStreamGPG
)
926 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "badStreamGPG.odt");
927 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
928 CPPUNIT_ASSERT(pBaseModel
);
929 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
930 CPPUNIT_ASSERT(pObjectShell
);
931 CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN
),
932 static_cast<int>(pObjectShell
->GetDocumentSignatureState()));
935 /// Test a typical broken ODF signature where the XML dsig hash is corrupted.
936 CPPUNIT_TEST_FIXTURE(SigningTest
, testODFBrokenDsigGPG
)
938 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "badDsigGPG.odt");
939 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
940 CPPUNIT_ASSERT(pBaseModel
);
941 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
942 CPPUNIT_ASSERT(pObjectShell
);
943 CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN
),
944 static_cast<int>(pObjectShell
->GetDocumentSignatureState()));
947 #if HAVE_GPGCONF_SOCKETDIR
949 /// Test loading an encrypted ODF document
950 CPPUNIT_TEST_FIXTURE(SigningTest
, testODFEncryptedGPG
)
952 // ODF1.2 + loext flavour
953 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "encryptedGPG.odt");
954 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
955 CPPUNIT_ASSERT(pBaseModel
);
956 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
957 CPPUNIT_ASSERT(pObjectShell
);
960 createDoc(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "encryptedGPG_odf13.odt");
961 pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
962 CPPUNIT_ASSERT(pBaseModel
);
963 pObjectShell
= pBaseModel
->GetObjectShell();
964 CPPUNIT_ASSERT(pObjectShell
);
969 SfxObjectShell
* SigningTest::assertDocument(const ::CppUnit::SourceLine aSrcLine
,
970 const OUString
& rFilterName
,
971 const SignatureState nDocSign
,
972 const SignatureState nMacroSign
,
973 const OUString
& sVersion
)
975 std::string sPos
= aSrcLine
.fileName() + ":" + OString::number(aSrcLine
.lineNumber()).getStr();
977 SfxBaseModel
* pBaseModel
= dynamic_cast<SfxBaseModel
*>(mxComponent
.get());
978 CPPUNIT_ASSERT_MESSAGE(sPos
, pBaseModel
);
979 SfxObjectShell
* pObjectShell
= pBaseModel
->GetObjectShell();
980 CPPUNIT_ASSERT_MESSAGE(sPos
, pObjectShell
);
982 CPPUNIT_ASSERT_EQUAL_MESSAGE(sPos
, rFilterName
,
983 pObjectShell
->GetMedium()->GetFilter()->GetFilterName());
984 SignatureState nActual
= pObjectShell
->GetDocumentSignatureState();
985 CPPUNIT_ASSERT_EQUAL_MESSAGE(sPos
, nDocSign
, nActual
);
986 nActual
= pObjectShell
->GetScriptingSignatureState();
987 CPPUNIT_ASSERT_EQUAL_MESSAGE(sPos
, nMacroSign
, nActual
);
989 OUString aODFVersion
;
990 uno::Reference
<beans::XPropertySet
> xPropSet(pObjectShell
->GetStorage(), uno::UNO_QUERY_THROW
);
991 xPropSet
->getPropertyValue("Version") >>= aODFVersion
;
992 CPPUNIT_ASSERT_EQUAL(sVersion
, aODFVersion
);
997 /// Test if a macro signature from a OTT 1.2 template is preserved for ODT 1.2
998 CPPUNIT_TEST_FIXTURE(SigningTest
, testPreserveMacroTemplateSignature12_ODF
)
1000 const OUString
aURL(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "tdf42316_odt12.ott");
1001 const OUString sLoadMessage
= "loading failed: " + aURL
;
1003 // load the template as-is to validate signatures
1004 mxComponent
= loadFromDesktop(
1005 aURL
, OUString(), comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1006 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1009 // we are a template, and have a valid document and macro signature
1010 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::OK
, SignatureState::OK
,
1013 // create new document from template
1014 // we can't use createDoc / MacrosTest::loadFromDesktop, because ALWAYS_EXECUTE_NO_WARN
1015 // won't verify the signature for templates, so the resulting document won't be able to
1016 // preserve the templates signature.
1017 mxComponent
->dispose();
1018 mxComponent
= mxDesktop
->loadComponentFromURL(
1019 aURL
, "_default", 0,
1020 comphelper::InitPropertySequence(
1021 { { "MacroExecutionMode",
1022 uno::Any(document::MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN
) } }));
1023 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1026 // we are somehow a template (?), and have just a valid macro signature
1027 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1028 SignatureState::OK
, ODFVER_012_TEXT
);
1030 // save as new ODT document
1031 utl::TempFile aTempFileSaveAsODT
;
1032 aTempFileSaveAsODT
.EnableKillingFile();
1035 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY
);
1036 uno::Sequence
<beans::PropertyValue
> descSaveAs(
1037 comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer8")) } }));
1038 xDocStorable
->storeAsURL(aTempFileSaveAsODT
.GetURL(), descSaveAs
);
1042 CPPUNIT_FAIL("Failed to save ODT document");
1045 // save as new OTT template
1046 utl::TempFile aTempFileSaveAsOTT
;
1047 aTempFileSaveAsOTT
.EnableKillingFile();
1050 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY
);
1051 uno::Sequence
<beans::PropertyValue
> descSaveAs(comphelper::InitPropertySequence(
1052 { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1053 xDocStorable
->storeAsURL(aTempFileSaveAsOTT
.GetURL(), descSaveAs
);
1057 CPPUNIT_FAIL("Failed to save OTT template");
1060 // load the saved OTT template as-is to validate signatures
1061 mxComponent
->dispose();
1063 = loadFromDesktop(aTempFileSaveAsOTT
.GetURL(), OUString(),
1064 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1065 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1068 // the loaded document is a OTT with a valid macro signature
1069 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1070 SignatureState::OK
, ODFVER_012_TEXT
);
1072 // load saved ODT document
1073 createDoc(aTempFileSaveAsODT
.GetURL());
1075 // the loaded document is a ODT with a macro signature
1076 assertDocument(CPPUNIT_SOURCELINE(), "writer8", SignatureState::NOSIGNATURES
,
1077 SignatureState::OK
, ODFVER_012_TEXT
);
1079 // save as new OTT template
1080 utl::TempFile aTempFileSaveAsODT_OTT
;
1081 aTempFileSaveAsODT_OTT
.EnableKillingFile();
1084 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY
);
1085 uno::Sequence
<beans::PropertyValue
> descSaveAs(comphelper::InitPropertySequence(
1086 { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1087 xDocStorable
->storeAsURL(aTempFileSaveAsODT_OTT
.GetURL(), descSaveAs
);
1091 CPPUNIT_FAIL("Failed to save OTT template");
1094 // load the template as-is to validate signatures
1095 mxComponent
->dispose();
1097 = loadFromDesktop(aTempFileSaveAsODT_OTT
.GetURL(), OUString(),
1098 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1099 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1102 // the loaded document is a OTT with a valid macro signature
1103 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1104 SignatureState::OK
, ODFVER_012_TEXT
);
1107 /// Test if a macro signature from an OTT 1.0 is dropped for ODT 1.2
1108 CPPUNIT_TEST_FIXTURE(SigningTest
, testDropMacroTemplateSignature
)
1110 const OUString
aURL(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "tdf42316.ott");
1111 const OUString sLoadMessage
= "loading failed: " + aURL
;
1113 // load the template as-is to validate signatures
1114 mxComponent
= loadFromDesktop(
1115 aURL
, OUString(), comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1116 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1119 // we are a template, and have a non-invalid macro signature
1120 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1121 SignatureState::NOTVALIDATED
, OUString());
1123 // create new document from template
1124 // we can't use createDoc / MacrosTest::loadFromDesktop, because ALWAYS_EXECUTE_NO_WARN
1125 // won't verify the signature for templates, so the resulting document won't be able to
1126 // preserve the templates signature.
1127 mxComponent
->dispose();
1128 mxComponent
= mxDesktop
->loadComponentFromURL(
1129 aURL
, "_default", 0,
1130 comphelper::InitPropertySequence(
1131 { { "MacroExecutionMode",
1132 uno::Any(document::MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN
) } }));
1133 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1136 // we are somehow a template (?), and have just a valid macro signature
1137 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1138 SignatureState::NOTVALIDATED
, OUString());
1140 // save as new ODT document
1141 utl::TempFile aTempFileSaveAs
;
1142 aTempFileSaveAs
.EnableKillingFile();
1145 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY
);
1146 uno::Sequence
<beans::PropertyValue
> descSaveAs(
1147 comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer8")) } }));
1148 xDocStorable
->storeAsURL(aTempFileSaveAs
.GetURL(), descSaveAs
);
1152 CPPUNIT_FAIL("Failed to save ODT document");
1155 // load saved document
1156 createDoc(aTempFileSaveAs
.GetURL());
1158 // the loaded document is a 1.2 ODT without any signatures
1159 assertDocument(CPPUNIT_SOURCELINE(), "writer8", SignatureState::NOSIGNATURES
,
1160 SignatureState::NOSIGNATURES
, ODFVER_012_TEXT
);
1162 // load the template as-is to validate signatures
1163 mxComponent
->dispose();
1164 mxComponent
= loadFromDesktop(
1165 aURL
, OUString(), comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1166 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1169 // we are a template, and have a non-invalid macro signature
1170 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1171 SignatureState::NOTVALIDATED
, OUString());
1173 // save as new OTT template
1174 utl::TempFile aTempFileSaveAsOTT
;
1175 aTempFileSaveAsOTT
.EnableKillingFile();
1178 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY
);
1179 uno::Sequence
<beans::PropertyValue
> descSaveAs(comphelper::InitPropertySequence(
1180 { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1181 xDocStorable
->storeAsURL(aTempFileSaveAsOTT
.GetURL(), descSaveAs
);
1185 CPPUNIT_FAIL("Failed to save OTT template");
1188 // load the template as-is to validate signatures
1189 mxComponent
->dispose();
1191 = loadFromDesktop(aTempFileSaveAsOTT
.GetURL(), OUString(),
1192 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1193 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1196 // the loaded document is a 1.2 OTT without any signatures
1197 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1198 SignatureState::NOSIGNATURES
, ODFVER_012_TEXT
);
1204 std::function
<void()> m_Func
;
1207 Resetter(std::function
<void()> const& rFunc
)
1217 catch (...) // has to be reliable
1219 fprintf(stderr
, "resetter failed with exception\n");
1225 /// Test if a macro signature from a OTT 1.0 template is preserved for ODT 1.0
1226 CPPUNIT_TEST_FIXTURE(SigningTest
, testPreserveMacroTemplateSignature10
)
1228 // set ODF version 1.0 / 1.1 as default
1230 std::shared_ptr
<comphelper::ConfigurationChanges
> pBatch(
1231 comphelper::ConfigurationChanges::create());
1232 officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch
);
1233 return pBatch
->commit();
1235 std::shared_ptr
<comphelper::ConfigurationChanges
> pBatch(
1236 comphelper::ConfigurationChanges::create());
1237 officecfg::Office::Common::Save::ODF::DefaultVersion::set(2, pBatch
);
1240 const OUString
aURL(m_directories
.getURLFromSrc(DATA_DIRECTORY
) + "tdf42316.ott");
1241 const OUString sLoadMessage
= "loading failed: " + aURL
;
1243 // load the template as-is to validate signatures
1244 mxComponent
= loadFromDesktop(
1245 aURL
, OUString(), comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1246 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1249 // we are a template, and have a non-invalid macro signature
1250 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1251 SignatureState::NOTVALIDATED
, OUString());
1253 // create new document from template
1254 // we can't use createDoc / MacrosTest::loadFromDesktop, because ALWAYS_EXECUTE_NO_WARN
1255 // won't verify the signature for templates, so the resulting document won't be able to
1256 // preserve the templates signature.
1257 mxComponent
->dispose();
1258 mxComponent
= mxDesktop
->loadComponentFromURL(
1259 aURL
, "_default", 0,
1260 comphelper::InitPropertySequence(
1261 { { "MacroExecutionMode",
1262 uno::Any(document::MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN
) } }));
1263 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1266 // we are somehow a template (?), and have just a valid macro signature
1267 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1268 SignatureState::NOTVALIDATED
, OUString());
1270 // save as new ODT document
1271 utl::TempFile aTempFileSaveAsODT
;
1272 aTempFileSaveAsODT
.EnableKillingFile();
1275 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY
);
1276 uno::Sequence
<beans::PropertyValue
> descSaveAs(
1277 comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer8")) } }));
1278 xDocStorable
->storeAsURL(aTempFileSaveAsODT
.GetURL(), descSaveAs
);
1282 CPPUNIT_FAIL("Failed to save ODT document");
1285 // save as new OTT template
1286 utl::TempFile aTempFileSaveAsOTT
;
1287 aTempFileSaveAsOTT
.EnableKillingFile();
1290 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY
);
1291 uno::Sequence
<beans::PropertyValue
> descSaveAs(comphelper::InitPropertySequence(
1292 { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1293 xDocStorable
->storeAsURL(aTempFileSaveAsOTT
.GetURL(), descSaveAs
);
1297 CPPUNIT_FAIL("Failed to save OTT template");
1300 // load the saved OTT template as-is to validate signatures
1301 mxComponent
->dispose();
1303 = loadFromDesktop(aTempFileSaveAsOTT
.GetURL(), OUString(),
1304 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1305 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1308 // the loaded document is a OTT with a non-invalid macro signature
1309 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1310 SignatureState::NOTVALIDATED
, OUString());
1312 // load saved ODT document
1313 createDoc(aTempFileSaveAsODT
.GetURL());
1315 // the loaded document is a ODT with a non-invalid macro signature
1316 assertDocument(CPPUNIT_SOURCELINE(), "writer8", SignatureState::NOSIGNATURES
,
1317 SignatureState::NOTVALIDATED
, OUString());
1319 // save as new OTT template
1320 utl::TempFile aTempFileSaveAsODT_OTT
;
1321 aTempFileSaveAsODT_OTT
.EnableKillingFile();
1324 uno::Reference
<frame::XStorable
> xDocStorable(mxComponent
, uno::UNO_QUERY
);
1325 uno::Sequence
<beans::PropertyValue
> descSaveAs(comphelper::InitPropertySequence(
1326 { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1327 xDocStorable
->storeAsURL(aTempFileSaveAsODT_OTT
.GetURL(), descSaveAs
);
1331 CPPUNIT_FAIL("Failed to save OTT template");
1334 // load the template as-is to validate signatures
1335 mxComponent
->dispose();
1337 = loadFromDesktop(aTempFileSaveAsODT_OTT
.GetURL(), OUString(),
1338 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1339 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage
, RTL_TEXTENCODING_UTF8
).getStr(),
1342 // the loaded document is a OTT with a non-invalid macro signature
1343 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES
,
1344 SignatureState::NOTVALIDATED
, OUString());
1349 void SigningTest::registerNamespaces(xmlXPathContextPtr
& pXmlXpathCtx
)
1351 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("odfds"),
1352 BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0"));
1353 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("dsig"),
1354 BAD_CAST("http://www.w3.org/2000/09/xmldsig#"));
1355 xmlXPathRegisterNs(pXmlXpathCtx
, BAD_CAST("xd"), BAD_CAST("http://uri.etsi.org/01903/v1.3.2#"));
1358 CPPUNIT_PLUGIN_IMPLEMENT();
1360 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */