diff options
author | Ashod Nakashian <ashod.nakashian@collabora.co.uk> | 2017-08-25 20:46:33 -0400 |
---|---|---|
committer | Ashod Nakashian <ashnakash@gmail.com> | 2017-08-28 03:50:15 +0200 |
commit | 3eb31b506a2860eba3a578333a6486ee2a518c1a (patch) | |
tree | 0e3609896c98e177d25629b26569a2639307230c /sw | |
parent | 6d8598acb23bbecb55ac235c15b9e01885588ad7 (diff) |
sw: sign paragraphs and validate
Change-Id: I917ad1460c89183eec38d50de8a0de2d76239ea6
Reviewed-on: https://gerrit.libreoffice.org/41592
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Diffstat (limited to 'sw')
-rw-r--r-- | sw/inc/editsh.hxx | 4 | ||||
-rw-r--r-- | sw/inc/strings.hrc | 7 | ||||
-rw-r--r-- | sw/source/core/edit/edfcol.cxx | 267 | ||||
-rw-r--r-- | sw/source/core/edit/edws.cxx | 9 |
4 files changed, 230 insertions, 57 deletions
diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx index 411c8eed3921..f8d40ddcd0ba 100644 --- a/sw/inc/editsh.hxx +++ b/sw/inc/editsh.hxx @@ -374,6 +374,9 @@ public: /// Sign the paragraph at the cursor. void SignParagraph(SwPaM* pPaM); + /// Validate paragraph signatures, if any, at the cursor. + void ValidateParagraphSignatures(bool updateDontRemove); + void Insert2(SwField const &, const bool bForceExpandHints); void UpdateFields( SwField & ); ///< One single field. @@ -969,6 +972,7 @@ private: * the existing nb-space will be removed. Bear this in mind if that problem * arises. */ bool m_bNbspRunNext; ///< NO-BREAK SPACE state flag passed to and maintained by SvxAutoCorrect::DoAutoCorrect() + bool m_bIsValidatingParagraphSignature; ///< Prevent nested calls of ValidateParagraphSignatures. }; inline const sfx2::LinkManager& SwEditShell::GetLinkManager() const diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc index 9c42670f24e0..9e77c736bdd8 100644 --- a/sw/inc/strings.hrc +++ b/sw/inc/strings.hrc @@ -1337,6 +1337,13 @@ #define STR_MENU_UP NC_("STR_MENU_UP", "~Upwards") #define STR_MENU_DOWN NC_("STR_MENU_DOWN", "Do~wnwards") +/*-------------------------------------------------------------------- + Description: Paragraph Signature + --------------------------------------------------------------------*/ +#define STR_VALID NC_("STR_VALID", "Valid") +#define STR_INVALID NC_("STR_INVALID", "Invalid") +#define STR_SIGNED_BY NC_("STR_SIGNED_BY", "Signed-by") + #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfcol.cxx b/sw/source/core/edit/edfcol.cxx index 456d06b65b96..5dd18baaedf3 100644 --- a/sw/source/core/edit/edfcol.cxx +++ b/sw/source/core/edit/edfcol.cxx @@ -32,12 +32,18 @@ #include <com/sun/star/text/TextContentAnchorType.hpp> #include <com/sun/star/text/VertOrientation.hpp> #include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/XTextRange.hpp> #include <com/sun/star/xml/crypto/SEInitializer.hpp> +#include <com/sun/star/rdf/XMetadatable.hpp> #include <basegfx/matrix/b2dhommatrix.hxx> #include <comphelper/propertysequence.hxx> #include <comphelper/propertyvalue.hxx> +#include <comphelper/processfactory.hxx> #include <comphelper/sequence.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> #include <editeng/formatbreakitem.hxx> #include <editeng/unoprnms.hxx> #include <sfx2/classificationhelper.hxx> @@ -64,7 +70,11 @@ #include <sfx2/watermarkitem.hxx> #include <DocumentDrawModelManager.hxx> +#include <unoparagraph.hxx> +#include <unotextrange.hxx> #include <cppuhelper/bootstrap.hxx> +#include <modeltoviewhelper.hxx> +#include <strings.hrc> #define WATERMARK_NAME "PowerPlusWaterMarkObject" @@ -169,6 +179,103 @@ uno::Reference<drawing::XShape> lcl_getWatermark(const uno::Reference<text::XTex return uno::Reference<drawing::XShape>(); } +/// Extract the text of the paragraph without any of the fields. +/// TODO: Consider moving to SwTextNode, or extend ModelToViewHelper. +OString lcl_getParagraphBodyText(const uno::Reference<text::XTextContent>& xText) +{ + OUStringBuffer strBuf; + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xText, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return OString(); + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Any elem = xTextPortions->nextElement(); + + //TODO: Consider including hidden and conditional texts/portions. + OUString aTextPortionType; + uno::Reference<beans::XPropertySet> xPropertySet(elem, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType == "Text") + { + uno::Reference<text::XTextRange> xTextRange(elem, uno::UNO_QUERY); + if (xTextRange.is()) + strBuf.append(xTextRange->getString()); + } + } + + // Cleanup the dummy characters added by fields (which we exclude). + comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDSTART); + comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDEND); + comphelper::string::remove(strBuf, CH_TXTATR_BREAKWORD); + + return strBuf.makeStringAndClear().trim().toUtf8(); +} + +/// Validate and return validation result and signature field display text. +std::pair<bool, OUString> +lcl_MakeParagraphSignatureFieldText(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextField>& xField, + const OString& utf8Text) +{ + static const OUString metaNS("urn:bails"); + + OUString msg = "Invalid Signature"; + bool valid = false; + + const css::uno::Reference<css::rdf::XResource> xSubject(xField, uno::UNO_QUERY); + std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, metaNS, xSubject); + const auto it = aStatements.find("loext:signature:signature"); + if (it != aStatements.end()) + { + const sal_Char* pData = utf8Text.getStr(); + const std::vector<unsigned char> data(pData, pData + utf8Text.getLength()); + + OString encSignature; + if (it->second.convertToString(&encSignature, RTL_TEXTENCODING_UTF8, 0)) + { + const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(encSignature)); + SignatureInformation aInfo(0); + valid = svl::crypto::Signing::Verify(data, true, sig, aInfo); + valid = valid && aInfo.nStatus == css::xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; + + msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.ouSubject + ", " + aInfo.ouDateTime + ": "; + if (valid) + msg += SwResId(STR_VALID); + else + msg += SwResId(STR_INVALID); + } + } + + return std::make_pair(valid, msg); +} + +/// Updates the signature field text if changed and returns true only iff updated. +bool lcl_UpdateParagraphSignatureField(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextField>& xField, + const OString& utf8Text) +{ + const std::pair<bool, OUString> res = lcl_MakeParagraphSignatureFieldText(xModel, xField, utf8Text); + uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY); + const OUString curText = xText->getString(); + if (curText != res.second) + { + xText->setString(res.second); + return true; + } + + return false; +} + +void lcl_RemoveParagraphSignatureField(const uno::Reference<css::text::XTextField>& xField) +{ + uno::Reference<css::text::XTextContent> xFieldTextContent(xField, uno::UNO_QUERY); + uno::Reference<css::text::XTextRange> xParagraph(xFieldTextContent->getAnchor()); + uno::Reference<css::text::XText> xParagraphText(xParagraph->getText(), uno::UNO_QUERY); + xParagraphText->removeTextContent(xFieldTextContent); +} + } // anonymous namespace SwTextFormatColl& SwEditShell::GetDfltTextFormatColl() const @@ -216,7 +323,7 @@ void SwEditShell::SetClassification(const OUString& rName, SfxClassificationPoli for (const OUString& rPageStyleName : aUsedPageStyles) { uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); - OUString aServiceName = "com.sun.star.text.TextField.DocInfo.Custom"; + const OUString aServiceName = "com.sun.star.text.TextField.DocInfo.Custom"; uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); if (bHeaderIsNeeded || bWatermarkIsNeeded || bHadWatermark) @@ -572,67 +679,119 @@ void SwEditShell::SignParagraph(SwPaM* pPaM) { if (!pPaM) return; - SwDocShell* pDocShell = GetDoc()->GetDocShell(); if (!pDocShell) return; - SwWrtShell* pCurShell = pDocShell->GetWrtShell(); - if (!pCurShell) + const SwPosition* pPosStart = pPaM->Start(); + if (!pPosStart) + return; + SwTextNode* pNode = pPosStart->nNode.GetNode().GetTextNode(); + if (!pNode) return; + // 1. Get the text (without fields). + const uno::Reference<text::XTextContent> xParent = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode); + const OString utf8Text = lcl_getParagraphBodyText(xParent); + if (utf8Text.isEmpty()) + return; + + // 2. Get certificate and SignatureInformation (needed to show signer name). + //FIXME: Temporary until the Paragraph Signing Dialog is available. + uno::Reference<uno::XComponentContext> xComponentContext = cppu::defaultBootstrap_InitialComponentContext(); + uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xComponentContext); + uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString()); + uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment(); + uno::Sequence<uno::Reference<security::XCertificate>> aCertificates = xSecurityEnvironment->getPersonalCertificates(); + if (!aCertificates.hasElements()) + return; + + uno::Reference<security::XCertificate> xCert = aCertificates[0]; + if (!xCert.is()) + return; + + // 3. Sign it. + svl::crypto::Signing signing(xCert); + signing.AddDataRange(utf8Text.getStr(), utf8Text.getLength()); + OStringBuffer sigBuf; + if (!signing.Sign(sigBuf)) + return; + + const OString signature = sigBuf.makeStringAndClear(); + + // 4. Add metadata + static const OUString metaNS("urn:bails"); + static const OUString metaFile("bails.rdf"); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + uno::Reference<css::text::XTextField> xField(xMultiServiceFactory->createInstance("com.sun.star.text.textfield.MetadataField"), uno::UNO_QUERY); + + uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY); + xContent->attach(xParent->getAnchor()->getEnd()); + + uno::Reference<rdf::XResource> xRes(xField, uno::UNO_QUERY); + const OUString name = "loext:signature:signature"; + SwRDFHelper::addStatement(xModel, metaNS, metaFile, xRes, name, OStringToOUString(signature, RTL_TEXTENCODING_UTF8, 0)); + + const std::pair<bool, OUString> res = lcl_MakeParagraphSignatureFieldText(xModel, xField, utf8Text); + uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY); + xText->setString(res.second); +} + +void SwEditShell::ValidateParagraphSignatures(bool updateDontRemove) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || m_bIsValidatingParagraphSignature) + return; + + SwPaM* pPaM = GetCursor(); const SwPosition* pPosStart = pPaM->Start(); SwTextNode* pNode = pPosStart->nNode.GetNode().GetTextNode(); - if (pNode) + if (!pNode) + return; + + const uno::Reference<text::XTextContent> xParent = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode); + + // 1. Get the text (without fields). + const OString utf8Text = lcl_getParagraphBodyText(xParent); + if (utf8Text.isEmpty()) + return; + + // 2. For each signature field, update it. + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParent, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return; + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + m_bIsValidatingParagraphSignature = true; + comphelper::ScopeGuard const g([this] () { + m_bIsValidatingParagraphSignature = false; + }); + + while (xTextPortions->hasMoreElements()) { - // 1. Get the text (without fields). - const OUString text = pNode->GetText(); - if (text.isEmpty()) - return; - - // 2. Get certificate and SignatureInformation (needed to show signer name). - //FIXME: Temporary until the Paragraph Signing Dialog is available. - uno::Reference<uno::XComponentContext> xComponentContext = cppu::defaultBootstrap_InitialComponentContext(); - uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xComponentContext); - uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString()); - uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment(); - uno::Sequence<uno::Reference<security::XCertificate>> aCertificates = xSecurityEnvironment->getPersonalCertificates(); - if (!aCertificates.hasElements()) - return; - - SignatureInformation aInfo(0); - uno::Reference<security::XCertificate> xCert = aCertificates[0]; - if (!xCert.is()) - return; - - // 3. Sign it. - svl::crypto::Signing signing(xCert); - signing.AddDataRange(text.getStr(), text.getLength()); - OStringBuffer sigBuf; - if (!signing.Sign(sigBuf)) - return; - - const OString signature = sigBuf.makeStringAndClear(); - const auto pData = reinterpret_cast<const unsigned char*>(text.getStr()); - const std::vector<unsigned char> data(pData, pData + text.getLength()); - const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(signature)); - if (!svl::crypto::Signing::Verify(data, true, sig, aInfo)) - return; - - // 4. Add metadata - static const OUString metaNS("urn:bails"); - static const OUString metaFile("bails.rdf"); - - std::map<OUString, OUString> aStatements = SwRDFHelper::getTextNodeStatements(metaNS, *pNode); - OUString name = "loext:signature:index"; - const OUString indexOld = aStatements[name]; - - // Update the index - const OUString index = OUString::number(indexOld.isEmpty() ? 1 : (indexOld.toInt32() + 1)); - SwRDFHelper::updateTextNodeStatement(metaNS, metaFile, *pNode, name, indexOld, index); - - // Add the signature - name = "loext:signature:signature" + index; - SwRDFHelper::addTextNodeStatement(metaNS, metaFile, *pNode, name, OStringToOUString(signature, RTL_TEXTENCODING_UTF8, 0)); + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService("com.sun.star.text.textfield.MetadataField")) + continue; + + uno::Reference<text::XTextField> xContent(xTextField, uno::UNO_QUERY); + + const bool isUndoEnabled = GetDoc()->GetIDocumentUndoRedo().DoesUndo(); + GetDoc()->GetIDocumentUndoRedo().DoUndo(false); + if (updateDontRemove) + lcl_UpdateParagraphSignatureField(xModel, xContent, utf8Text); + else if (!lcl_MakeParagraphSignatureFieldText(xModel, xContent, utf8Text).first) + lcl_RemoveParagraphSignatureField(xContent); + + GetDoc()->GetIDocumentUndoRedo().DoUndo(isUndoEnabled); } } diff --git a/sw/source/core/edit/edws.cxx b/sw/source/core/edit/edws.cxx index 3a719b11736f..4eb1ece5d0aa 100644 --- a/sw/source/core/edit/edws.cxx +++ b/sw/source/core/edit/edws.cxx @@ -35,13 +35,16 @@ // masqueraded copy constructor SwEditShell::SwEditShell( SwEditShell& rEdSH, vcl::Window *pWindow ) - : SwCursorShell( rEdSH, pWindow ), - m_bNbspRunNext(false) // TODO: would copying that make sense? only if editing continues + : SwCursorShell( rEdSH, pWindow ) + , m_bNbspRunNext(false) // TODO: would copying that make sense? only if editing continues + , m_bIsValidatingParagraphSignature(false) { } SwEditShell::SwEditShell( SwDoc& rDoc, vcl::Window *pWindow, const SwViewOption *pOptions ) - : SwCursorShell( rDoc, pWindow, pOptions ), m_bNbspRunNext(false) + : SwCursorShell( rDoc, pWindow, pOptions ) + , m_bNbspRunNext(false) + , m_bIsValidatingParagraphSignature(false) { if (0 < officecfg::Office::Common::Undo::Steps::get()) { |