summaryrefslogtreecommitdiffstats
path: root/xmlsecurity/source/helper/documentsignaturehelper.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /xmlsecurity/source/helper/documentsignaturehelper.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--xmlsecurity/source/helper/documentsignaturehelper.cxx592
1 files changed, 592 insertions, 0 deletions
diff --git a/xmlsecurity/source/helper/documentsignaturehelper.cxx b/xmlsecurity/source/helper/documentsignaturehelper.cxx
new file mode 100644
index 000000000..482ae6cc4
--- /dev/null
+++ b/xmlsecurity/source/helper/documentsignaturehelper.cxx
@@ -0,0 +1,592 @@
+/* -*- 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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <documentsignaturehelper.hxx>
+
+#include <algorithm>
+#include <functional>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/embed/StorageFormats.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/beans/StringPair.hpp>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+
+#include <comphelper/documentconstants.hxx>
+#include <comphelper/ofopxmlhelper.hxx>
+#include <comphelper/processfactory.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ref.hxx>
+#include <rtl/uri.hxx>
+#include <sal/log.hxx>
+#include <svx/xoutbmp.hxx>
+#include <tools/diagnose_ex.h>
+#include <xmloff/attrlist.hxx>
+
+#include <xsecctl.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace css::xml::sax;
+
+namespace
+{
+OUString getElement(OUString const & version, ::sal_Int32 * index)
+{
+ while (*index < version.getLength() && version[*index] == '0') {
+ ++*index;
+ }
+ return version.getToken(0, '.', *index);
+}
+
+
+// Return 1 if version1 is greater than version 2, 0 if they are equal
+//and -1 if version1 is less version 2
+int compareVersions(
+ OUString const & version1, OUString const & version2)
+{
+ for (::sal_Int32 i1 = 0, i2 = 0; i1 >= 0 || i2 >= 0;) {
+ OUString e1(getElement(version1, &i1));
+ OUString e2(getElement(version2, &i2));
+ if (e1.getLength() < e2.getLength()) {
+ return -1;
+ } else if (e1.getLength() > e2.getLength()) {
+ return 1;
+ } else if (e1 < e2) {
+ return -1;
+ } else if (e1 > e2) {
+ return 1;
+ }
+ }
+ return 0;
+}
+}
+
+static void ImplFillElementList(
+ std::vector< OUString >& rList, const Reference < css::embed::XStorage >& rxStore,
+ const OUString& rRootStorageName, const bool bRecursive,
+ const DocumentSignatureAlgorithm mode)
+{
+ const Sequence< OUString > aElements = rxStore->getElementNames();
+
+ for ( const auto& rName : aElements )
+ {
+ if (rName == "[Content_Types].xml")
+ // OOXML
+ continue;
+
+ // If the user enabled validating according to OOo 3.0
+ // then mimetype and all content of META-INF must be excluded.
+ if (mode != DocumentSignatureAlgorithm::OOo3_2
+ && (rName == "META-INF" || rName == "mimetype"))
+ {
+ continue;
+ }
+ else
+ {
+ OUString sEncName = ::rtl::Uri::encode(
+ rName, rtl_UriCharClassRelSegment,
+ rtl_UriEncodeStrict, RTL_TEXTENCODING_UTF8);
+ if (sEncName.isEmpty() && !rName.isEmpty())
+ throw css::uno::RuntimeException("Failed to encode element name of XStorage", nullptr);
+
+ if ( rxStore->isStreamElement( rName ) )
+ {
+ //Exclude documentsignatures.xml!
+ if (rName ==
+ DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName())
+ continue;
+ OUString aFullName( rRootStorageName + sEncName );
+ rList.push_back(aFullName);
+ }
+ else if ( bRecursive && rxStore->isStorageElement( rName ) )
+ {
+ Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( rName, css::embed::ElementModes::READ );
+ OUString aFullRootName( rRootStorageName + sEncName + "/" );
+ ImplFillElementList(rList, xSubStore, aFullRootName, bRecursive, mode);
+ }
+ }
+ }
+}
+
+
+bool DocumentSignatureHelper::isODFPre_1_2(const OUString & sVersion)
+{
+ //The property version exists only if the document is at least version 1.2
+ //That is, if the document has version 1.1 and sVersion is empty.
+ //The constant is defined in comphelper/documentconstants.hxx
+ return compareVersions(sVersion, ODFVER_012_TEXT) == -1;
+}
+
+bool DocumentSignatureHelper::isOOo3_2_Signature(const SignatureInformation & sigInfo)
+{
+ return std::any_of(sigInfo.vSignatureReferenceInfors.cbegin(),
+ sigInfo.vSignatureReferenceInfors.cend(),
+ [](const SignatureReferenceInformation& info) { return info.ouURI == "META-INF/manifest.xml"; });
+}
+
+DocumentSignatureAlgorithm
+DocumentSignatureHelper::getDocumentAlgorithm(
+ const OUString & sODFVersion, const SignatureInformation & sigInfo)
+{
+ OSL_ASSERT(!sODFVersion.isEmpty());
+ DocumentSignatureAlgorithm mode = DocumentSignatureAlgorithm::OOo3_2;
+ if (!isOOo3_2_Signature(sigInfo))
+ {
+ if (isODFPre_1_2(sODFVersion))
+ mode = DocumentSignatureAlgorithm::OOo2;
+ else
+ mode = DocumentSignatureAlgorithm::OOo3_0;
+ }
+ return mode;
+}
+
+//The function creates a list of files which are to be signed or for which
+//the signature is to be validated. The strings are UTF8 encoded URIs which
+//contain '/' as path separators.
+//
+//The algorithm how document signatures are created and validated has
+//changed over time. The change affects only which files within the document
+//are changed. Document signatures created by OOo 2.x only used particular files. Since
+//OOo 3.0 everything except "mimetype" and "META-INF" are signed. As of OOo 3.2 everything
+//except META-INF/documentsignatures.xml is signed.
+//Signatures are validated according to the algorithm which was then used for validation.
+//That is, when validating a signature which was created by OOo 3.0, then mimetype and
+//META-INF are not used.
+//
+//When a signature is created then we always use the latest algorithm. That is, we use
+//that of OOo 3.2
+std::vector< OUString >
+DocumentSignatureHelper::CreateElementList(
+ const Reference < css::embed::XStorage >& rxStore,
+ DocumentSignatureMode eMode,
+ const DocumentSignatureAlgorithm mode)
+{
+ std::vector< OUString > aElements;
+ OUString aSep( "/" );
+
+ switch ( eMode )
+ {
+ case DocumentSignatureMode::Content:
+ {
+ if (mode == DocumentSignatureAlgorithm::OOo2) //that is, ODF 1.0, 1.1
+ {
+ // 1) Main content
+ ImplFillElementList(aElements, rxStore, OUString(), false, mode);
+
+ // 2) Pictures...
+ OUString aSubStorageName( "Pictures" );
+ try
+ {
+ Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
+ ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
+ }
+ catch(css::io::IOException& )
+ {
+ ; // Doesn't have to exist...
+ }
+ // 3) OLE...
+ aSubStorageName = "ObjectReplacements";
+ try
+ {
+ Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
+ ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
+ xSubStore.clear();
+
+ // Object folders...
+ const Sequence< OUString > aElementNames = rxStore->getElementNames();
+ for ( const auto& rName : aElementNames )
+ {
+ if ( ( rName.match( "Object " ) ) && rxStore->isStorageElement( rName ) )
+ {
+ Reference < css::embed::XStorage > xTmpSubStore = rxStore->openStorageElement( rName, css::embed::ElementModes::READ );
+ ImplFillElementList(aElements, xTmpSubStore, rName+aSep, true, mode);
+ }
+ }
+ }
+ catch( css::io::IOException& )
+ {
+ ; // Doesn't have to exist...
+ }
+ }
+ else
+ {
+ // Everything except META-INF
+ ImplFillElementList(aElements, rxStore, OUString(), true, mode);
+ }
+ }
+ break;
+ case DocumentSignatureMode::Macros:
+ {
+ // 1) Macros
+ OUString aSubStorageName( "Basic" );
+ try
+ {
+ Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
+ ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
+ }
+ catch( css::io::IOException& )
+ {
+ ; // Doesn't have to exist...
+ }
+
+ // 2) Dialogs
+ aSubStorageName = "Dialogs";
+ try
+ {
+ Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
+ ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
+ }
+ catch( css::io::IOException& )
+ {
+ ; // Doesn't have to exist...
+ }
+ // 3) Scripts
+ aSubStorageName = "Scripts";
+ try
+ {
+ Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
+ ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
+ }
+ catch( css::io::IOException& )
+ {
+ ; // Doesn't have to exist...
+ }
+ }
+ break;
+ case DocumentSignatureMode::Package:
+ {
+ // Everything except META-INF
+ ImplFillElementList(aElements, rxStore, OUString(), true, mode);
+ }
+ break;
+ }
+
+ return aElements;
+}
+
+void DocumentSignatureHelper::AppendContentTypes(const uno::Reference<embed::XStorage>& xStorage, std::vector<OUString>& rElements)
+{
+ if (!xStorage.is() || !xStorage->hasByName("[Content_Types].xml"))
+ // ODF
+ return;
+
+ uno::Reference<io::XInputStream> xRelStream(xStorage->openStreamElement("[Content_Types].xml", embed::ElementModes::READ), uno::UNO_QUERY);
+ uno::Sequence< uno::Sequence<beans::StringPair> > aContentTypeInfo = comphelper::OFOPXMLHelper::ReadContentTypeSequence(xRelStream, comphelper::getProcessComponentContext());
+ if (aContentTypeInfo.getLength() < 2)
+ {
+ SAL_WARN("xmlsecurity.helper", "no defaults or overrides in aContentTypeInfo");
+ return;
+ }
+ uno::Sequence<beans::StringPair>& rDefaults = aContentTypeInfo[0];
+ uno::Sequence<beans::StringPair>& rOverrides = aContentTypeInfo[1];
+
+ for (OUString& rElement : rElements)
+ {
+ auto it = std::find_if(rOverrides.begin(), rOverrides.end(), [&](const beans::StringPair& rPair)
+ {
+ return rPair.First == "/" + rElement;
+ });
+
+ if (it != rOverrides.end())
+ {
+ rElement = "/" + rElement + "?ContentType=" + it->Second;
+ continue;
+ }
+
+ it = std::find_if(rDefaults.begin(), rDefaults.end(), [&](const beans::StringPair& rPair)
+ {
+ return rElement.endsWith("." + rPair.First);
+ });
+
+ if (it != rDefaults.end())
+ {
+ rElement = "/" + rElement + "?ContentType=" + it->Second;
+ continue;
+ }
+ SAL_WARN("xmlsecurity.helper", "found no content type for " << rElement);
+ }
+
+ std::sort(rElements.begin(), rElements.end());
+}
+
+SignatureStreamHelper DocumentSignatureHelper::OpenSignatureStream(
+ const Reference < css::embed::XStorage >& rxStore, sal_Int32 nOpenMode, DocumentSignatureMode eDocSigMode )
+{
+ sal_Int32 nSubStorageOpenMode = css::embed::ElementModes::READ;
+ if ( nOpenMode & css::embed::ElementModes::WRITE )
+ nSubStorageOpenMode = css::embed::ElementModes::WRITE;
+
+ SignatureStreamHelper aHelper;
+
+ if (!rxStore.is())
+ return aHelper;
+
+ if (rxStore->hasByName("META-INF"))
+ {
+ try
+ {
+ aHelper.xSignatureStorage = rxStore->openStorageElement( "META-INF", nSubStorageOpenMode );
+ if ( aHelper.xSignatureStorage.is() )
+ {
+ OUString aSIGStreamName;
+ if ( eDocSigMode == DocumentSignatureMode::Content )
+ aSIGStreamName = DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName();
+ else if ( eDocSigMode == DocumentSignatureMode::Macros )
+ aSIGStreamName = DocumentSignatureHelper::GetScriptingContentSignatureDefaultStreamName();
+ else
+ aSIGStreamName = DocumentSignatureHelper::GetPackageSignatureDefaultStreamName();
+
+ aHelper.xSignatureStream = aHelper.xSignatureStorage->openStreamElement( aSIGStreamName, nOpenMode );
+ }
+ }
+ catch(css::io::IOException& )
+ {
+ // Doesn't have to exist...
+ SAL_WARN_IF( nOpenMode != css::embed::ElementModes::READ, "xmlsecurity.helper", "Error creating signature stream..." );
+ }
+ }
+ else if(rxStore->hasByName("[Content_Types].xml"))
+ {
+ try
+ {
+ if (rxStore->hasByName("_xmlsignatures") && (nOpenMode & embed::ElementModes::TRUNCATE))
+ // Truncate, then all signatures will be written -> remove previous ones.
+ rxStore->removeElement("_xmlsignatures");
+
+ aHelper.xSignatureStorage = rxStore->openStorageElement("_xmlsignatures", nSubStorageOpenMode);
+ aHelper.nStorageFormat = embed::StorageFormats::OFOPXML;
+ }
+ catch (const io::IOException&)
+ {
+ TOOLS_WARN_EXCEPTION_IF(nOpenMode != css::embed::ElementModes::READ, "xmlsecurity.helper", "DocumentSignatureHelper::OpenSignatureStream:");
+ }
+ }
+
+ return aHelper;
+}
+
+/** Check whether the current file can be signed with GPG (only ODF >= 1.2 can currently) */
+bool DocumentSignatureHelper::CanSignWithGPG(
+ const Reference < css::embed::XStorage >& rxStore,
+ const OUString& sOdfVersion)
+{
+ if (!rxStore.is())
+ return false;
+
+ if (rxStore->hasByName("META-INF")) // ODF
+ {
+ return !isODFPre_1_2(sOdfVersion);
+ }
+
+ return false;
+}
+
+
+
+//sElementList contains all files which are expected to be signed. Only those files must me signed,
+//no more, no less.
+//The DocumentSignatureAlgorithm indicates if the document was created with OOo 2.x. Then
+//the uri s in the Reference elements in the signature, were not properly encoded.
+// For example: <Reference URI="ObjectReplacements/Object 1">
+bool DocumentSignatureHelper::checkIfAllFilesAreSigned(
+ const ::std::vector< OUString > & sElementList,
+ const SignatureInformation & sigInfo,
+ const DocumentSignatureAlgorithm alg)
+{
+ // Can only be valid if ALL streams are signed, which means real stream count == signed stream count
+ unsigned int nRealCount = 0;
+ std::function<OUString(const OUString&)> fEncode = [](const OUString& rStr) { return rStr; };
+ if (alg == DocumentSignatureAlgorithm::OOo2)
+ //Comparing URIs is a difficult. Therefore we kind of normalize
+ //it before comparing. We assume that our URI do not have a leading "./"
+ //and fragments at the end (...#...)
+ fEncode = [](const OUString& rStr) {
+ return rtl::Uri::encode(rStr, rtl_UriCharClassPchar, rtl_UriEncodeCheckEscapes, RTL_TEXTENCODING_UTF8);
+ };
+
+ for ( int i = sigInfo.vSignatureReferenceInfors.size(); i; )
+ {
+ const SignatureReferenceInformation& rInf = sigInfo.vSignatureReferenceInfors[--i];
+ // There is also an extra entry of type SignatureReferenceType::SAMEDOCUMENT because of signature date.
+ if ( ( rInf.nType == SignatureReferenceType::BINARYSTREAM ) || ( rInf.nType == SignatureReferenceType::XMLSTREAM ) )
+ {
+ //find the file in the element list
+ if (std::any_of(sElementList.cbegin(), sElementList.cend(),
+ [&fEncode, &rInf](const OUString& rElement) { return fEncode(rElement) == fEncode(rInf.ouURI); }))
+ nRealCount++;
+ }
+ }
+ return sElementList.size() == nRealCount;
+}
+
+/*Compares the Uri which are obtained from CreateElementList with
+ the path obtained from the manifest.xml.
+ Returns true if both strings are equal.
+*/
+bool DocumentSignatureHelper::equalsReferenceUriManifestPath(
+ const OUString & rUri, const OUString & rPath)
+{
+ //split up the uri and path into segments. Both are separated by '/'
+ std::vector<OUString> vUriSegments;
+ for (sal_Int32 nIndex = 0; nIndex >= 0; )
+ vUriSegments.push_back(rUri.getToken( 0, '/', nIndex ));
+
+ std::vector<OUString> vPathSegments;
+ for (sal_Int32 nIndex = 0; nIndex >= 0; )
+ vPathSegments.push_back(rPath.getToken( 0, '/', nIndex ));
+
+ if (vUriSegments.size() != vPathSegments.size())
+ return false;
+
+ //Now compare each segment of the uri with its counterpart from the path
+ return std::equal(
+ vUriSegments.cbegin(), vUriSegments.cend(), vPathSegments.cbegin(),
+ [](const OUString& rUriSegment, const OUString& rPathSegment) {
+ //Decode the uri segment, so that %20 becomes ' ', etc.
+ OUString sDecUri = rtl::Uri::decode(rUriSegment, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8);
+ return sDecUri == rPathSegment;
+ });
+}
+
+OUString DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName()
+{
+ return "documentsignatures.xml";
+}
+
+OUString DocumentSignatureHelper::GetScriptingContentSignatureDefaultStreamName()
+{
+ return "macrosignatures.xml";
+}
+
+OUString DocumentSignatureHelper::GetPackageSignatureDefaultStreamName()
+{
+ return "packagesignatures.xml";
+}
+
+void DocumentSignatureHelper::writeDigestMethod(
+ const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler)
+{
+ rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList());
+ pAttributeList->AddAttribute("Algorithm", ALGO_XMLDSIGSHA256);
+ xDocumentHandler->startElement("DigestMethod", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get()));
+ xDocumentHandler->endElement("DigestMethod");
+}
+
+void DocumentSignatureHelper::writeSignedProperties(
+ const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler,
+ const SignatureInformation& signatureInfo,
+ const OUString& sDate, const bool bWriteSignatureLineData)
+{
+ {
+ rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList());
+ pAttributeList->AddAttribute("Id", "idSignedProperties");
+ xDocumentHandler->startElement("xd:SignedProperties", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get()));
+ }
+
+ xDocumentHandler->startElement("xd:SignedSignatureProperties", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->startElement("xd:SigningTime", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->characters(sDate);
+ xDocumentHandler->endElement("xd:SigningTime");
+ xDocumentHandler->startElement("xd:SigningCertificate", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->startElement("xd:Cert", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->startElement("xd:CertDigest", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ writeDigestMethod(xDocumentHandler);
+
+ xDocumentHandler->startElement("DigestValue", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ // TODO: this is empty for gpg signatures currently
+ //assert(!signatureInfo.ouCertDigest.isEmpty());
+ xDocumentHandler->characters(signatureInfo.ouCertDigest);
+ xDocumentHandler->endElement("DigestValue");
+
+ xDocumentHandler->endElement("xd:CertDigest");
+ xDocumentHandler->startElement("xd:IssuerSerial", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->startElement("X509IssuerName", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->characters(signatureInfo.ouX509IssuerName);
+ xDocumentHandler->endElement("X509IssuerName");
+ xDocumentHandler->startElement("X509SerialNumber", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->characters(signatureInfo.ouX509SerialNumber);
+ xDocumentHandler->endElement("X509SerialNumber");
+ xDocumentHandler->endElement("xd:IssuerSerial");
+ xDocumentHandler->endElement("xd:Cert");
+ xDocumentHandler->endElement("xd:SigningCertificate");
+ xDocumentHandler->startElement("xd:SignaturePolicyIdentifier", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->startElement("xd:SignaturePolicyImplied", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->endElement("xd:SignaturePolicyImplied");
+ xDocumentHandler->endElement("xd:SignaturePolicyIdentifier");
+
+ if (bWriteSignatureLineData && !signatureInfo.ouSignatureLineId.isEmpty()
+ && signatureInfo.aValidSignatureImage.is() && signatureInfo.aInvalidSignatureImage.is())
+ {
+ rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList());
+ pAttributeList->AddAttribute(
+ "xmlns:loext", "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0");
+ xDocumentHandler->startElement(
+ "loext:SignatureLine",
+ Reference<XAttributeList>(pAttributeList.get()));
+
+ {
+ // Write SignatureLineId element
+ xDocumentHandler->startElement(
+ "loext:SignatureLineId",
+ Reference<XAttributeList>(new SvXMLAttributeList()));
+ xDocumentHandler->characters(signatureInfo.ouSignatureLineId);
+ xDocumentHandler->endElement("loext:SignatureLineId");
+ }
+
+ {
+ // Write SignatureLineValidImage element
+ xDocumentHandler->startElement(
+ "loext:SignatureLineValidImage",
+ Reference<XAttributeList>(new SvXMLAttributeList()));
+
+ OUString aGraphicInBase64;
+ Graphic aGraphic(signatureInfo.aValidSignatureImage);
+ if (!XOutBitmap::GraphicToBase64(aGraphic, aGraphicInBase64, false))
+ SAL_WARN("xmlsecurity.helper", "could not convert graphic to base64");
+
+ xDocumentHandler->characters(aGraphicInBase64);
+ xDocumentHandler->endElement("loext:SignatureLineValidImage");
+ }
+
+ {
+ // Write SignatureLineInvalidImage element
+ xDocumentHandler->startElement(
+ "loext:SignatureLineInvalidImage",
+ Reference<XAttributeList>(new SvXMLAttributeList()));
+ OUString aGraphicInBase64;
+ Graphic aGraphic(signatureInfo.aInvalidSignatureImage);
+ if (!XOutBitmap::GraphicToBase64(aGraphic, aGraphicInBase64, false))
+ SAL_WARN("xmlsecurity.helper", "could not convert graphic to base64");
+ xDocumentHandler->characters(aGraphicInBase64);
+ xDocumentHandler->endElement("loext:SignatureLineInvalidImage");
+ }
+
+ xDocumentHandler->endElement("loext:SignatureLine");
+ }
+
+ xDocumentHandler->endElement("xd:SignedSignatureProperties");
+
+ xDocumentHandler->endElement("xd:SignedProperties");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */