include/sfx2/lokhelper.hxx | 2 ++ sfx2/qa/cppunit/view.cxx | 32 ++++++++++++++++++++++++++++++++ sfx2/source/view/lokhelper.cxx | 36 ++++++++++++++++++++++++++++++++++++ sw/source/uibase/uno/loktxdoc.cxx | 24 +++--------------------- 4 files changed, 73 insertions(+), 21 deletions(-)
New commits: commit 39bf87f943cce9a0b5a784bc7426b5b98bbc6992 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Thu Nov 14 10:37:02 2024 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Thu Nov 14 17:28:21 2024 +0100 cool#9992 lok doc sign, hash extract: add signatureTime parameter Execute getCommandValues('Signature') on the same document twice, you get different hashes, because the content includes a timestamp, which changes, so it's not possible to know if the hash is stable or not. Also, working with a provided timestamp will needed for <https://docs.eideasy.com/electronic-signatures/api-flow-with-file-hashes-pdf.html#_4-add-the-signature-to-the-pdf-file> anyway. Fix the problem by adding a signatureTime parameter and this way we can have a test that makes sure we get the same hash if the time is provided. With this, the hash extraction part is meant to be ~complete. Change-Id: If5e1e5bcf84c3b777f26b2ded24dcca48ea9ad94 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176601 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/include/sfx2/lokhelper.hxx b/include/sfx2/lokhelper.hxx index ca2212139cc8..7bbacdd127ba 100644 --- a/include/sfx2/lokhelper.hxx +++ b/include/sfx2/lokhelper.hxx @@ -253,6 +253,8 @@ public: static bool supportsCommand(std::u16string_view rCommand); /// Returns information about a given command in JSON format. static void getCommandValues(tools::JsonWriter& rJsonWriter, std::string_view rCommand); + /// Parses key-value pamaters of rCommand. + static std::map<OUString, OUString> parseCommandParameters(std::u16string_view rCommand); private: static int createView(SfxViewFrame& rViewFrame, ViewShellDocId docId); diff --git a/sfx2/qa/cppunit/view.cxx b/sfx2/qa/cppunit/view.cxx index e481d7186ce0..4068270350a6 100644 --- a/sfx2/qa/cppunit/view.cxx +++ b/sfx2/qa/cppunit/view.cxx @@ -143,6 +143,38 @@ CPPUNIT_TEST_FIXTURE(Sfx2ViewTest, testLokHelperCommandValuesSignature) comphelper::Base64::decode(aBytes, aDigest); CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(32), aBytes.getLength()); } + +namespace +{ +OUString GetSignatureHash() +{ + tools::JsonWriter aWriter; + // Provide the current time, so the system timer is not contacted: + SfxLokHelper::getCommandValues(aWriter, ".uno:Signature?signatureTime=1731329053152"); + OString aJson = aWriter.finishAndGetAsOString(); + std::stringstream aStream{ std::string(aJson) }; + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + auto it = aTree.find("digest"); + CPPUNIT_ASSERT(it != aTree.not_found()); + return OUString::fromUtf8(it->second.get_value<std::string>()); +} +} + +CPPUNIT_TEST_FIXTURE(Sfx2ViewTest, testLokHelperCommandValuesSignatureHash) +{ + // Given an unsigned PDF file: + loadFromFile(u"unsigned.pdf"); + + // When extracting hashes, two times: + OUString aHash1 = GetSignatureHash(); + OUString aHash2 = GetSignatureHash(); + + // Then make sure that we get the same hash, since the same system time is provided: + // In case the test was slow enough that there was 1ms system time difference between the two + // calls, then this failed. + CPPUNIT_ASSERT_EQUAL(aHash1, aHash2); +} #endif CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sfx2/source/view/lokhelper.cxx b/sfx2/source/view/lokhelper.cxx index f8e819a2cb93..5bcb9ef22b98 100644 --- a/sfx2/source/view/lokhelper.cxx +++ b/sfx2/source/view/lokhelper.cxx @@ -43,6 +43,7 @@ #include <comphelper/base64.hxx> #include <tools/json_writer.hxx> #include <svl/cryptosign.hxx> +#include <tools/urlobj.hxx> #include <boost/property_tree/json_parser.hpp> @@ -1000,6 +1001,33 @@ bool SfxLokHelper::supportsCommand(std::u16string_view rCommand) return std::find(vSupport.begin(), vSupport.end(), rCommand) != vSupport.end(); } +std::map<OUString, OUString> SfxLokHelper::parseCommandParameters(std::u16string_view rCommand) +{ + std::map<OUString, OUString> aMap; + + INetURLObject aParser(rCommand); + OUString aArguments = aParser.GetParam(); + sal_Int32 nParamIndex = 0; + do + { + std::u16string_view aParam = o3tl::getToken(aArguments, 0, '&', nParamIndex); + sal_Int32 nIndex = 0; + OUString aKey; + OUString aValue; + do + { + std::u16string_view aToken = o3tl::getToken(aParam, 0, '=', nIndex); + if (aKey.isEmpty()) + aKey = aToken; + else + aValue = aToken; + } while (nIndex >= 0); + aMap[aKey] = INetURLObject::decode(aValue, INetURLObject::DecodeMechanism::WithCharset); + } while (nParamIndex >= 0); + + return aMap; +} + void SfxLokHelper::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_view rCommand) { static constexpr OStringLiteral aSignature(".uno:Signature"); @@ -1015,6 +1043,14 @@ void SfxLokHelper::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_ } svl::crypto::SigningContext aSigningContext; + std::map<OUString, OUString> aMap + = SfxLokHelper::parseCommandParameters(OUString::fromUtf8(rCommand)); + auto it = aMap.find("signatureTime"); + if (it != aMap.end()) + { + // Signature time is provided: prefer it over the system time. + aSigningContext.m_nSignatureTime = it->second.toInt64(); + } pObjectShell->SignDocumentContentUsingCertificate(aSigningContext); rJsonWriter.put("signatureTime", aSigningContext.m_nSignatureTime); diff --git a/sw/source/uibase/uno/loktxdoc.cxx b/sw/source/uibase/uno/loktxdoc.cxx index 7db06fe50a09..dc1c3ab74e57 100644 --- a/sw/source/uibase/uno/loktxdoc.cxx +++ b/sw/source/uibase/uno/loktxdoc.cxx @@ -30,6 +30,7 @@ #include <tools/json_writer.hxx> #include <tools/urlobj.hxx> #include <xmloff/odffields.hxx> +#include <sfx2/lokhelper.hxx> #include <IDocumentMarkAccess.hxx> #include <doc.hxx> @@ -670,8 +671,6 @@ bool SwXTextDocument::supportsCommand(std::u16string_view rCommand) void SwXTextDocument::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_view rCommand) { - std::map<OUString, OUString> aMap; - static constexpr OStringLiteral aTextFormFields(".uno:TextFormFields"); static constexpr OStringLiteral aTextFormField(".uno:TextFormField"); static constexpr OStringLiteral aSetDocumentProperties(".uno:SetDocumentProperties"); @@ -682,25 +681,8 @@ void SwXTextDocument::getCommandValues(tools::JsonWriter& rJsonWriter, std::stri static constexpr OStringLiteral aField(".uno:Field"); static constexpr OStringLiteral aExtractDocStructure(".uno:ExtractDocumentStructure"); - INetURLObject aParser(OUString::fromUtf8(rCommand)); - OUString aArguments = aParser.GetParam(); - sal_Int32 nParamIndex = 0; - do - { - std::u16string_view aParam = o3tl::getToken(aArguments, 0, '&', nParamIndex); - sal_Int32 nIndex = 0; - OUString aKey; - OUString aValue; - do - { - std::u16string_view aToken = o3tl::getToken(aParam, 0, '=', nIndex); - if (aKey.isEmpty()) - aKey = aToken; - else - aValue = aToken; - } while (nIndex >= 0); - aMap[aKey] = INetURLObject::decode(aValue, INetURLObject::DecodeMechanism::WithCharset); - } while (nParamIndex >= 0); + std::map<OUString, OUString> aMap + = SfxLokHelper::parseCommandParameters(OUString::fromUtf8(rCommand)); if (o3tl::starts_with(rCommand, aTextFormFields)) {