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 
10 #include <config_features.h>
11 #include <config_gpgme.h>
12 
13 #include <sal/config.h>
14 
15 #include <cstdlib>
16 
17 #include <test/bootstrapfixture.hxx>
18 #include <unotest/macros_test.hxx>
19 #include <test/xmltesttools.hxx>
20 
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>
30 
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>
44 
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>
54 
55 using namespace com::sun::star;
56 
57 namespace
58 {
59 char const DATA_DIRECTORY[] = "/xmlsecurity/qa/unit/signing/data/";
60 }
61 
62 /// Testsuite for the document signing feature.
63 class SigningTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
64 {
65 protected:
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;
70 
71 #if HAVE_GPGCONF_SOCKETDIR
72     OString m_gpgconfCommandPrefix;
73 #endif
74 
75 public:
76     SigningTest();
77     virtual void setUp() override;
78     virtual void tearDown() override;
79     void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override;
80 
81 protected:
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);
91 #endif
92 };
93 
SigningTest()94 SigningTest::SigningTest() {}
95 
setUp()96 void SigningTest::setUp()
97 {
98     test::BootstrapFixture::setUp();
99 
100     OUString aSourceDir = m_directories.getURLFromSrc(DATA_DIRECTORY);
101     OUString aTargetDir
102         = m_directories.getURLFromWorkdir("CppunitTest/xmlsecurity_signing.test.user");
103 
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");
107 
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");
113 
114     OUString aTargetPath;
115     osl::FileBase::getSystemPathFromFileURL(aTargetDir, aTargetPath);
116 
117     OUString mozCertVar("MOZILLA_CERTIFICATE_FOLDER");
118     osl_setEnvironment(mozCertVar.pData, aTargetPath.pData);
119     OUString gpgHomeVar("GNUPGHOME");
120     osl_setEnvironment(gpgHomeVar.pData, aTargetPath.pData);
121 
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 + " ";
126     OString path;
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
131     // context
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);
140 #endif
141 
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());
147 }
148 
tearDown()149 void SigningTest::tearDown()
150 {
151     if (mxComponent.is())
152         mxComponent->dispose();
153 
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);
160 #endif
161 
162     test::BootstrapFixture::tearDown();
163 }
164 
createDoc(const OUString & rURL)165 void SigningTest::createDoc(const OUString& rURL)
166 {
167     if (mxComponent.is())
168         mxComponent->dispose();
169     if (rURL.isEmpty())
170         mxComponent = loadFromDesktop("private:factory/swriter", "com.sun.star.text.TextDocument");
171     else
172         mxComponent = loadFromDesktop(rURL, "com.sun.star.text.TextDocument");
173 }
174 
createCalc(const OUString & rURL)175 void SigningTest::createCalc(const OUString& rURL)
176 {
177     if (mxComponent.is())
178         mxComponent->dispose();
179     if (rURL.isEmpty())
180         mxComponent
181             = loadFromDesktop("private:factory/swriter", "com.sun.star.sheet.SpreadsheetDocument");
182     else
183         mxComponent = loadFromDesktop(rURL, "com.sun.star.sheet.SpreadsheetDocument");
184 }
185 
186 uno::Reference<security::XCertificate>
getCertificate(DocumentSignatureManager & rSignatureManager,svl::crypto::SignatureMethodAlgorithm eAlgo)187 SigningTest::getCertificate(DocumentSignatureManager& rSignatureManager,
188                             svl::crypto::SignatureMethodAlgorithm eAlgo)
189 {
190     uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment
191         = rSignatureManager.getSecurityEnvironment();
192     const uno::Sequence<uno::Reference<security::XCertificate>> aCertificates
193         = xSecurityEnvironment->getPersonalCertificates();
194 
195     for (const auto& xCertificate : aCertificates)
196     {
197         auto pCertificate = dynamic_cast<xmlsecurity::Certificate*>(xCertificate.get());
198         CPPUNIT_ASSERT(pCertificate);
199         if (pCertificate->getSignatureMethodAlgorithm() == eAlgo)
200             return xCertificate;
201     }
202     return uno::Reference<security::XCertificate>();
203 }
204 
CPPUNIT_TEST_FIXTURE(SigningTest,testDescription)205 CPPUNIT_TEST_FIXTURE(SigningTest, testDescription)
206 {
207     // Create an empty document and store it to a tempfile, finally load it as a storage.
208     createDoc("");
209 
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());
216 
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");
225 
226     // Then add a signature document.
227     uno::Reference<security::XCertificate> xCertificate
228         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::RSA);
229     if (!xCertificate.is())
230         return;
231     OUString aDescription("SigningTest::testDescription");
232     sal_Int32 nSecurityId;
233     aManager.add(xCertificate, mxSecurityContext, aDescription, nSecurityId, false);
234 
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);
240 }
241 
CPPUNIT_TEST_FIXTURE(SigningTest,testECDSA)242 CPPUNIT_TEST_FIXTURE(SigningTest, testECDSA)
243 {
244     // Create an empty document and store it to a tempfile, finally load it as a storage.
245     createDoc("");
246 
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());
253 
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");
262 
263     // Then add a signature.
264     uno::Reference<security::XCertificate> xCertificate
265         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::ECDSA);
266     if (!xCertificate.is())
267         return;
268     OUString aDescription;
269     sal_Int32 nSecurityId;
270     aManager.add(xCertificate, mxSecurityContext, aDescription, nSecurityId, false);
271 
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
277     // broken.
278     CPPUNIT_ASSERT_EQUAL(css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED,
279                          rInformations[0].nStatus);
280 }
281 
CPPUNIT_TEST_FIXTURE(SigningTest,testECDSAOOXML)282 CPPUNIT_TEST_FIXTURE(SigningTest, testECDSAOOXML)
283 {
284     // Create an empty document and store it to a tempfile, finally load it as a storage.
285     createDoc("");
286 
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());
293 
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");
302 
303     // Then add a document signature.
304     uno::Reference<security::XCertificate> xCertificate
305         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::ECDSA);
306     if (!xCertificate.is())
307         return;
308     OUString aDescription;
309     sal_Int32 nSecurityId;
310     aManager.add(xCertificate, mxSecurityContext, aDescription, nSecurityId,
311                  /*bAdESCompliant=*/false);
312 
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
318     // broken.
319     CPPUNIT_ASSERT_EQUAL(css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED,
320                          rInformations[0].nStatus);
321 }
322 
CPPUNIT_TEST_FIXTURE(SigningTest,testECDSAPDF)323 CPPUNIT_TEST_FIXTURE(SigningTest, testECDSAPDF)
324 {
325     // Create an empty document and store it to a tempfile, finally load it as
326     // a stream.
327     createDoc("");
328 
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());
335 
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);
343 
344     // Then add a document signature.
345     uno::Reference<security::XCertificate> xCertificate
346         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::ECDSA);
347     if (!xCertificate.is())
348         return;
349     OUString aDescription;
350     sal_Int32 nSecurityId;
351     aManager.add(xCertificate, mxSecurityContext, aDescription, nSecurityId,
352                  /*bAdESCompliant=*/true);
353 
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
359     // broken.
360     CPPUNIT_ASSERT_EQUAL(css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED,
361                          rInformations[0].nStatus);
362 }
363 
CPPUNIT_TEST_FIXTURE(SigningTest,testOOXMLDescription)364 CPPUNIT_TEST_FIXTURE(SigningTest, testOOXMLDescription)
365 {
366     // Create an empty document and store it to a tempfile, finally load it as a storage.
367     createDoc("");
368 
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());
375 
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");
384 
385     // Then add a document signature.
386     uno::Reference<security::XCertificate> xCertificate
387         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::RSA);
388     if (!xCertificate.is())
389         return;
390     OUString aDescription("SigningTest::testDescription");
391     sal_Int32 nSecurityId;
392     aManager.add(xCertificate, mxSecurityContext, aDescription, nSecurityId, false);
393 
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);
399 }
400 
401 /// Test appending a new signature next to an existing one.
CPPUNIT_TEST_FIXTURE(SigningTest,testOOXMLAppend)402 CPPUNIT_TEST_FIXTURE(SigningTest, testOOXMLAppend)
403 {
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());
423 
424     // Then add a second document signature.
425     uno::Reference<security::XCertificate> xCertificate
426         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::RSA);
427     if (!xCertificate.is())
428         return;
429     sal_Int32 nSecurityId;
430     aManager.add(xCertificate, mxSecurityContext, OUString(), nSecurityId, false);
431 
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());
436 }
437 
438 /// Test removing a signature from existing ones.
CPPUNIT_TEST_FIXTURE(SigningTest,testOOXMLRemove)439 CPPUNIT_TEST_FIXTURE(SigningTest, testOOXMLRemove)
440 {
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());
459 
460     // Then remove the last added signature.
461     uno::Reference<security::XCertificate> xCertificate
462         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::RSA);
463     if (!xCertificate.is())
464         return;
465     aManager.remove(0);
466 
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);
471 }
472 
473 /// Test removing all signatures from a document.
CPPUNIT_TEST_FIXTURE(SigningTest,testOOXMLRemoveAll)474 CPPUNIT_TEST_FIXTURE(SigningTest, testOOXMLRemoveAll)
475 {
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());
495 
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())
500         return;
501     aManager.remove(0);
502     aManager.read(/*bUseTempStream=*/true);
503     aManager.write(/*bXAdESCompliantIfODF=*/false);
504 
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"));
508 
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];
516     CPPUNIT_ASSERT(
517         std::none_of(rOverrides.begin(), rOverrides.end(), [](const beans::StringPair& rPair) {
518             return rPair.First.startsWith("/_xmlsignatures/sig");
519         }));
520 }
521 
522 /// Test a typical ODF where all streams are signed.
CPPUNIT_TEST_FIXTURE(SigningTest,testODFGood)523 CPPUNIT_TEST_FIXTURE(SigningTest, testODFGood)
524 {
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));
535 }
536 
537 /// Test a typical broken ODF signature where one stream is corrupted.
CPPUNIT_TEST_FIXTURE(SigningTest,testODFBroken)538 CPPUNIT_TEST_FIXTURE(SigningTest, testODFBroken)
539 {
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()));
547 }
548 
549 // Document has a signature stream, but no actual signatures.
CPPUNIT_TEST_FIXTURE(SigningTest,testODFNo)550 CPPUNIT_TEST_FIXTURE(SigningTest, testODFNo)
551 {
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()));
559 }
560 
561 /// Test a typical OOXML where a number of (but not all) streams are signed.
CPPUNIT_TEST_FIXTURE(SigningTest,testOOXMLPartial)562 CPPUNIT_TEST_FIXTURE(SigningTest, testOOXMLPartial)
563 {
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));
576 }
577 
578 /// Test a typical broken OOXML signature where one stream is corrupted.
CPPUNIT_TEST_FIXTURE(SigningTest,testOOXMLBroken)579 CPPUNIT_TEST_FIXTURE(SigningTest, testOOXMLBroken)
580 {
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()));
589 }
590 
591 #if HAVE_FEATURE_PDFIMPORT
592 
593 /// Test a typical PDF where the signature is good.
CPPUNIT_TEST_FIXTURE(SigningTest,testPDFGood)594 CPPUNIT_TEST_FIXTURE(SigningTest, testPDFGood)
595 {
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));
606 }
607 
608 /// Test a typical PDF where the signature is bad.
CPPUNIT_TEST_FIXTURE(SigningTest,testPDFBad)609 CPPUNIT_TEST_FIXTURE(SigningTest, testPDFBad)
610 {
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()));
618 }
619 
CPPUNIT_TEST_FIXTURE(SigningTest,testPDFHideAndReplace)620 CPPUNIT_TEST_FIXTURE(SigningTest, testPDFHideAndReplace)
621 {
622     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY)
623               + "hide-and-replace-shadow-file-signed-2.pdf");
624     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
625     CPPUNIT_ASSERT(pBaseModel);
626     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
627     CPPUNIT_ASSERT(pObjectShell);
628     // Without the accompanying fix in place, this test would have failed with:
629     // - Expected: 2 (BROKEN)
630     // - Actual  : 6 (NOTVALIDATED_PARTIAL_OK)
631     // i.e. a non-commenting update after a signature was not marked as invalid.
632     CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN),
633                          static_cast<int>(pObjectShell->GetDocumentSignatureState()));
634 }
635 
636 /// Test a typical PDF which is not signed.
CPPUNIT_TEST_FIXTURE(SigningTest,testPDFNo)637 CPPUNIT_TEST_FIXTURE(SigningTest, testPDFNo)
638 {
639     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "no.pdf");
640     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
641     CPPUNIT_ASSERT(pBaseModel);
642     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
643     CPPUNIT_ASSERT(pObjectShell);
644     CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::NOSIGNATURES),
645                          static_cast<int>(pObjectShell->GetDocumentSignatureState()));
646 }
647 
648 #endif
649 
CPPUNIT_TEST_FIXTURE(SigningTest,test96097Calc)650 CPPUNIT_TEST_FIXTURE(SigningTest, test96097Calc)
651 {
652     createCalc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf96097.ods");
653     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
654     CPPUNIT_ASSERT_MESSAGE("Failed to access document base model", pBaseModel);
655 
656     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
657     CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pObjectShell);
658 
659     SignatureState nActual = pObjectShell->GetScriptingSignatureState();
660     CPPUNIT_ASSERT_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual)).getStr()),
661                            (nActual == SignatureState::OK || nActual == SignatureState::NOTVALIDATED
662                             || nActual == SignatureState::INVALID));
663 
664     uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY_THROW);
665 
666     // Save a copy
667     utl::TempFile aTempFileSaveCopy;
668     aTempFileSaveCopy.EnableKillingFile();
669     uno::Sequence<beans::PropertyValue> descSaveACopy(comphelper::InitPropertySequence(
670         { { "SaveACopy", uno::Any(true) }, { "FilterName", uno::Any(OUString("calc8")) } }));
671     xDocStorable->storeToURL(aTempFileSaveCopy.GetURL(), descSaveACopy);
672 
673     try
674     {
675         // Save As
676         utl::TempFile aTempFileSaveAs;
677         aTempFileSaveAs.EnableKillingFile();
678         uno::Sequence<beans::PropertyValue> descSaveAs(
679             comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("calc8")) } }));
680         xDocStorable->storeAsURL(aTempFileSaveAs.GetURL(), descSaveAs);
681     }
682     catch (...)
683     {
684         CPPUNIT_FAIL("Fail to save as the document");
685     }
686 }
687 
CPPUNIT_TEST_FIXTURE(SigningTest,test96097Doc)688 CPPUNIT_TEST_FIXTURE(SigningTest, test96097Doc)
689 {
690     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf96097.odt");
691     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
692     CPPUNIT_ASSERT(pBaseModel);
693     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
694     CPPUNIT_ASSERT(pObjectShell);
695 
696     SignatureState nActual = pObjectShell->GetScriptingSignatureState();
697     CPPUNIT_ASSERT_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual)).getStr()),
698                            (nActual == SignatureState::OK || nActual == SignatureState::NOTVALIDATED
699                             || nActual == SignatureState::INVALID));
700 
701     uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY_THROW);
702 
703     // Save a copy
704     utl::TempFile aTempFileSaveCopy;
705     aTempFileSaveCopy.EnableKillingFile();
706     uno::Sequence<beans::PropertyValue> descSaveACopy(comphelper::InitPropertySequence(
707         { { "SaveACopy", uno::Any(true) }, { "FilterName", uno::Any(OUString("writer8")) } }));
708     xDocStorable->storeToURL(aTempFileSaveCopy.GetURL(), descSaveACopy);
709 
710     try
711     {
712         // Save As
713         utl::TempFile aTempFileSaveAs;
714         aTempFileSaveAs.EnableKillingFile();
715         uno::Sequence<beans::PropertyValue> descSaveAs(
716             comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer8")) } }));
717         xDocStorable->storeAsURL(aTempFileSaveAs.GetURL(), descSaveAs);
718     }
719     catch (...)
720     {
721         CPPUNIT_FAIL("Fail to save as the document");
722     }
723 }
724 
CPPUNIT_TEST_FIXTURE(SigningTest,testXAdESNotype)725 CPPUNIT_TEST_FIXTURE(SigningTest, testXAdESNotype)
726 {
727     // Create a working copy.
728     utl::TempFile aTempFile;
729     aTempFile.EnableKillingFile();
730     OUString aURL = aTempFile.GetURL();
731     CPPUNIT_ASSERT_EQUAL(
732         osl::File::RC::E_None,
733         osl::File::copy(m_directories.getURLFromSrc(DATA_DIRECTORY) + "notype-xades.odt", aURL));
734 
735     // Read existing signature.
736     DocumentSignatureManager aManager(mxComponentContext, DocumentSignatureMode::Content);
737     CPPUNIT_ASSERT(aManager.init());
738     uno::Reference<embed::XStorage> xStorage
739         = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
740             ZIP_STORAGE_FORMAT_STRING, aTempFile.GetURL(), embed::ElementModes::READWRITE);
741     CPPUNIT_ASSERT(xStorage.is());
742     aManager.setStore(xStorage);
743     aManager.getSignatureHelper().SetStorage(xStorage, "1.2");
744     aManager.read(/*bUseTempStream=*/false);
745 
746     // Create a new signature.
747     uno::Reference<security::XCertificate> xCertificate
748         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::RSA);
749     if (!xCertificate.is())
750         return;
751     sal_Int32 nSecurityId;
752     aManager.add(xCertificate, mxSecurityContext, /*rDescription=*/OUString(), nSecurityId,
753                  /*bAdESCompliant=*/true);
754 
755     // Write to storage.
756     aManager.read(/*bUseTempStream=*/true);
757     aManager.write(/*bXAdESCompliantIfODF=*/true);
758     uno::Reference<embed::XTransactedObject> xTransactedObject(xStorage, uno::UNO_QUERY);
759     xTransactedObject->commit();
760 
761     // Parse the resulting XML.
762     uno::Reference<embed::XStorage> xMetaInf
763         = xStorage->openStorageElement("META-INF", embed::ElementModes::READ);
764     uno::Reference<io::XInputStream> xInputStream(
765         xMetaInf->openStreamElement("documentsignatures.xml", embed::ElementModes::READ),
766         uno::UNO_QUERY);
767     std::shared_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
768     xmlDocPtr pXmlDoc = parseXmlStream(pStream.get());
769 
770     // Without the accompanying fix in place, this test would have failed with "unexpected 'Type'
771     // attribute", i.e. the signature without such an attribute was not preserved correctly.
772     assertXPathNoAttribute(pXmlDoc,
773                            "/odfds:document-signatures/dsig:Signature[1]/dsig:SignedInfo/"
774                            "dsig:Reference[@URI='#idSignedProperties']",
775                            "Type");
776 
777     // New signature always has the Type attribute.
778     assertXPath(pXmlDoc,
779                 "/odfds:document-signatures/dsig:Signature[2]/dsig:SignedInfo/"
780                 "dsig:Reference[@URI='#idSignedProperties']",
781                 "Type", "http://uri.etsi.org/01903#SignedProperties");
782 }
783 
784 /// Creates a XAdES signature from scratch.
CPPUNIT_TEST_FIXTURE(SigningTest,testXAdES)785 CPPUNIT_TEST_FIXTURE(SigningTest, testXAdES)
786 {
787     // Create an empty document, store it to a tempfile and load it as a storage.
788     createDoc(OUString());
789 
790     utl::TempFile aTempFile;
791     aTempFile.EnableKillingFile();
792     uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
793     utl::MediaDescriptor aMediaDescriptor;
794     aMediaDescriptor["FilterName"] <<= OUString("writer8");
795     xStorable->storeAsURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
796 
797     DocumentSignatureManager aManager(mxComponentContext, DocumentSignatureMode::Content);
798     CPPUNIT_ASSERT(aManager.init());
799     uno::Reference<embed::XStorage> xStorage
800         = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
801             ZIP_STORAGE_FORMAT_STRING, aTempFile.GetURL(), embed::ElementModes::READWRITE);
802     CPPUNIT_ASSERT(xStorage.is());
803     aManager.setStore(xStorage);
804     aManager.getSignatureHelper().SetStorage(xStorage, "1.2");
805 
806     // Create a signature.
807     uno::Reference<security::XCertificate> xCertificate
808         = getCertificate(aManager, svl::crypto::SignatureMethodAlgorithm::RSA);
809     if (!xCertificate.is())
810         return;
811     sal_Int32 nSecurityId;
812     aManager.add(xCertificate, mxSecurityContext, /*rDescription=*/OUString(), nSecurityId,
813                  /*bAdESCompliant=*/true);
814 
815     // Write to storage.
816     aManager.read(/*bUseTempStream=*/true);
817     aManager.write(/*bXAdESCompliantIfODF=*/true);
818     uno::Reference<embed::XTransactedObject> xTransactedObject(xStorage, uno::UNO_QUERY);
819     xTransactedObject->commit();
820 
821     // Parse the resulting XML.
822     uno::Reference<embed::XStorage> xMetaInf
823         = xStorage->openStorageElement("META-INF", embed::ElementModes::READ);
824     uno::Reference<io::XInputStream> xInputStream(
825         xMetaInf->openStreamElement("documentsignatures.xml", embed::ElementModes::READ),
826         uno::UNO_QUERY);
827     std::shared_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
828     xmlDocPtr pXmlDoc = parseXmlStream(pStream.get());
829 
830     // Assert that the digest algorithm is SHA-256 in the bAdESCompliant case, not SHA-1.
831     assertXPath(pXmlDoc,
832                 "/odfds:document-signatures/dsig:Signature/dsig:SignedInfo/"
833                 "dsig:Reference[@URI='content.xml']/dsig:DigestMethod",
834                 "Algorithm", ALGO_XMLDSIGSHA256);
835 
836     // Assert that the digest of the signing certificate is included.
837     assertXPath(pXmlDoc, "//xd:CertDigest", 1);
838 
839     // Assert that the Type attribute on the idSignedProperties reference is
840     // not missing.
841     assertXPath(pXmlDoc,
842                 "/odfds:document-signatures/dsig:Signature/dsig:SignedInfo/"
843                 "dsig:Reference[@URI='#idSignedProperties']",
844                 "Type", "http://uri.etsi.org/01903#SignedProperties");
845 }
846 
847 /// Works with an existing good XAdES signature.
CPPUNIT_TEST_FIXTURE(SigningTest,testXAdESGood)848 CPPUNIT_TEST_FIXTURE(SigningTest, testXAdESGood)
849 {
850     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "good-xades.odt");
851     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
852     CPPUNIT_ASSERT(pBaseModel);
853     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
854     CPPUNIT_ASSERT(pObjectShell);
855     // We expect NOTVALIDATED in case the root CA is not imported on the system, and OK otherwise, so accept both.
856     SignatureState nActual = pObjectShell->GetDocumentSignatureState();
857     CPPUNIT_ASSERT_MESSAGE(
858         (OString::number(o3tl::underlyingEnumValue(nActual)).getStr()),
859         (nActual == SignatureState::NOTVALIDATED || nActual == SignatureState::OK));
860 }
861 
862 /// Test importing of signature line
CPPUNIT_TEST_FIXTURE(SigningTest,testSignatureLineOOXML)863 CPPUNIT_TEST_FIXTURE(SigningTest, testSignatureLineOOXML)
864 {
865     // Given: A document (docx) with a signature line and a valid signature
866     uno::Reference<security::XDocumentDigitalSignatures> xSignatures(
867         security::DocumentDigitalSignatures::createWithVersion(
868             comphelper::getProcessComponentContext(), "1.2"));
869 
870     uno::Reference<embed::XStorage> xStorage
871         = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
872             ZIP_STORAGE_FORMAT_STRING,
873             m_directories.getURLFromSrc(DATA_DIRECTORY) + "signatureline.docx",
874             embed::ElementModes::READ);
875     CPPUNIT_ASSERT(xStorage.is());
876 
877     uno::Sequence<security::DocumentSignatureInformation> xSignatureInfo
878         = xSignatures->verifyScriptingContentSignatures(xStorage,
879                                                         uno::Reference<io::XInputStream>());
880 
881     // The signature should have a valid signature, and signature line with two valid images
882     CPPUNIT_ASSERT(xSignatureInfo[0].SignatureIsValid);
883     CPPUNIT_ASSERT_EQUAL(OUString("{DEE0514B-13E8-4674-A831-46E3CDB18BB4}"),
884                          xSignatureInfo[0].SignatureLineId);
885     CPPUNIT_ASSERT(xSignatureInfo[0].ValidSignatureLineImage.is());
886     CPPUNIT_ASSERT(xSignatureInfo[0].InvalidSignatureLineImage.is());
887 }
888 
CPPUNIT_TEST_FIXTURE(SigningTest,testSignatureLineODF)889 CPPUNIT_TEST_FIXTURE(SigningTest, testSignatureLineODF)
890 {
891     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "signatureline.odt");
892     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
893     CPPUNIT_ASSERT(pBaseModel);
894     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
895     CPPUNIT_ASSERT(pObjectShell);
896 
897     uno::Sequence<security::DocumentSignatureInformation> xSignatureInfo
898         = pObjectShell->GetDocumentSignatureInformation(false);
899 
900     CPPUNIT_ASSERT(xSignatureInfo[0].SignatureIsValid);
901     CPPUNIT_ASSERT_EQUAL(OUString("{41CF56EE-331B-4125-97D8-2F5669DD3AAC}"),
902                          xSignatureInfo[0].SignatureLineId);
903     CPPUNIT_ASSERT(xSignatureInfo[0].ValidSignatureLineImage.is());
904     CPPUNIT_ASSERT(xSignatureInfo[0].InvalidSignatureLineImage.is());
905 }
906 
907 #if HAVE_FEATURE_GPGVERIFY
908 /// Test a typical ODF where all streams are GPG-signed.
CPPUNIT_TEST_FIXTURE(SigningTest,testODFGoodGPG)909 CPPUNIT_TEST_FIXTURE(SigningTest, testODFGoodGPG)
910 {
911     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "goodGPG.odt");
912     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
913     CPPUNIT_ASSERT(pBaseModel);
914     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
915     CPPUNIT_ASSERT(pObjectShell);
916     // Our local gpg config fully trusts the signing cert, so in
917     // contrast to the X509 test we can fail on NOTVALIDATED here
918     SignatureState nActual = pObjectShell->GetDocumentSignatureState();
919     CPPUNIT_ASSERT_EQUAL_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual)).getStr()),
920                                  SignatureState::OK, nActual);
921 }
922 
923 /// Test a typical ODF where all streams are GPG-signed, but we don't trust the signature.
CPPUNIT_TEST_FIXTURE(SigningTest,testODFUntrustedGoodGPG)924 CPPUNIT_TEST_FIXTURE(SigningTest, testODFUntrustedGoodGPG)
925 {
926     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "untrustedGoodGPG.odt");
927     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
928     CPPUNIT_ASSERT(pBaseModel);
929     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
930     CPPUNIT_ASSERT(pObjectShell);
931     // Our local gpg config does _not_ trust the signing cert, so in
932     // contrast to the X509 test we can fail everything but
933     // NOTVALIDATED here
934     SignatureState nActual = pObjectShell->GetDocumentSignatureState();
935     CPPUNIT_ASSERT_EQUAL_MESSAGE((OString::number(o3tl::underlyingEnumValue(nActual)).getStr()),
936                                  SignatureState::NOTVALIDATED, nActual);
937 }
938 
939 /// Test a typical broken ODF signature where one stream is corrupted.
CPPUNIT_TEST_FIXTURE(SigningTest,testODFBrokenStreamGPG)940 CPPUNIT_TEST_FIXTURE(SigningTest, testODFBrokenStreamGPG)
941 {
942     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "badStreamGPG.odt");
943     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
944     CPPUNIT_ASSERT(pBaseModel);
945     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
946     CPPUNIT_ASSERT(pObjectShell);
947     CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN),
948                          static_cast<int>(pObjectShell->GetDocumentSignatureState()));
949 }
950 
951 /// Test a typical broken ODF signature where the XML dsig hash is corrupted.
CPPUNIT_TEST_FIXTURE(SigningTest,testODFBrokenDsigGPG)952 CPPUNIT_TEST_FIXTURE(SigningTest, testODFBrokenDsigGPG)
953 {
954     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "badDsigGPG.odt");
955     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
956     CPPUNIT_ASSERT(pBaseModel);
957     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
958     CPPUNIT_ASSERT(pObjectShell);
959     CPPUNIT_ASSERT_EQUAL(static_cast<int>(SignatureState::BROKEN),
960                          static_cast<int>(pObjectShell->GetDocumentSignatureState()));
961 }
962 
963 #if HAVE_GPGCONF_SOCKETDIR
964 
965 /// Test loading an encrypted ODF document
CPPUNIT_TEST_FIXTURE(SigningTest,testODFEncryptedGPG)966 CPPUNIT_TEST_FIXTURE(SigningTest, testODFEncryptedGPG)
967 {
968     // ODF1.2 + loext flavour
969     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "encryptedGPG.odt");
970     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
971     CPPUNIT_ASSERT(pBaseModel);
972     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
973     CPPUNIT_ASSERT(pObjectShell);
974 
975     // ODF1.3 flavour
976     createDoc(m_directories.getURLFromSrc(DATA_DIRECTORY) + "encryptedGPG_odf13.odt");
977     pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
978     CPPUNIT_ASSERT(pBaseModel);
979     pObjectShell = pBaseModel->GetObjectShell();
980     CPPUNIT_ASSERT(pObjectShell);
981 }
982 
983 #endif
984 
assertDocument(const::CppUnit::SourceLine aSrcLine,const OUString & rFilterName,const SignatureState nDocSign,const SignatureState nMacroSign,const OUString & sVersion)985 SfxObjectShell* SigningTest::assertDocument(const ::CppUnit::SourceLine aSrcLine,
986                                             const OUString& rFilterName,
987                                             const SignatureState nDocSign,
988                                             const SignatureState nMacroSign,
989                                             const OUString& sVersion)
990 {
991     std::string sPos = aSrcLine.fileName() + ":" + OString::number(aSrcLine.lineNumber()).getStr();
992 
993     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(mxComponent.get());
994     CPPUNIT_ASSERT_MESSAGE(sPos, pBaseModel);
995     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
996     CPPUNIT_ASSERT_MESSAGE(sPos, pObjectShell);
997 
998     CPPUNIT_ASSERT_EQUAL_MESSAGE(sPos, rFilterName,
999                                  pObjectShell->GetMedium()->GetFilter()->GetFilterName());
1000     SignatureState nActual = pObjectShell->GetDocumentSignatureState();
1001     CPPUNIT_ASSERT_EQUAL_MESSAGE(sPos, nDocSign, nActual);
1002     nActual = pObjectShell->GetScriptingSignatureState();
1003     CPPUNIT_ASSERT_EQUAL_MESSAGE(sPos, nMacroSign, nActual);
1004 
1005     OUString aODFVersion;
1006     uno::Reference<beans::XPropertySet> xPropSet(pObjectShell->GetStorage(), uno::UNO_QUERY_THROW);
1007     xPropSet->getPropertyValue("Version") >>= aODFVersion;
1008     CPPUNIT_ASSERT_EQUAL(sVersion, aODFVersion);
1009 
1010     return pObjectShell;
1011 }
1012 
1013 /// Test if a macro signature from a OTT 1.2 template is preserved for ODT 1.2
CPPUNIT_TEST_FIXTURE(SigningTest,testPreserveMacroTemplateSignature12_ODF)1014 CPPUNIT_TEST_FIXTURE(SigningTest, testPreserveMacroTemplateSignature12_ODF)
1015 {
1016     const OUString aURL(m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf42316_odt12.ott");
1017     const OUString sLoadMessage = "loading failed: " + aURL;
1018 
1019     // load the template as-is to validate signatures
1020     mxComponent = loadFromDesktop(
1021         aURL, OUString(), comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1022     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1023                            mxComponent.is());
1024 
1025     // we are a template, and have a valid document and macro signature
1026     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::OK, SignatureState::OK,
1027                    ODFVER_012_TEXT);
1028 
1029     // create new document from template
1030     // we can't use createDoc / MacrosTest::loadFromDesktop, because ALWAYS_EXECUTE_NO_WARN
1031     // won't verify the signature for templates, so the resulting document won't be able to
1032     // preserve the templates signature.
1033     mxComponent->dispose();
1034     mxComponent = mxDesktop->loadComponentFromURL(
1035         aURL, "_default", 0,
1036         comphelper::InitPropertySequence(
1037             { { "MacroExecutionMode",
1038                 uno::Any(document::MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN) } }));
1039     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1040                            mxComponent.is());
1041 
1042     // we are somehow a template (?), and have just a valid macro signature
1043     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1044                    SignatureState::OK, ODFVER_012_TEXT);
1045 
1046     // save as new ODT document
1047     utl::TempFile aTempFileSaveAsODT;
1048     aTempFileSaveAsODT.EnableKillingFile();
1049     try
1050     {
1051         uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY);
1052         uno::Sequence<beans::PropertyValue> descSaveAs(
1053             comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer8")) } }));
1054         xDocStorable->storeAsURL(aTempFileSaveAsODT.GetURL(), descSaveAs);
1055     }
1056     catch (...)
1057     {
1058         CPPUNIT_FAIL("Failed to save ODT document");
1059     }
1060 
1061     // save as new OTT template
1062     utl::TempFile aTempFileSaveAsOTT;
1063     aTempFileSaveAsOTT.EnableKillingFile();
1064     try
1065     {
1066         uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY);
1067         uno::Sequence<beans::PropertyValue> descSaveAs(comphelper::InitPropertySequence(
1068             { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1069         xDocStorable->storeAsURL(aTempFileSaveAsOTT.GetURL(), descSaveAs);
1070     }
1071     catch (...)
1072     {
1073         CPPUNIT_FAIL("Failed to save OTT template");
1074     }
1075 
1076     // load the saved OTT template as-is to validate signatures
1077     mxComponent->dispose();
1078     mxComponent
1079         = loadFromDesktop(aTempFileSaveAsOTT.GetURL(), OUString(),
1080                           comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1081     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1082                            mxComponent.is());
1083 
1084     // the loaded document is a OTT with a valid macro signature
1085     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1086                    SignatureState::OK, ODFVER_012_TEXT);
1087 
1088     // load saved ODT document
1089     createDoc(aTempFileSaveAsODT.GetURL());
1090 
1091     // the loaded document is a ODT with a macro signature
1092     assertDocument(CPPUNIT_SOURCELINE(), "writer8", SignatureState::NOSIGNATURES,
1093                    SignatureState::OK, ODFVER_012_TEXT);
1094 
1095     // save as new OTT template
1096     utl::TempFile aTempFileSaveAsODT_OTT;
1097     aTempFileSaveAsODT_OTT.EnableKillingFile();
1098     try
1099     {
1100         uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY);
1101         uno::Sequence<beans::PropertyValue> descSaveAs(comphelper::InitPropertySequence(
1102             { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1103         xDocStorable->storeAsURL(aTempFileSaveAsODT_OTT.GetURL(), descSaveAs);
1104     }
1105     catch (...)
1106     {
1107         CPPUNIT_FAIL("Failed to save OTT template");
1108     }
1109 
1110     // load the template as-is to validate signatures
1111     mxComponent->dispose();
1112     mxComponent
1113         = loadFromDesktop(aTempFileSaveAsODT_OTT.GetURL(), OUString(),
1114                           comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1115     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1116                            mxComponent.is());
1117 
1118     // the loaded document is a OTT with a valid macro signature
1119     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1120                    SignatureState::OK, ODFVER_012_TEXT);
1121 }
1122 
1123 /// Test if a macro signature from an OTT 1.0 is dropped for ODT 1.2
CPPUNIT_TEST_FIXTURE(SigningTest,testDropMacroTemplateSignature)1124 CPPUNIT_TEST_FIXTURE(SigningTest, testDropMacroTemplateSignature)
1125 {
1126     const OUString aURL(m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf42316.ott");
1127     const OUString sLoadMessage = "loading failed: " + aURL;
1128 
1129     // load the template as-is to validate signatures
1130     mxComponent = loadFromDesktop(
1131         aURL, OUString(), comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1132     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1133                            mxComponent.is());
1134 
1135     // we are a template, and have a non-invalid macro signature
1136     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1137                    SignatureState::NOTVALIDATED, OUString());
1138 
1139     // create new document from template
1140     // we can't use createDoc / MacrosTest::loadFromDesktop, because ALWAYS_EXECUTE_NO_WARN
1141     // won't verify the signature for templates, so the resulting document won't be able to
1142     // preserve the templates signature.
1143     mxComponent->dispose();
1144     mxComponent = mxDesktop->loadComponentFromURL(
1145         aURL, "_default", 0,
1146         comphelper::InitPropertySequence(
1147             { { "MacroExecutionMode",
1148                 uno::Any(document::MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN) } }));
1149     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1150                            mxComponent.is());
1151 
1152     // we are somehow a template (?), and have just a valid macro signature
1153     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1154                    SignatureState::NOTVALIDATED, OUString());
1155 
1156     // save as new ODT document
1157     utl::TempFile aTempFileSaveAs;
1158     aTempFileSaveAs.EnableKillingFile();
1159     try
1160     {
1161         uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY);
1162         uno::Sequence<beans::PropertyValue> descSaveAs(
1163             comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer8")) } }));
1164         xDocStorable->storeAsURL(aTempFileSaveAs.GetURL(), descSaveAs);
1165     }
1166     catch (...)
1167     {
1168         CPPUNIT_FAIL("Failed to save ODT document");
1169     }
1170 
1171     // load saved document
1172     createDoc(aTempFileSaveAs.GetURL());
1173 
1174     // the loaded document is a 1.2 ODT without any signatures
1175     assertDocument(CPPUNIT_SOURCELINE(), "writer8", SignatureState::NOSIGNATURES,
1176                    SignatureState::NOSIGNATURES, ODFVER_012_TEXT);
1177 
1178     // load the template as-is to validate signatures
1179     mxComponent->dispose();
1180     mxComponent = loadFromDesktop(
1181         aURL, OUString(), comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1182     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1183                            mxComponent.is());
1184 
1185     // we are a template, and have a non-invalid macro signature
1186     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1187                    SignatureState::NOTVALIDATED, OUString());
1188 
1189     // save as new OTT template
1190     utl::TempFile aTempFileSaveAsOTT;
1191     aTempFileSaveAsOTT.EnableKillingFile();
1192     try
1193     {
1194         uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY);
1195         uno::Sequence<beans::PropertyValue> descSaveAs(comphelper::InitPropertySequence(
1196             { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1197         xDocStorable->storeAsURL(aTempFileSaveAsOTT.GetURL(), descSaveAs);
1198     }
1199     catch (...)
1200     {
1201         CPPUNIT_FAIL("Failed to save OTT template");
1202     }
1203 
1204     // load the template as-is to validate signatures
1205     mxComponent->dispose();
1206     mxComponent
1207         = loadFromDesktop(aTempFileSaveAsOTT.GetURL(), OUString(),
1208                           comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1209     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1210                            mxComponent.is());
1211 
1212     // the loaded document is a 1.2 OTT without any signatures
1213     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1214                    SignatureState::NOSIGNATURES, ODFVER_012_TEXT);
1215 }
1216 
1217 class Resetter
1218 {
1219 private:
1220     std::function<void()> m_Func;
1221 
1222 public:
Resetter(std::function<void ()> const & rFunc)1223     Resetter(std::function<void()> const& rFunc)
1224         : m_Func(rFunc)
1225     {
1226     }
~Resetter()1227     ~Resetter()
1228     {
1229         try
1230         {
1231             m_Func();
1232         }
1233         catch (...) // has to be reliable
1234         {
1235             fprintf(stderr, "resetter failed with exception\n");
1236             abort();
1237         }
1238     }
1239 };
1240 
1241 /// Test if a macro signature from a OTT 1.0 template is preserved for ODT 1.0
CPPUNIT_TEST_FIXTURE(SigningTest,testPreserveMacroTemplateSignature10)1242 CPPUNIT_TEST_FIXTURE(SigningTest, testPreserveMacroTemplateSignature10)
1243 {
1244     // set ODF version 1.0 / 1.1 as default
1245     Resetter _([]() {
1246         std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
1247             comphelper::ConfigurationChanges::create());
1248         officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
1249         return pBatch->commit();
1250     });
1251     std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
1252         comphelper::ConfigurationChanges::create());
1253     officecfg::Office::Common::Save::ODF::DefaultVersion::set(2, pBatch);
1254     pBatch->commit();
1255 
1256     const OUString aURL(m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf42316.ott");
1257     const OUString sLoadMessage = "loading failed: " + aURL;
1258 
1259     // load the template as-is to validate signatures
1260     mxComponent = loadFromDesktop(
1261         aURL, OUString(), comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1262     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1263                            mxComponent.is());
1264 
1265     // we are a template, and have a non-invalid macro signature
1266     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1267                    SignatureState::NOTVALIDATED, OUString());
1268 
1269     // create new document from template
1270     // we can't use createDoc / MacrosTest::loadFromDesktop, because ALWAYS_EXECUTE_NO_WARN
1271     // won't verify the signature for templates, so the resulting document won't be able to
1272     // preserve the templates signature.
1273     mxComponent->dispose();
1274     mxComponent = mxDesktop->loadComponentFromURL(
1275         aURL, "_default", 0,
1276         comphelper::InitPropertySequence(
1277             { { "MacroExecutionMode",
1278                 uno::Any(document::MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN) } }));
1279     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1280                            mxComponent.is());
1281 
1282     // we are somehow a template (?), and have just a valid macro signature
1283     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1284                    SignatureState::NOTVALIDATED, OUString());
1285 
1286     // save as new ODT document
1287     utl::TempFile aTempFileSaveAsODT;
1288     aTempFileSaveAsODT.EnableKillingFile();
1289     try
1290     {
1291         uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY);
1292         uno::Sequence<beans::PropertyValue> descSaveAs(
1293             comphelper::InitPropertySequence({ { "FilterName", uno::Any(OUString("writer8")) } }));
1294         xDocStorable->storeAsURL(aTempFileSaveAsODT.GetURL(), descSaveAs);
1295     }
1296     catch (...)
1297     {
1298         CPPUNIT_FAIL("Failed to save ODT document");
1299     }
1300 
1301     // save as new OTT template
1302     utl::TempFile aTempFileSaveAsOTT;
1303     aTempFileSaveAsOTT.EnableKillingFile();
1304     try
1305     {
1306         uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY);
1307         uno::Sequence<beans::PropertyValue> descSaveAs(comphelper::InitPropertySequence(
1308             { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1309         xDocStorable->storeAsURL(aTempFileSaveAsOTT.GetURL(), descSaveAs);
1310     }
1311     catch (...)
1312     {
1313         CPPUNIT_FAIL("Failed to save OTT template");
1314     }
1315 
1316     // load the saved OTT template as-is to validate signatures
1317     mxComponent->dispose();
1318     mxComponent
1319         = loadFromDesktop(aTempFileSaveAsOTT.GetURL(), OUString(),
1320                           comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1321     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1322                            mxComponent.is());
1323 
1324     // the loaded document is a OTT with a non-invalid macro signature
1325     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1326                    SignatureState::NOTVALIDATED, OUString());
1327 
1328     // load saved ODT document
1329     createDoc(aTempFileSaveAsODT.GetURL());
1330 
1331     // the loaded document is a ODT with a non-invalid macro signature
1332     assertDocument(CPPUNIT_SOURCELINE(), "writer8", SignatureState::NOSIGNATURES,
1333                    SignatureState::NOTVALIDATED, OUString());
1334 
1335     // save as new OTT template
1336     utl::TempFile aTempFileSaveAsODT_OTT;
1337     aTempFileSaveAsODT_OTT.EnableKillingFile();
1338     try
1339     {
1340         uno::Reference<frame::XStorable> xDocStorable(mxComponent, uno::UNO_QUERY);
1341         uno::Sequence<beans::PropertyValue> descSaveAs(comphelper::InitPropertySequence(
1342             { { "FilterName", uno::Any(OUString("writer8_template")) } }));
1343         xDocStorable->storeAsURL(aTempFileSaveAsODT_OTT.GetURL(), descSaveAs);
1344     }
1345     catch (...)
1346     {
1347         CPPUNIT_FAIL("Failed to save OTT template");
1348     }
1349 
1350     // load the template as-is to validate signatures
1351     mxComponent->dispose();
1352     mxComponent
1353         = loadFromDesktop(aTempFileSaveAsODT_OTT.GetURL(), OUString(),
1354                           comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1355     CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1356                            mxComponent.is());
1357 
1358     // the loaded document is a OTT with a non-invalid macro signature
1359     assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::NOSIGNATURES,
1360                    SignatureState::NOTVALIDATED, OUString());
1361 }
1362 
1363 #endif
1364 
registerNamespaces(xmlXPathContextPtr & pXmlXpathCtx)1365 void SigningTest::registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx)
1366 {
1367     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("odfds"),
1368                        BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0"));
1369     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("dsig"),
1370                        BAD_CAST("http://www.w3.org/2000/09/xmldsig#"));
1371     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xd"), BAD_CAST("http://uri.etsi.org/01903/v1.3.2#"));
1372 }
1373 
1374 CPPUNIT_PLUGIN_IMPLEMENT();
1375 
1376 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1377