bump product version to 6.4.0.3
[LibreOffice.git] / xmlsecurity / qa / unit / signing / signing.cxx
blobf2039b609e7ec2161556e6467165e376f4edf0d7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include <config_features.h>
11 #include <config_gpgme.h>
13 #include <sal/config.h>
15 #include <cstdlib>
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;
57 namespace
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
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;
71 #if HAVE_GPGCONF_SOCKETDIR
72 OString m_gpgconfCommandPrefix;
73 #endif
75 public:
76 SigningTest();
77 virtual void setUp() override;
78 virtual void tearDown() override;
79 void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override;
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
94 SigningTest::SigningTest() {}
96 void SigningTest::setUp()
98 test::BootstrapFixture::setUp();
100 OUString aSourceDir = m_directories.getURLFromSrc(DATA_DIRECTORY);
101 OUString aTargetDir
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 + " ";
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
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);
160 #endif
162 test::BootstrapFixture::tearDown();
165 void SigningTest::createDoc(const OUString& rURL)
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");
175 void SigningTest::createCalc(const OUString& rURL)
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");
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)
200 return xCertificate;
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.
208 createDoc("");
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())
230 return;
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.
245 createDoc("");
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())
267 return;
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
277 // broken.
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.
285 createDoc("");
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())
307 return;
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
318 // broken.
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
326 // a stream.
327 createDoc("");
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())
348 return;
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
359 // broken.
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.
367 createDoc("");
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())
389 return;
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())
428 return;
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())
464 return;
465 aManager.remove(0);
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())
500 return;
501 aManager.remove(0);
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];
516 CPPUNIT_ASSERT(
517 std::none_of(rOverrides.begin(), rOverrides.end(), [](const beans::StringPair& rPair) {
518 return rPair.First.startsWith("/_xmlsignatures/sig");
519 }));
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()));
632 #endif
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);
650 // Save a copy
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);
659 // Save As
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);
666 catch (...)
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);
687 // Save a copy
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);
696 // Save As
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);
703 catch (...)
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())
734 return;
735 sal_Int32 nSecurityId;
736 aManager.add(xCertificate, mxSecurityContext, /*rDescription=*/OUString(), nSecurityId,
737 /*bAdESCompliant=*/true);
739 // Write to storage.
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),
750 uno::UNO_QUERY);
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']",
759 "Type");
761 // New signature always has the Type attribute.
762 assertXPath(pXmlDoc,
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())
794 return;
795 sal_Int32 nSecurityId;
796 aManager.add(xCertificate, mxSecurityContext, /*rDescription=*/OUString(), nSecurityId,
797 /*bAdESCompliant=*/true);
799 // Write to storage.
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),
810 uno::UNO_QUERY);
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.
815 assertXPath(pXmlDoc,
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
824 // not missing.
825 assertXPath(pXmlDoc,
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
917 // NOTVALIDATED here
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);
959 // ODF1.3 flavour
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);
967 #endif
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);
994 return pObjectShell;
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(),
1007 mxComponent.is());
1009 // we are a template, and have a valid document and macro signature
1010 assertDocument(CPPUNIT_SOURCELINE(), "writer8_template", SignatureState::OK, SignatureState::OK,
1011 ODFVER_012_TEXT);
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(),
1024 mxComponent.is());
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);
1040 catch (...)
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);
1055 catch (...)
1057 CPPUNIT_FAIL("Failed to save OTT template");
1060 // load the saved OTT template as-is to validate signatures
1061 mxComponent->dispose();
1062 mxComponent
1063 = loadFromDesktop(aTempFileSaveAsOTT.GetURL(), OUString(),
1064 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1065 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1066 mxComponent.is());
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);
1089 catch (...)
1091 CPPUNIT_FAIL("Failed to save OTT template");
1094 // load the template as-is to validate signatures
1095 mxComponent->dispose();
1096 mxComponent
1097 = loadFromDesktop(aTempFileSaveAsODT_OTT.GetURL(), OUString(),
1098 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1099 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1100 mxComponent.is());
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(),
1117 mxComponent.is());
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(),
1134 mxComponent.is());
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);
1150 catch (...)
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(),
1167 mxComponent.is());
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);
1183 catch (...)
1185 CPPUNIT_FAIL("Failed to save OTT template");
1188 // load the template as-is to validate signatures
1189 mxComponent->dispose();
1190 mxComponent
1191 = loadFromDesktop(aTempFileSaveAsOTT.GetURL(), OUString(),
1192 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1193 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1194 mxComponent.is());
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);
1201 class Resetter
1203 private:
1204 std::function<void()> m_Func;
1206 public:
1207 Resetter(std::function<void()> const& rFunc)
1208 : m_Func(rFunc)
1211 ~Resetter()
1215 m_Func();
1217 catch (...) // has to be reliable
1219 fprintf(stderr, "resetter failed with exception\n");
1220 abort();
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
1229 Resetter _([]() {
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);
1238 pBatch->commit();
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(),
1247 mxComponent.is());
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(),
1264 mxComponent.is());
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);
1280 catch (...)
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);
1295 catch (...)
1297 CPPUNIT_FAIL("Failed to save OTT template");
1300 // load the saved OTT template as-is to validate signatures
1301 mxComponent->dispose();
1302 mxComponent
1303 = loadFromDesktop(aTempFileSaveAsOTT.GetURL(), OUString(),
1304 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1305 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1306 mxComponent.is());
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);
1329 catch (...)
1331 CPPUNIT_FAIL("Failed to save OTT template");
1334 // load the template as-is to validate signatures
1335 mxComponent->dispose();
1336 mxComponent
1337 = loadFromDesktop(aTempFileSaveAsODT_OTT.GetURL(), OUString(),
1338 comphelper::InitPropertySequence({ { "AsTemplate", uno::Any(false) } }));
1339 CPPUNIT_ASSERT_MESSAGE(OUStringToOString(sLoadMessage, RTL_TEXTENCODING_UTF8).getStr(),
1340 mxComponent.is());
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());
1347 #endif
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: */