From c2321de7ae73ad40bb98bb8293323e5fb77a7638 Mon Sep 17 00:00:00 2001 From: Tomaž Vajngerl Date: Mon, 31 Dec 2018 12:27:39 +0100 Subject: lok: add signDocument to "Office" iface - to sign not opened docs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- desktop/qa/desktop_lib/test_desktop_lib.cxx | 83 +++++++++++++++++- 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, 348 insertions(+), 2 deletions(-) create mode 100644 include/sfx2/DocumentSigner.hxx create mode 100644 sfx2/source/doc/DocumentSigner.cxx diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx b/desktop/qa/desktop_lib/test_desktop_lib.cxx index 2ec64b6cc37f..581d673d2d1d 100644 --- a/desktop/qa/desktop_lib/test_desktop_lib.cxx +++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx @@ -120,7 +120,10 @@ public: void testExtractParameter(); void testGetSignatureState_NonSigned(); void testGetSignatureState_Signed(); - void testInsertCertificate(); + void testInsertCertificate_DER_ODT(); + void testInsertCertificate_PEM_ODT(); + void testInsertCertificate_PEM_DOCX(); + void testSignDocument_PEM_PDF(); void testABI(); CPPUNIT_TEST_SUITE(DesktopLOKTest); @@ -166,7 +169,10 @@ public: CPPUNIT_TEST(testExtractParameter); CPPUNIT_TEST(testGetSignatureState_Signed); CPPUNIT_TEST(testGetSignatureState_NonSigned); - CPPUNIT_TEST(testInsertCertificate); + 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(); @@ -2390,6 +2396,66 @@ void DesktopLOKTest::testInsertCertificate() 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 aCertificate; + std::vector 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 documentClassOffset(int i) @@ -2402,6 +2468,19 @@ constexpr size_t documentClassOffset(int i) void DesktopLOKTest::testABI() { // STABLE ABI, NEVER CHANGE (unless there's a very good reason, agreed by ESC, etc.) + CPPUNIT_ASSERT_EQUAL(classOffset(0), offsetof(struct _LibreOfficeKitClass, destroy)); + CPPUNIT_ASSERT_EQUAL(classOffset(1), offsetof(struct _LibreOfficeKitClass, documentLoad)); + CPPUNIT_ASSERT_EQUAL(classOffset(2), offsetof(struct _LibreOfficeKitClass, getError)); + CPPUNIT_ASSERT_EQUAL(classOffset(3), offsetof(struct _LibreOfficeKitClass, documentLoadWithOptions)); + CPPUNIT_ASSERT_EQUAL(classOffset(4), offsetof(struct _LibreOfficeKitClass, freeError)); + CPPUNIT_ASSERT_EQUAL(classOffset(5), offsetof(struct _LibreOfficeKitClass, registerCallback)); + CPPUNIT_ASSERT_EQUAL(classOffset(6), offsetof(struct _LibreOfficeKitClass, getFilterTypes)); + CPPUNIT_ASSERT_EQUAL(classOffset(7), offsetof(struct _LibreOfficeKitClass, setOptionalFeatures)); + 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 f6462057cd9d..aa8dfe85c88f 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -95,6 +95,7 @@ #include #include #include +#include #include #include #include @@ -1445,6 +1446,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) @@ -1467,6 +1475,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; } @@ -1699,6 +1708,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 aCertificateSequence; + + std::string aCertificateString(reinterpret_cast(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 aPrivateKeySequence; + std::string aPrivateKeyString(reinterpret_cast(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 xSEInitializer = xml::crypto::SEInitializer::create(xContext); + uno::Reference xSecurityContext; + xSecurityContext = xSEInitializer->createSecurityContext(OUString()); + if (!xSecurityContext.is()) + return false; + + uno::Reference xSecurityEnvironment; + xSecurityEnvironment = xSecurityContext->getSecurityEnvironment(); + uno::Reference xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY); + + if (!xCertificateCreator.is()) + return false; + + uno::Reference 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 952f023cd26c..7a63fa2030e3 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 863f1ac48ba0..4f9ef60d0a3f 100644 --- a/include/LibreOfficeKit/LibreOfficeKit.hxx +++ b/include/LibreOfficeKit/LibreOfficeKit.hxx @@ -826,6 +826,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 +#include + +#include + +#include + +namespace sfx2 +{ +class SFX2_DLLPUBLIC DocumentSigner +{ +private: + OUString m_aUrl; + +public: + DocumentSigner(OUString const& rUrl) + : m_aUrl(rUrl) + { + } + + bool signDocument(css::uno::Reference 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 50a1d4bfd778..e7f57369b048 100644 --- a/sfx2/Library_sfx.mk +++ b/sfx2/Library_sfx.mk @@ -195,6 +195,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 + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace css; + +namespace sfx2 +{ +bool DocumentSigner::signDocument(uno::Reference const& rxCertificate) +{ + std::unique_ptr pStream( + utl::UcbStreamHelper::CreateStream(m_aUrl, StreamMode::READ | StreamMode::WRITE)); + uno::Reference xInputStream(new utl::OStreamWrapper(std::move(pStream))); + + bool bHasValidDocumentSignature = true; + + bool bResult = false; + uno::Reference xWriteableZipStore; + try + { + xWriteableZipStore = comphelper::OStorageHelper::GetStorageOfFormatFromStream( + ZIP_STORAGE_FORMAT_STRING, xInputStream); + } + catch (const io::IOException&) + { + } + + OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(xWriteableZipStore)); + + uno::Reference xSigner( + security::DocumentDigitalSignatures::createWithVersionAndValidSignature( + comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature)); + + try + { + uno::Reference xMetaInf; + uno::Reference 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 xStorage; + xStorage = comphelper::OStorageHelper::GetStorageOfFormatFromStream( + ZIP_STORAGE_FORMAT_STRING, xInputStream); + + // ODF. + uno::Reference 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 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 xStorage; + xStorage = comphelper::OStorageHelper::GetStorageOfFormatFromStream( + ZIP_STORAGE_FORMAT_STRING, xInputStream); + + // OOXML. + uno::Reference xStream; + + // We need read-write to be able to add the signature relation. + bool bSuccess = xSigner->signDocumentWithCertificate(rxCertificate, xStorage, xStream); + + if (bSuccess) + { + uno::Reference xTransact(xWriteableZipStore, + uno::UNO_QUERY_THROW); + xTransact->commit(); + bResult = true; + } + } + else + { + // Something not ZIP based: e.g. PDF. + bResult = xSigner->signDocumentWithCertificate( + rxCertificate, uno::Reference(), xInputStream); + } + } + catch (const uno::Exception&) + { + } + return bResult; +} + +} // namespace sfx2 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit