1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 #include <documentsignaturehelper.hxx>
22 
23 #include <algorithm>
24 #include <functional>
25 
26 #include <com/sun/star/container/XNameAccess.hpp>
27 #include <com/sun/star/io/IOException.hpp>
28 #include <com/sun/star/embed/XStorage.hpp>
29 #include <com/sun/star/embed/StorageFormats.hpp>
30 #include <com/sun/star/embed/ElementModes.hpp>
31 #include <com/sun/star/beans/StringPair.hpp>
32 #include <com/sun/star/xml/sax/XDocumentHandler.hpp>
33 
34 #include <comphelper/documentconstants.hxx>
35 #include <comphelper/ofopxmlhelper.hxx>
36 #include <comphelper/processfactory.hxx>
37 #include <osl/diagnose.h>
38 #include <rtl/ref.hxx>
39 #include <rtl/uri.hxx>
40 #include <sal/log.hxx>
41 #include <svx/xoutbmp.hxx>
42 #include <tools/diagnose_ex.h>
43 #include <xmloff/attrlist.hxx>
44 
45 #include <xsecctl.hxx>
46 
47 using namespace ::com::sun::star;
48 using namespace ::com::sun::star::uno;
49 using namespace css::xml::sax;
50 
51 namespace
52 {
getElement(OUString const & version,::sal_Int32 * index)53 OUString getElement(OUString const & version, ::sal_Int32 * index)
54 {
55     while (*index < version.getLength() && version[*index] == '0') {
56         ++*index;
57     }
58     return version.getToken(0, '.', *index);
59 }
60 
61 
62 // Return 1 if version1 is greater than version 2, 0 if they are equal
63 //and -1 if version1 is less version 2
compareVersions(OUString const & version1,OUString const & version2)64 int compareVersions(
65     OUString const & version1, OUString const & version2)
66 {
67     for (::sal_Int32 i1 = 0, i2 = 0; i1 >= 0 || i2 >= 0;) {
68         OUString e1(getElement(version1, &i1));
69         OUString e2(getElement(version2, &i2));
70         if (e1.getLength() < e2.getLength()) {
71             return -1;
72         } else if (e1.getLength() > e2.getLength()) {
73             return 1;
74         } else if (e1 < e2) {
75             return -1;
76         } else if (e1 > e2) {
77             return 1;
78         }
79     }
80     return 0;
81 }
82 }
83 
ImplFillElementList(std::vector<OUString> & rList,const Reference<css::embed::XStorage> & rxStore,const OUString & rRootStorageName,const bool bRecursive,const DocumentSignatureAlgorithm mode)84 static void ImplFillElementList(
85     std::vector< OUString >& rList, const Reference < css::embed::XStorage >& rxStore,
86     const OUString& rRootStorageName, const bool bRecursive,
87     const DocumentSignatureAlgorithm mode)
88 {
89     const Sequence< OUString > aElements = rxStore->getElementNames();
90 
91     for ( const auto& rName : aElements )
92     {
93         if (rName == "[Content_Types].xml")
94             // OOXML
95             continue;
96 
97         // If the user enabled validating according to OOo 3.0
98         // then mimetype and all content of META-INF must be excluded.
99         if (mode != DocumentSignatureAlgorithm::OOo3_2
100             && (rName == "META-INF" || rName == "mimetype"))
101         {
102             continue;
103         }
104         else
105         {
106             OUString sEncName = ::rtl::Uri::encode(
107                 rName, rtl_UriCharClassRelSegment,
108                 rtl_UriEncodeStrict, RTL_TEXTENCODING_UTF8);
109             if (sEncName.isEmpty() && !rName.isEmpty())
110                 throw css::uno::RuntimeException("Failed to encode element name of XStorage", nullptr);
111 
112             if ( rxStore->isStreamElement( rName ) )
113             {
114                 //Exclude documentsignatures.xml!
115                 if (rName ==
116                     DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName())
117                     continue;
118                 OUString aFullName( rRootStorageName + sEncName );
119                 rList.push_back(aFullName);
120             }
121             else if ( bRecursive && rxStore->isStorageElement( rName ) )
122             {
123                 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( rName, css::embed::ElementModes::READ );
124                 OUString aFullRootName( rRootStorageName + sEncName + "/"  );
125                 ImplFillElementList(rList, xSubStore, aFullRootName, bRecursive, mode);
126             }
127         }
128     }
129 }
130 
131 
isODFPre_1_2(const OUString & sVersion)132 bool DocumentSignatureHelper::isODFPre_1_2(const OUString & sVersion)
133 {
134     //The property version exists only if the document is at least version 1.2
135     //That is, if the document has version 1.1 and sVersion is empty.
136     //The constant is defined in comphelper/documentconstants.hxx
137     return compareVersions(sVersion, ODFVER_012_TEXT) == -1;
138 }
139 
isOOo3_2_Signature(const SignatureInformation & sigInfo)140 bool DocumentSignatureHelper::isOOo3_2_Signature(const SignatureInformation & sigInfo)
141 {
142     return std::any_of(sigInfo.vSignatureReferenceInfors.cbegin(),
143                        sigInfo.vSignatureReferenceInfors.cend(),
144                        [](const SignatureReferenceInformation& info) { return info.ouURI == "META-INF/manifest.xml"; });
145 }
146 
147 DocumentSignatureAlgorithm
getDocumentAlgorithm(const OUString & sODFVersion,const SignatureInformation & sigInfo)148 DocumentSignatureHelper::getDocumentAlgorithm(
149     const OUString & sODFVersion, const SignatureInformation & sigInfo)
150 {
151     OSL_ASSERT(!sODFVersion.isEmpty());
152     DocumentSignatureAlgorithm mode = DocumentSignatureAlgorithm::OOo3_2;
153     if (!isOOo3_2_Signature(sigInfo))
154     {
155         if (isODFPre_1_2(sODFVersion))
156             mode = DocumentSignatureAlgorithm::OOo2;
157         else
158             mode = DocumentSignatureAlgorithm::OOo3_0;
159     }
160     return mode;
161 }
162 
163 //The function creates a list of files which are to be signed or for which
164 //the signature is to be validated. The strings are UTF8 encoded URIs which
165 //contain '/' as path separators.
166 //
167 //The algorithm how document signatures are created and validated has
168 //changed over time. The change affects only which files within the document
169 //are changed. Document signatures created by OOo 2.x only used particular files. Since
170 //OOo 3.0 everything except "mimetype" and "META-INF" are signed. As of OOo 3.2 everything
171 //except META-INF/documentsignatures.xml is signed.
172 //Signatures are validated according to the algorithm which was then used for validation.
173 //That is, when validating a signature which was created by OOo 3.0, then mimetype and
174 //META-INF are not used.
175 //
176 //When a signature is created then we always use the latest algorithm. That is, we use
177 //that of OOo 3.2
178 std::vector< OUString >
CreateElementList(const Reference<css::embed::XStorage> & rxStore,DocumentSignatureMode eMode,const DocumentSignatureAlgorithm mode)179 DocumentSignatureHelper::CreateElementList(
180     const Reference < css::embed::XStorage >& rxStore,
181     DocumentSignatureMode eMode,
182     const DocumentSignatureAlgorithm mode)
183 {
184     std::vector< OUString > aElements;
185     OUString aSep(  "/"  );
186 
187     switch ( eMode )
188     {
189         case DocumentSignatureMode::Content:
190         {
191             if (mode == DocumentSignatureAlgorithm::OOo2) //that is, ODF 1.0, 1.1
192             {
193                 // 1) Main content
194                 ImplFillElementList(aElements, rxStore, OUString(), false, mode);
195 
196                 // 2) Pictures...
197                 OUString aSubStorageName( "Pictures" );
198                 try
199                 {
200                     Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
201                     ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
202                 }
203                 catch(css::io::IOException& )
204                 {
205                     ; // Doesn't have to exist...
206                 }
207                 // 3) OLE...
208                 aSubStorageName = "ObjectReplacements";
209                 try
210                 {
211                     Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
212                     ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
213                     xSubStore.clear();
214 
215                     // Object folders...
216                     const Sequence< OUString > aElementNames = rxStore->getElementNames();
217                     for ( const auto& rName : aElementNames )
218                     {
219                         if ( ( rName.match( "Object " ) ) && rxStore->isStorageElement( rName ) )
220                         {
221                             Reference < css::embed::XStorage > xTmpSubStore = rxStore->openStorageElement( rName, css::embed::ElementModes::READ );
222                             ImplFillElementList(aElements, xTmpSubStore, rName+aSep, true, mode);
223                         }
224                     }
225                 }
226                 catch( css::io::IOException& )
227                 {
228                     ; // Doesn't have to exist...
229                 }
230             }
231             else
232             {
233                 // Everything except META-INF
234                 ImplFillElementList(aElements, rxStore, OUString(), true, mode);
235             }
236         }
237         break;
238         case DocumentSignatureMode::Macros:
239         {
240             // 1) Macros
241             OUString aSubStorageName( "Basic" );
242             try
243             {
244                 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
245                 ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
246             }
247             catch( css::io::IOException& )
248             {
249                 ; // Doesn't have to exist...
250             }
251 
252             // 2) Dialogs
253             aSubStorageName = "Dialogs";
254             try
255             {
256                 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
257                 ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
258             }
259             catch( css::io::IOException& )
260             {
261                 ; // Doesn't have to exist...
262             }
263             // 3) Scripts
264             aSubStorageName = "Scripts";
265             try
266             {
267                 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ );
268                 ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode);
269             }
270             catch( css::io::IOException& )
271             {
272                 ; // Doesn't have to exist...
273             }
274         }
275         break;
276         case DocumentSignatureMode::Package:
277         {
278             // Everything except META-INF
279             ImplFillElementList(aElements, rxStore, OUString(), true, mode);
280         }
281         break;
282     }
283 
284     return aElements;
285 }
286 
AppendContentTypes(const uno::Reference<embed::XStorage> & xStorage,std::vector<OUString> & rElements)287 void DocumentSignatureHelper::AppendContentTypes(const uno::Reference<embed::XStorage>& xStorage, std::vector<OUString>& rElements)
288 {
289     if (!xStorage.is() || !xStorage->hasByName("[Content_Types].xml"))
290         // ODF
291         return;
292 
293     uno::Reference<io::XInputStream> xRelStream(xStorage->openStreamElement("[Content_Types].xml", embed::ElementModes::READ), uno::UNO_QUERY);
294     uno::Sequence< uno::Sequence<beans::StringPair> > aContentTypeInfo = comphelper::OFOPXMLHelper::ReadContentTypeSequence(xRelStream, comphelper::getProcessComponentContext());
295     if (aContentTypeInfo.getLength() < 2)
296     {
297         SAL_WARN("xmlsecurity.helper", "no defaults or overrides in aContentTypeInfo");
298         return;
299     }
300     uno::Sequence<beans::StringPair>& rDefaults = aContentTypeInfo[0];
301     uno::Sequence<beans::StringPair>& rOverrides = aContentTypeInfo[1];
302 
303     for (OUString& rElement : rElements)
304     {
305         auto it = std::find_if(rOverrides.begin(), rOverrides.end(), [&](const beans::StringPair& rPair)
306         {
307             return rPair.First == "/" + rElement;
308         });
309 
310         if (it != rOverrides.end())
311         {
312             rElement = "/" + rElement + "?ContentType=" + it->Second;
313             continue;
314         }
315 
316         it = std::find_if(rDefaults.begin(), rDefaults.end(), [&](const beans::StringPair& rPair)
317         {
318             return rElement.endsWith("." + rPair.First);
319         });
320 
321         if (it != rDefaults.end())
322         {
323             rElement = "/" + rElement + "?ContentType=" + it->Second;
324             continue;
325         }
326         SAL_WARN("xmlsecurity.helper", "found no content type for " << rElement);
327     }
328 
329     std::sort(rElements.begin(), rElements.end());
330 }
331 
OpenSignatureStream(const Reference<css::embed::XStorage> & rxStore,sal_Int32 nOpenMode,DocumentSignatureMode eDocSigMode)332 SignatureStreamHelper DocumentSignatureHelper::OpenSignatureStream(
333     const Reference < css::embed::XStorage >& rxStore, sal_Int32 nOpenMode, DocumentSignatureMode eDocSigMode )
334 {
335     sal_Int32 nSubStorageOpenMode = css::embed::ElementModes::READ;
336     if ( nOpenMode & css::embed::ElementModes::WRITE )
337         nSubStorageOpenMode = css::embed::ElementModes::WRITE;
338 
339     SignatureStreamHelper aHelper;
340 
341     if (!rxStore.is())
342         return aHelper;
343 
344     if (rxStore->hasByName("META-INF"))
345     {
346         try
347         {
348             aHelper.xSignatureStorage = rxStore->openStorageElement( "META-INF", nSubStorageOpenMode );
349             if ( aHelper.xSignatureStorage.is() )
350             {
351                 OUString aSIGStreamName;
352                 if ( eDocSigMode == DocumentSignatureMode::Content )
353                     aSIGStreamName = DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName();
354                 else if ( eDocSigMode == DocumentSignatureMode::Macros )
355                     aSIGStreamName = DocumentSignatureHelper::GetScriptingContentSignatureDefaultStreamName();
356                 else
357                     aSIGStreamName = DocumentSignatureHelper::GetPackageSignatureDefaultStreamName();
358 
359                 aHelper.xSignatureStream = aHelper.xSignatureStorage->openStreamElement( aSIGStreamName, nOpenMode );
360             }
361         }
362         catch(css::io::IOException& )
363         {
364             // Doesn't have to exist...
365             SAL_WARN_IF( nOpenMode != css::embed::ElementModes::READ, "xmlsecurity.helper", "Error creating signature stream..." );
366         }
367     }
368     else if(rxStore->hasByName("[Content_Types].xml"))
369     {
370         try
371         {
372             if (rxStore->hasByName("_xmlsignatures") && (nOpenMode & embed::ElementModes::TRUNCATE))
373                 // Truncate, then all signatures will be written -> remove previous ones.
374                 rxStore->removeElement("_xmlsignatures");
375 
376             aHelper.xSignatureStorage = rxStore->openStorageElement("_xmlsignatures", nSubStorageOpenMode);
377             aHelper.nStorageFormat = embed::StorageFormats::OFOPXML;
378         }
379         catch (const io::IOException&)
380         {
381             TOOLS_WARN_EXCEPTION_IF(nOpenMode != css::embed::ElementModes::READ, "xmlsecurity.helper", "DocumentSignatureHelper::OpenSignatureStream:");
382         }
383     }
384 
385     return aHelper;
386 }
387 
388 /** Check whether the current file can be signed with GPG (only ODF >= 1.2 can currently) */
CanSignWithGPG(const Reference<css::embed::XStorage> & rxStore,const OUString & sOdfVersion)389 bool DocumentSignatureHelper::CanSignWithGPG(
390     const Reference < css::embed::XStorage >& rxStore,
391     const OUString& sOdfVersion)
392 {
393     if (!rxStore.is())
394         return false;
395 
396     if (rxStore->hasByName("META-INF")) // ODF
397     {
398         return !isODFPre_1_2(sOdfVersion);
399     }
400 
401     return false;
402 }
403 
404 
405 
406 //sElementList contains all files which are expected to be signed. Only those files must me signed,
407 //no more, no less.
408 //The DocumentSignatureAlgorithm indicates if the document was created with OOo 2.x. Then
409 //the uri s in the Reference elements in the signature, were not properly encoded.
410 // For example: <Reference URI="ObjectReplacements/Object 1">
checkIfAllFilesAreSigned(const::std::vector<OUString> & sElementList,const SignatureInformation & sigInfo,const DocumentSignatureAlgorithm alg)411 bool DocumentSignatureHelper::checkIfAllFilesAreSigned(
412     const ::std::vector< OUString > & sElementList,
413     const SignatureInformation & sigInfo,
414     const DocumentSignatureAlgorithm alg)
415 {
416     // Can only be valid if ALL streams are signed, which means real stream count == signed stream count
417     unsigned int nRealCount = 0;
418     std::function<OUString(const OUString&)> fEncode = [](const OUString& rStr) { return rStr; };
419     if (alg == DocumentSignatureAlgorithm::OOo2)
420         //Comparing URIs is a difficult. Therefore we kind of normalize
421         //it before comparing. We assume that our URI do not have a leading "./"
422         //and fragments at the end (...#...)
423         fEncode = [](const OUString& rStr) {
424             return rtl::Uri::encode(rStr, rtl_UriCharClassPchar, rtl_UriEncodeCheckEscapes, RTL_TEXTENCODING_UTF8);
425         };
426 
427     for ( int i = sigInfo.vSignatureReferenceInfors.size(); i; )
428     {
429         const SignatureReferenceInformation& rInf = sigInfo.vSignatureReferenceInfors[--i];
430         // There is also an extra entry of type SignatureReferenceType::SAMEDOCUMENT because of signature date.
431         if ( ( rInf.nType == SignatureReferenceType::BINARYSTREAM ) || ( rInf.nType == SignatureReferenceType::XMLSTREAM ) )
432         {
433             //find the file in the element list
434             if (std::any_of(sElementList.cbegin(), sElementList.cend(),
435                     [&fEncode, &rInf](const OUString& rElement) { return fEncode(rElement) == fEncode(rInf.ouURI); }))
436                 nRealCount++;
437         }
438     }
439     return  sElementList.size() == nRealCount;
440 }
441 
442 /*Compares the Uri which are obtained from CreateElementList with
443   the  path obtained from the manifest.xml.
444   Returns true if both strings are equal.
445 */
equalsReferenceUriManifestPath(const OUString & rUri,const OUString & rPath)446 bool DocumentSignatureHelper::equalsReferenceUriManifestPath(
447     const OUString & rUri, const OUString & rPath)
448 {
449     //split up the uri and path into segments. Both are separated by '/'
450     std::vector<OUString> vUriSegments;
451     for (sal_Int32 nIndex = 0; nIndex >= 0; )
452         vUriSegments.push_back(rUri.getToken( 0, '/', nIndex ));
453 
454     std::vector<OUString> vPathSegments;
455     for (sal_Int32 nIndex = 0; nIndex >= 0; )
456         vPathSegments.push_back(rPath.getToken( 0, '/', nIndex ));
457 
458     if (vUriSegments.size() != vPathSegments.size())
459         return false;
460 
461     //Now compare each segment of the uri with its counterpart from the path
462     return std::equal(
463         vUriSegments.cbegin(), vUriSegments.cend(), vPathSegments.cbegin(),
464         [](const OUString& rUriSegment, const OUString& rPathSegment) {
465             //Decode the uri segment, so that %20 becomes ' ', etc.
466             OUString sDecUri = rtl::Uri::decode(rUriSegment, rtl_UriDecodeWithCharset,  RTL_TEXTENCODING_UTF8);
467             return sDecUri == rPathSegment;
468         });
469 }
470 
GetDocumentContentSignatureDefaultStreamName()471 OUString DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName()
472 {
473     return "documentsignatures.xml";
474 }
475 
GetScriptingContentSignatureDefaultStreamName()476 OUString DocumentSignatureHelper::GetScriptingContentSignatureDefaultStreamName()
477 {
478     return "macrosignatures.xml";
479 }
480 
GetPackageSignatureDefaultStreamName()481 OUString DocumentSignatureHelper::GetPackageSignatureDefaultStreamName()
482 {
483     return "packagesignatures.xml";
484 }
485 
writeDigestMethod(const uno::Reference<xml::sax::XDocumentHandler> & xDocumentHandler)486 void DocumentSignatureHelper::writeDigestMethod(
487     const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler)
488 {
489     rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList());
490     pAttributeList->AddAttribute("Algorithm", ALGO_XMLDSIGSHA256);
491     xDocumentHandler->startElement("DigestMethod", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get()));
492     xDocumentHandler->endElement("DigestMethod");
493 }
494 
writeSignedProperties(const uno::Reference<xml::sax::XDocumentHandler> & xDocumentHandler,const SignatureInformation & signatureInfo,const OUString & sDate,const bool bWriteSignatureLineData)495 void DocumentSignatureHelper::writeSignedProperties(
496     const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler,
497     const SignatureInformation& signatureInfo,
498     const OUString& sDate, const bool bWriteSignatureLineData)
499 {
500     {
501         rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList());
502         pAttributeList->AddAttribute("Id", "idSignedProperties");
503         xDocumentHandler->startElement("xd:SignedProperties", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get()));
504     }
505 
506     xDocumentHandler->startElement("xd:SignedSignatureProperties", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
507     xDocumentHandler->startElement("xd:SigningTime", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
508     xDocumentHandler->characters(sDate);
509     xDocumentHandler->endElement("xd:SigningTime");
510     xDocumentHandler->startElement("xd:SigningCertificate", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
511     xDocumentHandler->startElement("xd:Cert", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
512     xDocumentHandler->startElement("xd:CertDigest", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
513     writeDigestMethod(xDocumentHandler);
514 
515     xDocumentHandler->startElement("DigestValue", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
516     // TODO: this is empty for gpg signatures currently
517     //assert(!signatureInfo.ouCertDigest.isEmpty());
518     xDocumentHandler->characters(signatureInfo.ouCertDigest);
519     xDocumentHandler->endElement("DigestValue");
520 
521     xDocumentHandler->endElement("xd:CertDigest");
522     xDocumentHandler->startElement("xd:IssuerSerial", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
523     xDocumentHandler->startElement("X509IssuerName", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
524     xDocumentHandler->characters(signatureInfo.ouX509IssuerName);
525     xDocumentHandler->endElement("X509IssuerName");
526     xDocumentHandler->startElement("X509SerialNumber", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
527     xDocumentHandler->characters(signatureInfo.ouX509SerialNumber);
528     xDocumentHandler->endElement("X509SerialNumber");
529     xDocumentHandler->endElement("xd:IssuerSerial");
530     xDocumentHandler->endElement("xd:Cert");
531     xDocumentHandler->endElement("xd:SigningCertificate");
532     xDocumentHandler->startElement("xd:SignaturePolicyIdentifier", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
533     xDocumentHandler->startElement("xd:SignaturePolicyImplied", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList()));
534     xDocumentHandler->endElement("xd:SignaturePolicyImplied");
535     xDocumentHandler->endElement("xd:SignaturePolicyIdentifier");
536 
537     if (bWriteSignatureLineData && !signatureInfo.ouSignatureLineId.isEmpty()
538         && signatureInfo.aValidSignatureImage.is() && signatureInfo.aInvalidSignatureImage.is())
539     {
540         rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList());
541         pAttributeList->AddAttribute(
542             "xmlns:loext", "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0");
543         xDocumentHandler->startElement(
544             "loext:SignatureLine",
545             Reference<XAttributeList>(pAttributeList.get()));
546 
547         {
548             // Write SignatureLineId element
549             xDocumentHandler->startElement(
550                 "loext:SignatureLineId",
551                 Reference<XAttributeList>(new SvXMLAttributeList()));
552             xDocumentHandler->characters(signatureInfo.ouSignatureLineId);
553             xDocumentHandler->endElement("loext:SignatureLineId");
554         }
555 
556         {
557             // Write SignatureLineValidImage element
558             xDocumentHandler->startElement(
559                 "loext:SignatureLineValidImage",
560                 Reference<XAttributeList>(new SvXMLAttributeList()));
561 
562             OUString aGraphicInBase64;
563             Graphic aGraphic(signatureInfo.aValidSignatureImage);
564             if (!XOutBitmap::GraphicToBase64(aGraphic, aGraphicInBase64, false))
565                 SAL_WARN("xmlsecurity.helper", "could not convert graphic to base64");
566 
567             xDocumentHandler->characters(aGraphicInBase64);
568             xDocumentHandler->endElement("loext:SignatureLineValidImage");
569         }
570 
571         {
572             // Write SignatureLineInvalidImage element
573             xDocumentHandler->startElement(
574                 "loext:SignatureLineInvalidImage",
575                 Reference<XAttributeList>(new SvXMLAttributeList()));
576             OUString aGraphicInBase64;
577             Graphic aGraphic(signatureInfo.aInvalidSignatureImage);
578             if (!XOutBitmap::GraphicToBase64(aGraphic, aGraphicInBase64, false))
579                 SAL_WARN("xmlsecurity.helper", "could not convert graphic to base64");
580             xDocumentHandler->characters(aGraphicInBase64);
581             xDocumentHandler->endElement("loext:SignatureLineInvalidImage");
582         }
583 
584         xDocumentHandler->endElement("loext:SignatureLine");
585     }
586 
587     xDocumentHandler->endElement("xd:SignedSignatureProperties");
588 
589     xDocumentHandler->endElement("xd:SignedProperties");
590 }
591 
592 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
593