desktop/qa/desktop_lib/test_desktop_lib.cxx | 63 ++++++++++++++ desktop/source/lib/init.cxx | 78 +++++++++++++++++ include/LibreOfficeKit/LibreOfficeKit.h | 10 ++ include/LibreOfficeKit/LibreOfficeKit.hxx | 12 ++ include/sfx2/DocumentSigner.hxx | 41 +++++++++ sfx2/Library_sfx.mk | 1 sfx2/source/doc/DocumentSigner.cxx | 125 ++++++++++++++++++++++++++++ 7 files changed, 330 insertions(+)
New commits: commit a8cb7cf68ff661b502e7c006fe4330098b5b0944 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Mon Dec 31 12:27:39 2018 +0100 Commit: Tomaž Vajngerl <qui...@gmail.com> CommitDate: Tue Jan 1 09:53:20 2019 +0100 lok: add signDocument to "Office" iface - to sign not opened docs. LOKit function "signDocument" can sign document without the need to open them. This is useful to sign PDF documents after they are created from a currently opened (ODF, DOCX) document. This adds DocumentDigner to sfx2, which could also be used in desktop LibreOffice without opening them and in further tests. Change-Id: Id6f242e817f594aa672f94f9c6f9b34e4035d46a Reviewed-on: https://gerrit.libreoffice.org/65767 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx b/desktop/qa/desktop_lib/test_desktop_lib.cxx index 740e2d4fdecf..55d8c6761420 100644 --- a/desktop/qa/desktop_lib/test_desktop_lib.cxx +++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx @@ -126,6 +126,7 @@ public: void testInsertCertificate_DER_ODT(); void testInsertCertificate_PEM_ODT(); void testInsertCertificate_PEM_DOCX(); + void testSignDocument_PEM_PDF(); void testABI(); CPPUNIT_TEST_SUITE(DesktopLOKTest); @@ -174,6 +175,7 @@ public: CPPUNIT_TEST(testInsertCertificate_DER_ODT); CPPUNIT_TEST(testInsertCertificate_PEM_ODT); CPPUNIT_TEST(testInsertCertificate_PEM_DOCX); + CPPUNIT_TEST(testSignDocument_PEM_PDF); CPPUNIT_TEST(testABI); CPPUNIT_TEST_SUITE_END(); @@ -2485,6 +2487,66 @@ void DesktopLOKTest::testInsertCertificate_PEM_DOCX() comphelper::LibreOfficeKit::setActive(false); } +void DesktopLOKTest::testSignDocument_PEM_PDF() +{ + comphelper::LibreOfficeKit::setActive(); + + // Load the document, save it into a temp file and load that file again + LibLODocument_Impl* pDocument = loadDoc("blank_text.odt"); + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(mxComponent.is()); + pDocument->m_pDocumentClass->initializeForRendering(pDocument, "{}"); + Scheduler::ProcessEventsToIdle(); + + std::vector<unsigned char> aCertificate; + std::vector<unsigned char> aPrivateKey; + + { + readFileIntoByteVector("test-cert-chain-1.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector("test-cert-chain-2.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + { + readFileIntoByteVector("test-cert-chain-3.pem", aCertificate); + + bool bResult = pDocument->m_pDocumentClass->addCertificate( + pDocument, aCertificate.data(), int(aCertificate.size())); + CPPUNIT_ASSERT(bResult); + } + + CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, aTempFile.GetURL().toUtf8().getStr(), "pdf", nullptr)); + + closeDoc(); + + Scheduler::ProcessEventsToIdle(); + + readFileIntoByteVector("test-cert-signing.pem", aCertificate); + readFileIntoByteVector("test-PK-signing.pem", aPrivateKey); + + LibLibreOffice_Impl aOffice; + bool bResult = aOffice.m_pOfficeClass->signDocument(&aOffice, aTempFile.GetURL().toUtf8().getStr(), + aCertificate.data(), int(aCertificate.size()), + aPrivateKey.data(), int(aPrivateKey.size())); + + CPPUNIT_ASSERT(bResult); + + comphelper::LibreOfficeKit::setActive(false); +} + namespace { constexpr size_t classOffset(int i) @@ -2513,6 +2575,7 @@ void DesktopLOKTest::testABI() CPPUNIT_ASSERT_EQUAL(classOffset(8), offsetof(struct _LibreOfficeKitClass, setDocumentPassword)); CPPUNIT_ASSERT_EQUAL(classOffset(9), offsetof(struct _LibreOfficeKitClass, getVersionInfo)); CPPUNIT_ASSERT_EQUAL(classOffset(10), offsetof(struct _LibreOfficeKitClass, runMacro)); + CPPUNIT_ASSERT_EQUAL(classOffset(11), offsetof(struct _LibreOfficeKitClass, signDocument)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(0), offsetof(struct _LibreOfficeKitDocumentClass, destroy)); CPPUNIT_ASSERT_EQUAL(documentClassOffset(1), offsetof(struct _LibreOfficeKitDocumentClass, saveAs)); diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 0537d3808d61..0ec67bc6618f 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -94,6 +94,7 @@ #include <sfx2/msgpool.hxx> #include <sfx2/dispatch.hxx> #include <sfx2/lokhelper.hxx> +#include <sfx2/DocumentSigner.hxx> #include <svx/dialmgr.hxx> #include <svx/dialogs.hrc> #include <svx/strings.hrc> @@ -1423,6 +1424,13 @@ static void lo_setDocumentPassword(LibreOfficeKit* pThis, static char* lo_getVersionInfo(LibreOfficeKit* pThis); static int lo_runMacro (LibreOfficeKit* pThis, const char* pURL); +static bool lo_signDocument(LibreOfficeKit* pThis, + const char* pUrl, + const unsigned char* pCertificateBinary, + const int nCertificateBinarySize, + const unsigned char* pPrivateKeyBinary, + const int nPrivateKeyBinarySize); + LibLibreOffice_Impl::LibLibreOffice_Impl() : m_pOfficeClass( gOfficeClass.lock() ) , maThread(nullptr) @@ -1445,6 +1453,7 @@ LibLibreOffice_Impl::LibLibreOffice_Impl() m_pOfficeClass->setDocumentPassword = lo_setDocumentPassword; m_pOfficeClass->getVersionInfo = lo_getVersionInfo; m_pOfficeClass->runMacro = lo_runMacro; + m_pOfficeClass->signDocument = lo_signDocument; gOfficeClass = m_pOfficeClass; } @@ -1672,6 +1681,75 @@ static int lo_runMacro(LibreOfficeKit* pThis, const char *pURL) return false; } +static bool lo_signDocument(LibreOfficeKit* /*pThis*/, + const char* pURL, + const unsigned char* pCertificateBinary, + const int nCertificateBinarySize, + const unsigned char* pPrivateKeyBinary, + const int nPrivateKeyBinarySize) +{ + OUString aURL(getAbsoluteURL(pURL)); + if (aURL.isEmpty()) + return false; + + if (!xContext.is()) + return false; + + uno::Sequence<sal_Int8> aCertificateSequence; + + std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize); + std::string aCertificateBase64String = extractCertificate(aCertificateString); + if (!aCertificateBase64String.empty()) + { + OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String.c_str()); + comphelper::Base64::decode(aCertificateSequence, aBase64OUString); + } + else + { + aCertificateSequence.realloc(nCertificateBinarySize); + std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.begin()); + } + + uno::Sequence<sal_Int8> aPrivateKeySequence; + std::string aPrivateKeyString(reinterpret_cast<const char*>(pPrivateKeyBinary), nPrivateKeyBinarySize); + std::string aPrivateKeyBase64String = extractPrivateKey(aPrivateKeyString); + if (!aPrivateKeyBase64String.empty()) + { + OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String.c_str()); + comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString); + } + else + { + aPrivateKeySequence.realloc(nPrivateKeyBinarySize); + std::copy(pPrivateKeyBinary, pPrivateKeyBinary + nPrivateKeyBinarySize, aPrivateKeySequence.begin()); + } + + uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext); + uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext; + xSecurityContext = xSEInitializer->createSecurityContext(OUString()); + if (!xSecurityContext.is()) + return false; + + uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment; + xSecurityEnvironment = xSecurityContext->getSecurityEnvironment(); + uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY); + + if (!xCertificateCreator.is()) + return false; + + uno::Reference<security::XCertificate> xCertificate; + xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence); + + if (!xCertificate.is()) + return false; + + sfx2::DocumentSigner aDocumentSigner(aURL); + if (!aDocumentSigner.signDocument(xCertificate)) + return false; + + return true; +} + static void lo_registerCallback (LibreOfficeKit* pThis, LibreOfficeKitCallback pCallback, void* pData) diff --git a/include/LibreOfficeKit/LibreOfficeKit.h b/include/LibreOfficeKit/LibreOfficeKit.h index 4dd23a2cbc7a..2df1cea6dd31 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.h +++ b/include/LibreOfficeKit/LibreOfficeKit.h @@ -94,6 +94,16 @@ struct _LibreOfficeKitClass @since LibreOffice 6.0 */ int (*runMacro) (LibreOfficeKit *pThis, const char* pURL); + + /** @see lok::Office::signDocument(). + @since LibreOffice 6.2 + */ + bool (*signDocument) (LibreOfficeKit* pThis, + const char* pUrl, + const unsigned char* pCertificateBinary, + const int nCertificateBinarySize, + const unsigned char* pPrivateKeyBinary, + const int nPrivateKeyBinarySize); }; #define LIBREOFFICEKIT_DOCUMENT_HAS(pDoc,member) LIBREOFFICEKIT_HAS_MEMBER(LibreOfficeKitDocumentClass,member,(pDoc)->pClass->nSize) diff --git a/include/LibreOfficeKit/LibreOfficeKit.hxx b/include/LibreOfficeKit/LibreOfficeKit.hxx index 5d7771cf80b0..31e95a09c0ec 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.hxx +++ b/include/LibreOfficeKit/LibreOfficeKit.hxx @@ -791,6 +791,18 @@ public: { return mpThis->pClass->runMacro( mpThis, pURL ); } + + /** + * Exports the document and signes its content. + */ + bool signDocument(const char* pURL, + const unsigned char* pCertificateBinary, const int nCertificateBinarySize, + const unsigned char* pPrivateKeyBinary, const int nPrivateKeyBinarySize) + { + return mpThis->pClass->signDocument(mpThis, pURL, + pCertificateBinary, nCertificateBinarySize, + pPrivateKeyBinary, nPrivateKeyBinarySize); + } }; /// Factory method to create a lok::Office instance. diff --git a/include/sfx2/DocumentSigner.hxx b/include/sfx2/DocumentSigner.hxx new file mode 100644 index 000000000000..1f4326ef3976 --- /dev/null +++ b/include/sfx2/DocumentSigner.hxx @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#ifndef INCLUDED_SFX2_DOCUMENTSIGNER_HXX +#define INCLUDED_SFX2_DOCUMENTSIGNER_HXX + +#include <sal/config.h> +#include <sfx2/dllapi.h> + +#include <memory> + +#include <com/sun/star/security/XCertificate.hpp> + +namespace sfx2 +{ +class SFX2_DLLPUBLIC DocumentSigner +{ +private: + OUString m_aUrl; + +public: + DocumentSigner(OUString const& rUrl) + : m_aUrl(rUrl) + { + } + + bool signDocument(css::uno::Reference<css::security::XCertificate> const& rxCertificate); +}; + +} // namespace sfx2 + +#endif // INCLUDED_SFX2_DOCUMENTSIGNER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/Library_sfx.mk b/sfx2/Library_sfx.mk index 97f6f9706e76..18cf9396794d 100644 --- a/sfx2/Library_sfx.mk +++ b/sfx2/Library_sfx.mk @@ -194,6 +194,7 @@ $(eval $(call gb_Library_add_exception_objects,sfx,\ sfx2/source/dialog/tplpitem \ sfx2/source/dialog/versdlg \ sfx2/source/doc/DocumentMetadataAccess \ + sfx2/source/doc/DocumentSigner \ sfx2/source/doc/Metadatable \ sfx2/source/doc/QuerySaveDocument \ sfx2/source/doc/SfxDocumentMetaData \ diff --git a/sfx2/source/doc/DocumentSigner.cxx b/sfx2/source/doc/DocumentSigner.cxx new file mode 100644 index 000000000000..7e29b02b97b4 --- /dev/null +++ b/sfx2/source/doc/DocumentSigner.cxx @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <sfx2/DocumentSigner.hxx> + +#include <tools/stream.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/streamwrap.hxx> + +#include <comphelper/storagehelper.hxx> +#include <comphelper/processfactory.hxx> + +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/security/DocumentSignatureInformation.hpp> +#include <com/sun/star/security/DocumentDigitalSignatures.hpp> +#include <com/sun/star/io/WrongFormatException.hpp> +#include <com/sun/star/io/XStream.hpp> + +using namespace css; + +namespace sfx2 +{ +bool DocumentSigner::signDocument(uno::Reference<security::XCertificate> const& rxCertificate) +{ + std::unique_ptr<SvStream> pStream( + utl::UcbStreamHelper::CreateStream(m_aUrl, StreamMode::READ | StreamMode::WRITE)); + uno::Reference<io::XStream> xInputStream(new utl::OStreamWrapper(std::move(pStream))); + + bool bHasValidDocumentSignature = true; + + bool bResult = false; + uno::Reference<embed::XStorage> xWriteableZipStore; + try + { + xWriteableZipStore = comphelper::OStorageHelper::GetStorageOfFormatFromStream( + ZIP_STORAGE_FORMAT_STRING, xInputStream); + } + catch (const io::IOException&) + { + } + + OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(xWriteableZipStore)); + + uno::Reference<security::XDocumentDigitalSignatures> xSigner( + security::DocumentDigitalSignatures::createWithVersionAndValidSignature( + comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature)); + + try + { + uno::Reference<embed::XStorage> xMetaInf; + uno::Reference<container::XNameAccess> xNameAccess(xWriteableZipStore, uno::UNO_QUERY); + if (xNameAccess.is() && xNameAccess->hasByName("META-INF")) + { + xMetaInf = xWriteableZipStore->openStorageElement("META-INF", + embed::ElementModes::READWRITE); + if (!xMetaInf.is()) + throw uno::RuntimeException(); + } + if (xMetaInf.is()) + { + uno::Reference<embed::XStorage> xStorage; + xStorage = comphelper::OStorageHelper::GetStorageOfFormatFromStream( + ZIP_STORAGE_FORMAT_STRING, xInputStream); + + // ODF. + uno::Reference<io::XStream> xStream; + xStream.set( + xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), + embed::ElementModes::READWRITE), + uno::UNO_SET_THROW); + bool bSuccess = xSigner->signDocumentWithCertificate(rxCertificate, xStorage, xStream); + if (bSuccess) + { + uno::Reference<embed::XTransactedObject> xTransact(xMetaInf, uno::UNO_QUERY_THROW); + xTransact->commit(); + xTransact.set(xWriteableZipStore, uno::UNO_QUERY_THROW); + xTransact->commit(); + bResult = true; + } + } + else if (xWriteableZipStore.is()) + { + uno::Reference<embed::XStorage> xStorage; + xStorage = comphelper::OStorageHelper::GetStorageOfFormatFromStream( + ZIP_STORAGE_FORMAT_STRING, xInputStream); + + // OOXML. + uno::Reference<io::XStream> xStream; + + // We need read-write to be able to add the signature relation. + bool bSuccess = xSigner->signDocumentWithCertificate(rxCertificate, xStorage, xStream); + + if (bSuccess) + { + uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStore, + uno::UNO_QUERY_THROW); + xTransact->commit(); + bResult = true; + } + } + else + { + // Something not ZIP based: e.g. PDF. + bResult = xSigner->signDocumentWithCertificate( + rxCertificate, uno::Reference<embed::XStorage>(), xInputStream); + } + } + catch (const uno::Exception&) + { + } + return bResult; +} + +} // namespace sfx2 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits