include/sfx2/lokhelper.hxx | 2 ++ sfx2/qa/cppunit/view.cxx | 32 ++++++++++++++++++++++++++++++++ sfx2/source/view/lokhelper.cxx | 38 ++++++++++++++++++++++++++++++++++++++ sw/source/uibase/uno/loktxdoc.cxx | 26 +++----------------------- 4 files changed, 75 insertions(+), 23 deletions(-)
New commits: commit 7dfe6cc8845413ef8673be3e1a761015fd66cc75 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Thu Nov 14 10:37:02 2024 +0100 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Thu Nov 14 15:37:55 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/+/176574 Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> diff --git a/include/sfx2/lokhelper.hxx b/include/sfx2/lokhelper.hxx index 526ad1a2aa53..2bd132da11bc 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 4f049b22ae88..ee595fc4bbd6 100644 --- a/sfx2/qa/cppunit/view.cxx +++ b/sfx2/qa/cppunit/view.cxx @@ -141,6 +141,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 c525c673b15e..5f31fcba8ebc 100644 --- a/sfx2/source/view/lokhelper.cxx +++ b/sfx2/source/view/lokhelper.cxx @@ -42,6 +42,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> @@ -998,6 +999,35 @@ 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); + OUString aDecodedValue + = INetURLObject::decode(aValue, INetURLObject::DecodeMechanism::WithCharset); + aMap[aKey] = aDecodedValue; + } while (nParamIndex >= 0); + + return aMap; +} + void SfxLokHelper::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_view rCommand) { static constexpr OStringLiteral aSignature(".uno:Signature"); @@ -1013,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 66b10556c027..c19a39a81b4f 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> @@ -876,8 +877,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"); @@ -888,27 +887,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); - OUString aDecodedValue - = INetURLObject::decode(aValue, INetURLObject::DecodeMechanism::WithCharset); - aMap[aKey] = aDecodedValue; - } while (nParamIndex >= 0); + std::map<OUString, OUString> aMap + = SfxLokHelper::parseCommandParameters(OUString::fromUtf8(rCommand)); if (o3tl::starts_with(rCommand, aTextFormFields)) {