diff options
Diffstat (limited to 'xmlsecurity/source/helper')
-rw-r--r-- | xmlsecurity/source/helper/documentsignaturehelper.cxx | 592 | ||||
-rw-r--r-- | xmlsecurity/source/helper/documentsignaturemanager.cxx | 687 | ||||
-rw-r--r-- | xmlsecurity/source/helper/ooxmlsecexporter.cxx | 546 | ||||
-rw-r--r-- | xmlsecurity/source/helper/ooxmlsecexporter.hxx | 59 | ||||
-rw-r--r-- | xmlsecurity/source/helper/ooxmlsecparser.cxx | 293 | ||||
-rw-r--r-- | xmlsecurity/source/helper/ooxmlsecparser.hxx | 87 | ||||
-rw-r--r-- | xmlsecurity/source/helper/pdfsignaturehelper.cxx | 187 | ||||
-rw-r--r-- | xmlsecurity/source/helper/xmlsignaturehelper.cxx | 549 | ||||
-rw-r--r-- | xmlsecurity/source/helper/xmlsignaturehelper2.cxx | 113 | ||||
-rw-r--r-- | xmlsecurity/source/helper/xsecctl.cxx | 986 | ||||
-rw-r--r-- | xmlsecurity/source/helper/xsecparser.cxx | 530 | ||||
-rw-r--r-- | xmlsecurity/source/helper/xsecparser.hxx | 162 | ||||
-rw-r--r-- | xmlsecurity/source/helper/xsecsign.cxx | 447 | ||||
-rw-r--r-- | xmlsecurity/source/helper/xsecverify.cxx | 537 |
14 files changed, 5775 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: */ diff --git a/xmlsecurity/source/helper/documentsignaturemanager.cxx b/xmlsecurity/source/helper/documentsignaturemanager.cxx new file mode 100644 index 000000000..79d2cdf26 --- /dev/null +++ b/xmlsecurity/source/helper/documentsignaturemanager.cxx @@ -0,0 +1,687 @@ +/* -*- 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 <documentsignaturemanager.hxx> +#include <config_gpgme.h> + +#include <gpg/SEInitializer.hxx> + +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/xml/crypto/SEInitializer.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/packages/manifest/ManifestReader.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/xml/sax/XWriter.hpp> + +#include <comphelper/base64.hxx> +#include <comphelper/storagehelper.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/datetime.hxx> + +#include <certificate.hxx> +#include <biginteger.hxx> + +#include <xmlsec/xmlsec_init.hxx> + +#include <pdfsignaturehelper.hxx> + +#include <memory> + +using namespace css; +using namespace css::graphic; +using namespace css::uno; + +DocumentSignatureManager::DocumentSignatureManager( + const uno::Reference<uno::XComponentContext>& xContext, DocumentSignatureMode eMode) + : mxContext(xContext) + , maSignatureHelper(xContext) + , meSignatureMode(eMode) +{ +} + +DocumentSignatureManager::~DocumentSignatureManager() { deInitXmlSec(); } + +bool DocumentSignatureManager::init() +{ + SAL_WARN_IF(mxSEInitializer.is(), "xmlsecurity.helper", + "DocumentSignatureManager::Init - mxSEInitializer already set!"); + SAL_WARN_IF(mxSecurityContext.is(), "xmlsecurity.helper", + "DocumentSignatureManager::Init - mxSecurityContext already set!"); + SAL_WARN_IF(mxGpgSEInitializer.is(), "xmlsecurity.helper", + "DocumentSignatureManager::Init - mxGpgSEInitializer already set!"); + + // xmlsec is needed by both services, so init before those + initXmlSec(); + + mxSEInitializer = xml::crypto::SEInitializer::create(mxContext); +#if HAVE_FEATURE_GPGME + mxGpgSEInitializer.set(new SEInitializerGpg()); +#endif + + if (mxSEInitializer.is()) + mxSecurityContext = mxSEInitializer->createSecurityContext(OUString()); + +#if HAVE_FEATURE_GPGME + if (mxGpgSEInitializer.is()) + mxGpgSecurityContext = mxGpgSEInitializer->createSecurityContext(OUString()); + + return mxSecurityContext.is() || mxGpgSecurityContext.is(); +#else + return mxSecurityContext.is(); +#endif +} + +PDFSignatureHelper& DocumentSignatureManager::getPDFSignatureHelper() +{ + bool bInit = true; + if (!mxSecurityContext.is()) + bInit = init(); + + SAL_WARN_IF(!bInit, "xmlsecurity.comp", "Error initializing security context!"); + + if (!mpPDFSignatureHelper) + mpPDFSignatureHelper = std::make_unique<PDFSignatureHelper>(); + + return *mpPDFSignatureHelper; +} + +#if 0 // For some reason does not work +bool DocumentSignatureManager::IsXAdESRelevant() +{ + if (mxStore.is()) + { + // ZIP-based: ODF or OOXML. + maSignatureHelper.StartMission(); + + SignatureStreamHelper aStreamHelper = ImplOpenSignatureStream(embed::ElementModes::READ, /*bUseTempStream=*/true); + if (aStreamHelper.nStorageFormat == embed::StorageFormats::OFOPXML) + { + maSignatureHelper.EndMission(); + return false; + } + // FIXME: How to figure out if it is ODF 1.2? + maSignatureHelper.EndMission(); + return true; + } + return false; +} +#endif + +bool DocumentSignatureManager::readManifest() +{ + // Check if manifest was already read + if (m_manifest.hasElements()) + return true; + + if (!mxContext.is()) + return false; + + if (!mxStore.is()) + return false; + + uno::Reference<packages::manifest::XManifestReader> xReader + = packages::manifest::ManifestReader::create(mxContext); + + if (mxStore->hasByName("META-INF")) + { + //Get the manifest.xml + uno::Reference<embed::XStorage> xSubStore( + mxStore->openStorageElement("META-INF", embed::ElementModes::READ), UNO_SET_THROW); + + uno::Reference<io::XInputStream> xStream( + xSubStore->openStreamElement("manifest.xml", css::embed::ElementModes::READ), + UNO_QUERY_THROW); + + m_manifest = xReader->readManifestSequence(xStream); + } + return true; +} + +/* Using the zip storage, we cannot get the properties "MediaType" and "IsEncrypted" + We use the manifest to find out if a file is xml and if it is encrypted. + The parameter is an encoded uri. However, the manifest contains paths. Therefore + the path is encoded as uri, so they can be compared. +*/ +bool DocumentSignatureManager::isXML(const OUString& rURI) +{ + SAL_WARN_IF(!mxStore.is(), "xmlsecurity.helper", "empty storage reference"); + + bool bIsXML = false; + bool bPropsAvailable = false; + const OUString sPropFullPath("FullPath"); + const OUString sPropMediaType("MediaType"); + const OUString sPropDigest("Digest"); + + if (readManifest()) + { + for (const uno::Sequence<beans::PropertyValue>& entry : std::as_const(m_manifest)) + { + OUString sPath; + OUString sMediaType; + bool bEncrypted = false; + for (const beans::PropertyValue& prop : entry) + { + if (prop.Name == sPropFullPath) + prop.Value >>= sPath; + else if (prop.Name == sPropMediaType) + prop.Value >>= sMediaType; + else if (prop.Name == sPropDigest) + bEncrypted = true; + } + if (DocumentSignatureHelper::equalsReferenceUriManifestPath(rURI, sPath)) + { + bIsXML = sMediaType == "text/xml" && !bEncrypted; + bPropsAvailable = true; + break; + } + } + } + if (!bPropsAvailable) + { + //This would be the case for at least mimetype, META-INF/manifest.xml + //META-INF/macrosignatures.xml. + //Files can only be encrypted if they are in the manifest.xml. + //That is, the current file cannot be encrypted, otherwise bPropsAvailable + //would be true. + sal_Int32 nSep = rURI.lastIndexOf('.'); + if (nSep != -1) + { + OUString aExt = rURI.copy(nSep + 1); + if (aExt.equalsIgnoreAsciiCase("XML")) + bIsXML = true; + } + } + return bIsXML; +} + +//If bTempStream is true, then a temporary stream is return. If it is false then, the actual +//signature stream is used. +//Every time the user presses Add a new temporary stream is created. +//We keep the temporary stream as member because ImplGetSignatureInformations +//will later access the stream to create DocumentSignatureInformation objects +//which are stored in maCurrentSignatureInformations. +SignatureStreamHelper DocumentSignatureManager::ImplOpenSignatureStream(sal_Int32 nStreamOpenMode, + bool bTempStream) +{ + SignatureStreamHelper aHelper; + if (mxStore.is() && mxStore->hasByName("[Content_Types].xml")) + aHelper.nStorageFormat = embed::StorageFormats::OFOPXML; + + if (bTempStream) + { + if (nStreamOpenMode & embed::ElementModes::TRUNCATE) + { + //We write always into a new temporary stream. + mxTempSignatureStream.set(io::TempFile::create(mxContext), uno::UNO_QUERY_THROW); + if (aHelper.nStorageFormat != embed::StorageFormats::OFOPXML) + aHelper.xSignatureStream = mxTempSignatureStream; + else + { + mxTempSignatureStorage = comphelper::OStorageHelper::GetStorageOfFormatFromStream( + ZIP_STORAGE_FORMAT_STRING, mxTempSignatureStream); + aHelper.xSignatureStorage = mxTempSignatureStorage; + } + } + else + { + //When we read from the temp stream, then we must have previously + //created one. + SAL_WARN_IF(!mxTempSignatureStream.is(), "xmlsecurity.helper", + "empty temp. signature stream reference"); + } + aHelper.xSignatureStream = mxTempSignatureStream; + if (aHelper.nStorageFormat == embed::StorageFormats::OFOPXML) + aHelper.xSignatureStorage = mxTempSignatureStorage; + } + else + { + //No temporary stream + if (!mxSignatureStream.is()) + { + //We may not have a dedicated stream for writing the signature + //So we take one directly from the storage + //Or DocumentDigitalSignatures::showDocumentContentSignatures was called, + //in which case Add/Remove is not allowed. This is done, for example, if the + //document is readonly + aHelper = DocumentSignatureHelper::OpenSignatureStream(mxStore, nStreamOpenMode, + meSignatureMode); + } + else + { + aHelper.xSignatureStream = mxSignatureStream; + } + } + + if (nStreamOpenMode & embed::ElementModes::TRUNCATE) + { + if (aHelper.xSignatureStream.is() + && aHelper.nStorageFormat != embed::StorageFormats::OFOPXML) + { + uno::Reference<io::XTruncate> xTruncate(aHelper.xSignatureStream, uno::UNO_QUERY_THROW); + xTruncate->truncate(); + } + } + else if (bTempStream || mxSignatureStream.is()) + { + //In case we read the signature stream from the storage directly, + //which is the case when DocumentDigitalSignatures::showDocumentContentSignatures + //then XSeakable is not supported + uno::Reference<io::XSeekable> xSeek(aHelper.xSignatureStream, uno::UNO_QUERY_THROW); + xSeek->seek(0); + } + + return aHelper; +} + +bool DocumentSignatureManager::add( + const uno::Reference<security::XCertificate>& xCert, + const uno::Reference<xml::crypto::XXMLSecurityContext>& xSecurityContext, + const OUString& rDescription, sal_Int32& nSecurityId, bool bAdESCompliant, + const OUString& rSignatureLineId, const Reference<XGraphic>& xValidGraphic, + const Reference<XGraphic>& xInvalidGraphic) +{ + if (!xCert.is()) + { + SAL_WARN("xmlsecurity.helper", "no certificate selected"); + return false; + } + + // GPG or X509 key? + uno::Reference<lang::XServiceInfo> xServiceInfo(xSecurityContext, uno::UNO_QUERY); + if (xServiceInfo->getImplementationName() + == "com.sun.star.xml.security.gpg.XMLSecurityContext_GpgImpl") + { + // GPG keys only really have PGPKeyId and PGPKeyPacket + if (!mxStore.is()) + { + SAL_WARN("xmlsecurity.helper", "cannot sign pdfs with GPG keys"); + return false; + } + + maSignatureHelper.StartMission(xSecurityContext); + + nSecurityId = maSignatureHelper.GetNewSecurityId(); + + OUStringBuffer aStrBuffer; + comphelper::Base64::encode(aStrBuffer, xCert->getEncoded()); + + OUString aKeyId; + if (auto pCertificate = dynamic_cast<xmlsecurity::Certificate*>(xCert.get())) + { + OUStringBuffer aBuffer; + comphelper::Base64::encode(aBuffer, pCertificate->getSHA256Thumbprint()); + aKeyId = aBuffer.makeStringAndClear(); + } + else + SAL_WARN("xmlsecurity.helper", + "XCertificate implementation without an xmlsecurity::Certificate one"); + + maSignatureHelper.SetGpgCertificate(nSecurityId, aKeyId, aStrBuffer.makeStringAndClear(), + xCert->getIssuerName()); + } + else + { + OUString aCertSerial = xmlsecurity::bigIntegerToNumericString(xCert->getSerialNumber()); + if (aCertSerial.isEmpty()) + { + SAL_WARN("xmlsecurity.helper", "Error in Certificate, problem with serial number!"); + return false; + } + + if (!mxStore.is()) + { + // Something not ZIP based, try PDF. + nSecurityId = getPDFSignatureHelper().GetNewSecurityId(); + getPDFSignatureHelper().SetX509Certificate(xCert); + getPDFSignatureHelper().SetDescription(rDescription); + uno::Reference<io::XInputStream> xInputStream(mxSignatureStream, uno::UNO_QUERY); + if (!getPDFSignatureHelper().Sign(xInputStream, bAdESCompliant)) + { + SAL_WARN("xmlsecurity.helper", "PDFSignatureHelper::Sign() failed"); + return false; + } + return true; + } + + maSignatureHelper.StartMission(xSecurityContext); + + nSecurityId = maSignatureHelper.GetNewSecurityId(); + + OUStringBuffer aStrBuffer; + comphelper::Base64::encode(aStrBuffer, xCert->getEncoded()); + + OUString aCertDigest; + svl::crypto::SignatureMethodAlgorithm eAlgorithmID + = svl::crypto::SignatureMethodAlgorithm::RSA; + if (auto pCertificate = dynamic_cast<xmlsecurity::Certificate*>(xCert.get())) + { + OUStringBuffer aBuffer; + comphelper::Base64::encode(aBuffer, pCertificate->getSHA256Thumbprint()); + aCertDigest = aBuffer.makeStringAndClear(); + + eAlgorithmID = pCertificate->getSignatureMethodAlgorithm(); + } + else + SAL_WARN("xmlsecurity.helper", + "XCertificate implementation without an xmlsecurity::Certificate one"); + + maSignatureHelper.SetX509Certificate(nSecurityId, xCert->getIssuerName(), aCertSerial, + aStrBuffer.makeStringAndClear(), aCertDigest, + eAlgorithmID); + } + + const uno::Sequence<uno::Reference<security::XCertificate>> aCertPath + = xSecurityContext->getSecurityEnvironment()->buildCertificatePath(xCert); + + OUStringBuffer aStrBuffer; + for (uno::Reference<security::XCertificate> const& rxCertificate : aCertPath) + { + comphelper::Base64::encode(aStrBuffer, rxCertificate->getEncoded()); + OUString aString = aStrBuffer.makeStringAndClear(); + maSignatureHelper.AddEncapsulatedX509Certificate(aString); + } + + std::vector<OUString> aElements = DocumentSignatureHelper::CreateElementList( + mxStore, meSignatureMode, DocumentSignatureAlgorithm::OOo3_2); + DocumentSignatureHelper::AppendContentTypes(mxStore, aElements); + + for (OUString const& rUri : aElements) + { + bool bBinaryMode = !isXML(rUri); + maSignatureHelper.AddForSigning(nSecurityId, rUri, bBinaryMode, bAdESCompliant); + } + + maSignatureHelper.SetDateTime(nSecurityId, DateTime(DateTime::SYSTEM)); + maSignatureHelper.SetDescription(nSecurityId, rDescription); + + if (!rSignatureLineId.isEmpty()) + maSignatureHelper.SetSignatureLineId(nSecurityId, rSignatureLineId); + + if (xValidGraphic.is()) + maSignatureHelper.SetSignatureLineValidGraphic(nSecurityId, xValidGraphic); + + if (xInvalidGraphic.is()) + maSignatureHelper.SetSignatureLineInvalidGraphic(nSecurityId, xInvalidGraphic); + + // We open a signature stream in which the existing and the new + //signature is written. ImplGetSignatureInformation (later in this function) will + //then read the stream and fill maCurrentSignatureInformations. The final signature + //is written when the user presses OK. Then only maCurrentSignatureInformation and + //a sax writer are used to write the information. + SignatureStreamHelper aStreamHelper + = ImplOpenSignatureStream(embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE, true); + + if (aStreamHelper.nStorageFormat != embed::StorageFormats::OFOPXML) + { + uno::Reference<io::XOutputStream> xOutputStream(aStreamHelper.xSignatureStream, + uno::UNO_QUERY_THROW); + uno::Reference<xml::sax::XWriter> xSaxWriter + = maSignatureHelper.CreateDocumentHandlerWithHeader(xOutputStream); + + // Export old signatures... + uno::Reference<xml::sax::XDocumentHandler> xDocumentHandler(xSaxWriter, + uno::UNO_QUERY_THROW); + std::size_t nInfos = maCurrentSignatureInformations.size(); + for (std::size_t n = 0; n < nInfos; n++) + XMLSignatureHelper::ExportSignature(xDocumentHandler, maCurrentSignatureInformations[n], + bAdESCompliant); + + // Create a new one... + maSignatureHelper.CreateAndWriteSignature(xDocumentHandler, bAdESCompliant); + + // That's it... + XMLSignatureHelper::CloseDocumentHandler(xDocumentHandler); + } + else + { + // OOXML + + // Handle relations. + maSignatureHelper.EnsureSignaturesRelation(mxStore, /*bAdd=*/true); + // Old signatures + the new one. + int nSignatureCount = maCurrentSignatureInformations.size() + 1; + maSignatureHelper.ExportSignatureRelations(aStreamHelper.xSignatureStorage, + nSignatureCount); + + // Export old signatures. + for (std::size_t i = 0; i < maCurrentSignatureInformations.size(); ++i) + maSignatureHelper.ExportOOXMLSignature(mxStore, aStreamHelper.xSignatureStorage, + maCurrentSignatureInformations[i], i + 1); + + // Create a new signature. + maSignatureHelper.CreateAndWriteOOXMLSignature(mxStore, aStreamHelper.xSignatureStorage, + nSignatureCount); + + // Flush objects. + uno::Reference<embed::XTransactedObject> xTransact(aStreamHelper.xSignatureStorage, + uno::UNO_QUERY); + xTransact->commit(); + uno::Reference<io::XOutputStream> xOutputStream(aStreamHelper.xSignatureStream, + uno::UNO_QUERY); + xOutputStream->closeOutput(); + + uno::Reference<io::XTempFile> xTempFile(aStreamHelper.xSignatureStream, uno::UNO_QUERY); + SAL_INFO("xmlsecurity.helper", + "DocumentSignatureManager::add temporary storage at " << xTempFile->getUri()); + } + + maSignatureHelper.EndMission(); + return true; +} + +void DocumentSignatureManager::remove(sal_uInt16 nPosition) +{ + if (!mxStore.is()) + { + // Something not ZIP based, try PDF. + uno::Reference<io::XInputStream> xInputStream(mxSignatureStream, uno::UNO_QUERY); + if (!PDFSignatureHelper::RemoveSignature(xInputStream, nPosition)) + { + SAL_WARN("xmlsecurity.helper", "PDFSignatureHelper::RemoveSignature() failed"); + return; + } + + // Only erase when the removal was successful, it may fail for PDF. + // Also, erase the requested and all following signatures, as PDF signatures are always chained. + maCurrentSignatureInformations.erase(maCurrentSignatureInformations.begin() + nPosition, + maCurrentSignatureInformations.end()); + return; + } + + maCurrentSignatureInformations.erase(maCurrentSignatureInformations.begin() + nPosition); + + // Export all other signatures... + SignatureStreamHelper aStreamHelper = ImplOpenSignatureStream( + embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE, /*bTempStream=*/true); + + if (aStreamHelper.nStorageFormat != embed::StorageFormats::OFOPXML) + { + uno::Reference<io::XOutputStream> xOutputStream(aStreamHelper.xSignatureStream, + uno::UNO_QUERY_THROW); + uno::Reference<xml::sax::XWriter> xSaxWriter + = maSignatureHelper.CreateDocumentHandlerWithHeader(xOutputStream); + + uno::Reference<xml::sax::XDocumentHandler> xDocumentHandler(xSaxWriter, + uno::UNO_QUERY_THROW); + std::size_t nInfos = maCurrentSignatureInformations.size(); + for (std::size_t n = 0; n < nInfos; ++n) + XMLSignatureHelper::ExportSignature(xDocumentHandler, maCurrentSignatureInformations[n], + false /* ??? */); + + XMLSignatureHelper::CloseDocumentHandler(xDocumentHandler); + } + else + { + // OOXML + + // Handle relations. + int nSignatureCount = maCurrentSignatureInformations.size(); + maSignatureHelper.ExportSignatureRelations(aStreamHelper.xSignatureStorage, + nSignatureCount); + + // Export old signatures. + for (std::size_t i = 0; i < maCurrentSignatureInformations.size(); ++i) + maSignatureHelper.ExportOOXMLSignature(mxStore, aStreamHelper.xSignatureStorage, + maCurrentSignatureInformations[i], i + 1); + + // Flush objects. + uno::Reference<embed::XTransactedObject> xTransact(aStreamHelper.xSignatureStorage, + uno::UNO_QUERY); + xTransact->commit(); + uno::Reference<io::XOutputStream> xOutputStream(aStreamHelper.xSignatureStream, + uno::UNO_QUERY); + xOutputStream->closeOutput(); + + uno::Reference<io::XTempFile> xTempFile(aStreamHelper.xSignatureStream, uno::UNO_QUERY); + SAL_INFO("xmlsecurity.helper", "DocumentSignatureManager::remove: temporary storage is at " + << xTempFile->getUri()); + } +} + +void DocumentSignatureManager::read(bool bUseTempStream, bool bCacheLastSignature) +{ + maCurrentSignatureInformations.clear(); + + if (mxStore.is()) + { + // ZIP-based: ODF or OOXML. + maSignatureHelper.StartMission(mxSecurityContext); + + SignatureStreamHelper aStreamHelper + = ImplOpenSignatureStream(embed::ElementModes::READ, bUseTempStream); + if (aStreamHelper.nStorageFormat != embed::StorageFormats::OFOPXML + && aStreamHelper.xSignatureStream.is()) + { + uno::Reference<io::XInputStream> xInputStream(aStreamHelper.xSignatureStream, + uno::UNO_QUERY); + maSignatureHelper.ReadAndVerifySignature(xInputStream); + } + else if (aStreamHelper.nStorageFormat == embed::StorageFormats::OFOPXML + && aStreamHelper.xSignatureStorage.is()) + maSignatureHelper.ReadAndVerifySignatureStorage(aStreamHelper.xSignatureStorage, + bCacheLastSignature); + maSignatureHelper.EndMission(); + + maCurrentSignatureInformations = maSignatureHelper.GetSignatureInformations(); + } + else + { + // Something not ZIP based, try PDF. + uno::Reference<io::XInputStream> xInputStream(mxSignatureStream, uno::UNO_QUERY); + if (getPDFSignatureHelper().ReadAndVerifySignature(xInputStream)) + maCurrentSignatureInformations = getPDFSignatureHelper().GetSignatureInformations(); + } +} + +void DocumentSignatureManager::write(bool bXAdESCompliantIfODF) +{ + if (!mxStore.is()) + { + // Something not ZIP based, assume PDF, which is written directly in add() already. + return; + } + + // Export all other signatures... + SignatureStreamHelper aStreamHelper = ImplOpenSignatureStream( + embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE, false); + + if (aStreamHelper.xSignatureStream.is() + && aStreamHelper.nStorageFormat != embed::StorageFormats::OFOPXML) + { + // ODF + uno::Reference<io::XOutputStream> xOutputStream(aStreamHelper.xSignatureStream, + uno::UNO_QUERY); + uno::Reference<xml::sax::XWriter> xSaxWriter + = maSignatureHelper.CreateDocumentHandlerWithHeader(xOutputStream); + + uno::Reference<xml::sax::XDocumentHandler> xDocumentHandler(xSaxWriter, + uno::UNO_QUERY_THROW); + std::size_t nInfos = maCurrentSignatureInformations.size(); + for (std::size_t n = 0; n < nInfos; ++n) + XMLSignatureHelper::ExportSignature(xDocumentHandler, maCurrentSignatureInformations[n], + bXAdESCompliantIfODF); + + XMLSignatureHelper::CloseDocumentHandler(xDocumentHandler); + } + else if (aStreamHelper.xSignatureStorage.is() + && aStreamHelper.nStorageFormat == embed::StorageFormats::OFOPXML) + { + // OOXML + std::size_t nSignatureCount = maCurrentSignatureInformations.size(); + maSignatureHelper.ExportSignatureContentTypes(mxStore, nSignatureCount); + if (nSignatureCount > 0) + maSignatureHelper.ExportSignatureRelations(aStreamHelper.xSignatureStorage, + nSignatureCount); + else + { + // Removing all signatures: then need to remove the signature relation as well. + maSignatureHelper.EnsureSignaturesRelation(mxStore, /*bAdd=*/false); + // Also remove the whole signature sub-storage: release our read-write reference + remove the element. + aStreamHelper = SignatureStreamHelper(); + mxStore->removeElement("_xmlsignatures"); + } + + for (std::size_t i = 0; i < nSignatureCount; ++i) + maSignatureHelper.ExportOOXMLSignature(mxStore, aStreamHelper.xSignatureStorage, + maCurrentSignatureInformations[i], i + 1); + } + + // If stream was not provided, we are responsible for committing it... + if (!mxSignatureStream.is() && aStreamHelper.xSignatureStorage.is()) + { + uno::Reference<embed::XTransactedObject> xTrans(aStreamHelper.xSignatureStorage, + uno::UNO_QUERY); + xTrans->commit(); + } +} + +uno::Reference<xml::crypto::XSecurityEnvironment> DocumentSignatureManager::getSecurityEnvironment() +{ + return mxSecurityContext.is() ? mxSecurityContext->getSecurityEnvironment() + : uno::Reference<xml::crypto::XSecurityEnvironment>(); +} + +uno::Reference<xml::crypto::XSecurityEnvironment> +DocumentSignatureManager::getGpgSecurityEnvironment() +{ + return mxGpgSecurityContext.is() ? mxGpgSecurityContext->getSecurityEnvironment() + : uno::Reference<xml::crypto::XSecurityEnvironment>(); +} + +uno::Reference<xml::crypto::XXMLSecurityContext> const& +DocumentSignatureManager::getSecurityContext() const +{ + return mxSecurityContext; +} + +uno::Reference<xml::crypto::XXMLSecurityContext> const& +DocumentSignatureManager::getGpgSecurityContext() const +{ + return mxGpgSecurityContext; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/ooxmlsecexporter.cxx b/xmlsecurity/source/helper/ooxmlsecexporter.cxx new file mode 100644 index 000000000..fe4d0df89 --- /dev/null +++ b/xmlsecurity/source/helper/ooxmlsecexporter.cxx @@ -0,0 +1,546 @@ +/* -*- 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/. + */ + +#include "ooxmlsecexporter.hxx" + +#include <algorithm> +#include <memory> + +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XHierarchicalStorageAccess.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> + +#include <comphelper/ofopxmlhelper.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> +#include <svx/xoutbmp.hxx> +#include <unotools/datetime.hxx> +#include <vcl/salctype.hxx> +#include <xmloff/attrlist.hxx> + +#include <documentsignaturehelper.hxx> +#include <xsecctl.hxx> + +using namespace com::sun::star; +using namespace css::xml::sax; + +struct OOXMLSecExporter::Impl +{ +private: + const uno::Reference<uno::XComponentContext>& m_xComponentContext; + const uno::Reference<embed::XStorage>& m_xRootStorage; + const uno::Reference<xml::sax::XDocumentHandler>& m_xDocumentHandler; + const SignatureInformation& m_rInformation; + OUString m_aSignatureTimeValue; + +public: + Impl(const uno::Reference<uno::XComponentContext>& xComponentContext, + const uno::Reference<embed::XStorage>& xRootStorage, + const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler, + const SignatureInformation& rInformation) + : m_xComponentContext(xComponentContext) + , m_xRootStorage(xRootStorage) + , m_xDocumentHandler(xDocumentHandler) + , m_rInformation(rInformation) + { + } + + /// Should we intentionally not sign this stream? + static bool isOOXMLBlacklist(const OUString& rStreamName); + /// Should we intentionally not sign this relation type? + static bool isOOXMLRelationBlacklist(const OUString& rRelationName); + + const uno::Reference<xml::sax::XDocumentHandler>& getDocumentHandler() const + { + return m_xDocumentHandler; + } + + void writeSignedInfo(); + void writeCanonicalizationMethod(); + void writeCanonicalizationTransform(); + void writeSignatureMethod(); + void writeSignedInfoReferences(); + void writeSignatureValue(); + void writeKeyInfo(); + void writePackageObject(); + void writeManifest(); + void writeRelationshipTransform(const OUString& rURI); + /// Writes <SignatureProperties> inside idPackageObject. + void writePackageObjectSignatureProperties(); + /// Writes a single <Reference> inside <Manifest>. + void writeManifestReference(const SignatureReferenceInformation& rReference); + void writeOfficeObject(); + /// Writes <SignatureInfoV1>. + void writeSignatureInfo(); + void writePackageSignature(); + void writeSignatureLineImages(); +}; + +bool OOXMLSecExporter::Impl::isOOXMLBlacklist(const OUString& rStreamName) +{ + static const std::initializer_list<OUStringLiteral> vBlacklist + = { "/%5BContent_Types%5D.xml", "/docProps/app.xml", "/docProps/core.xml", + // Don't attempt to sign other signatures for now. + "/_xmlsignatures" }; + // Just check the prefix, as we don't care about the content type part of the stream name. + return std::any_of(vBlacklist.begin(), vBlacklist.end(), [&](const OUStringLiteral& rLiteral) { + return rStreamName.startsWith(rLiteral); + }); +} + +bool OOXMLSecExporter::Impl::isOOXMLRelationBlacklist(const OUString& rRelationName) +{ + static const std::initializer_list<OUStringLiteral> vBlacklist = { + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", + "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", + "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin" + }; + return std::find(vBlacklist.begin(), vBlacklist.end(), rRelationName) != vBlacklist.end(); +} + +void OOXMLSecExporter::Impl::writeSignedInfo() +{ + m_xDocumentHandler->startElement( + "SignedInfo", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + + writeCanonicalizationMethod(); + writeSignatureMethod(); + writeSignedInfoReferences(); + + m_xDocumentHandler->endElement("SignedInfo"); +} + +void OOXMLSecExporter::Impl::writeCanonicalizationMethod() +{ + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Algorithm", ALGO_C14N); + m_xDocumentHandler->startElement( + "CanonicalizationMethod", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + m_xDocumentHandler->endElement("CanonicalizationMethod"); +} + +void OOXMLSecExporter::Impl::writeCanonicalizationTransform() +{ + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Algorithm", ALGO_C14N); + m_xDocumentHandler->startElement( + "Transform", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + m_xDocumentHandler->endElement("Transform"); +} + +void OOXMLSecExporter::Impl::writeSignatureMethod() +{ + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + + if (m_rInformation.eAlgorithmID == svl::crypto::SignatureMethodAlgorithm::ECDSA) + pAttributeList->AddAttribute("Algorithm", ALGO_ECDSASHA256); + else + pAttributeList->AddAttribute("Algorithm", ALGO_RSASHA256); + + m_xDocumentHandler->startElement( + "SignatureMethod", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + m_xDocumentHandler->endElement("SignatureMethod"); +} + +void OOXMLSecExporter::Impl::writeSignedInfoReferences() +{ + const SignatureReferenceInformations& rReferences = m_rInformation.vSignatureReferenceInfors; + for (const SignatureReferenceInformation& rReference : rReferences) + { + if (rReference.nType == SignatureReferenceType::SAMEDOCUMENT) + { + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + if (rReference.ouURI != "idSignedProperties") + pAttributeList->AddAttribute("Type", + "http://www.w3.org/2000/09/xmldsig#Object"); + else + pAttributeList->AddAttribute("Type", + "http://uri.etsi.org/01903#SignedProperties"); + pAttributeList->AddAttribute("URI", "#" + rReference.ouURI); + m_xDocumentHandler->startElement( + "Reference", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + } + if (rReference.ouURI == "idSignedProperties") + { + m_xDocumentHandler->startElement( + "Transforms", + uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + writeCanonicalizationTransform(); + m_xDocumentHandler->endElement("Transforms"); + } + + DocumentSignatureHelper::writeDigestMethod(m_xDocumentHandler); + m_xDocumentHandler->startElement( + "DigestValue", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters(rReference.ouDigestValue); + m_xDocumentHandler->endElement("DigestValue"); + m_xDocumentHandler->endElement("Reference"); + } + } +} + +void OOXMLSecExporter::Impl::writeSignatureValue() +{ + m_xDocumentHandler->startElement( + "SignatureValue", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters(m_rInformation.ouSignatureValue); + m_xDocumentHandler->endElement("SignatureValue"); +} + +void OOXMLSecExporter::Impl::writeKeyInfo() +{ + m_xDocumentHandler->startElement( + "KeyInfo", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->startElement( + "X509Data", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->startElement( + "X509Certificate", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters(m_rInformation.ouX509Certificate); + m_xDocumentHandler->endElement("X509Certificate"); + m_xDocumentHandler->endElement("X509Data"); + m_xDocumentHandler->endElement("KeyInfo"); +} + +void OOXMLSecExporter::Impl::writePackageObject() +{ + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Id", "idPackageObject"); + m_xDocumentHandler->startElement( + "Object", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + + writeManifest(); + writePackageObjectSignatureProperties(); + + m_xDocumentHandler->endElement("Object"); +} + +void OOXMLSecExporter::Impl::writeManifest() +{ + m_xDocumentHandler->startElement( + "Manifest", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + const SignatureReferenceInformations& rReferences = m_rInformation.vSignatureReferenceInfors; + for (const SignatureReferenceInformation& rReference : rReferences) + { + if (rReference.nType != SignatureReferenceType::SAMEDOCUMENT) + { + if (OOXMLSecExporter::Impl::isOOXMLBlacklist(rReference.ouURI)) + continue; + + writeManifestReference(rReference); + } + } + m_xDocumentHandler->endElement("Manifest"); +} + +void OOXMLSecExporter::Impl::writeRelationshipTransform(const OUString& rURI) +{ + uno::Reference<embed::XHierarchicalStorageAccess> xHierarchicalStorageAccess(m_xRootStorage, + uno::UNO_QUERY); + uno::Reference<io::XInputStream> xRelStream( + xHierarchicalStorageAccess->openStreamElementByHierarchicalName(rURI, + embed::ElementModes::READ), + uno::UNO_QUERY); + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Algorithm", ALGO_RELATIONSHIP); + m_xDocumentHandler->startElement( + "Transform", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + } + + const uno::Sequence<uno::Sequence<beans::StringPair>> aRelationsInfo + = comphelper::OFOPXMLHelper::ReadRelationsInfoSequence(xRelStream, rURI, + m_xComponentContext); + for (const uno::Sequence<beans::StringPair>& rPairs : aRelationsInfo) + { + OUString aId; + OUString aType; + for (const beans::StringPair& rPair : rPairs) + { + if (rPair.First == "Id") + aId = rPair.Second; + else if (rPair.First == "Type") + aType = rPair.Second; + } + + if (OOXMLSecExporter::Impl::isOOXMLRelationBlacklist(aType)) + continue; + + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("xmlns:mdssi", NS_MDSSI); + pAttributeList->AddAttribute("SourceId", aId); + m_xDocumentHandler->startElement( + "mdssi:RelationshipReference", + uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + m_xDocumentHandler->endElement("mdssi:RelationshipReference"); + } + + m_xDocumentHandler->endElement("Transform"); +} + +void OOXMLSecExporter::Impl::writePackageObjectSignatureProperties() +{ + m_xDocumentHandler->startElement( + "SignatureProperties", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Id", "idSignatureTime"); + pAttributeList->AddAttribute("Target", "#idPackageSignature"); + m_xDocumentHandler->startElement( + "SignatureProperty", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + } + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("xmlns:mdssi", NS_MDSSI); + m_xDocumentHandler->startElement( + "mdssi:SignatureTime", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + } + m_xDocumentHandler->startElement( + "mdssi:Format", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("YYYY-MM-DDThh:mm:ssTZD"); + m_xDocumentHandler->endElement("mdssi:Format"); + + m_xDocumentHandler->startElement( + "mdssi:Value", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + if (!m_rInformation.ouDateTime.isEmpty()) + m_aSignatureTimeValue = m_rInformation.ouDateTime; + else + { + m_aSignatureTimeValue = utl::toISO8601(m_rInformation.stDateTime); + // Ignore sub-seconds. + sal_Int32 nCommaPos = m_aSignatureTimeValue.indexOf(','); + if (nCommaPos != -1) + { + m_aSignatureTimeValue = m_aSignatureTimeValue.copy(0, nCommaPos); + m_aSignatureTimeValue += "Z"; + } + } + m_xDocumentHandler->characters(m_aSignatureTimeValue); + m_xDocumentHandler->endElement("mdssi:Value"); + + m_xDocumentHandler->endElement("mdssi:SignatureTime"); + m_xDocumentHandler->endElement("SignatureProperty"); + m_xDocumentHandler->endElement("SignatureProperties"); +} + +void OOXMLSecExporter::Impl::writeManifestReference(const SignatureReferenceInformation& rReference) +{ + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("URI", rReference.ouURI); + m_xDocumentHandler->startElement( + "Reference", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + + // Transforms + if (rReference.ouURI.endsWith( + "?ContentType=application/vnd.openxmlformats-package.relationships+xml")) + { + OUString aURI = rReference.ouURI; + // Ignore leading slash. + if (aURI.startsWith("/")) + aURI = aURI.copy(1); + // Ignore query part of the URI. + sal_Int32 nQueryPos = aURI.indexOf('?'); + if (nQueryPos != -1) + aURI = aURI.copy(0, nQueryPos); + + m_xDocumentHandler->startElement( + "Transforms", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + + writeRelationshipTransform(aURI); + writeCanonicalizationTransform(); + + m_xDocumentHandler->endElement("Transforms"); + } + + DocumentSignatureHelper::writeDigestMethod(m_xDocumentHandler); + m_xDocumentHandler->startElement( + "DigestValue", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters(rReference.ouDigestValue); + m_xDocumentHandler->endElement("DigestValue"); + m_xDocumentHandler->endElement("Reference"); +} + +void OOXMLSecExporter::Impl::writeOfficeObject() +{ + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Id", "idOfficeObject"); + m_xDocumentHandler->startElement( + "Object", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + } + m_xDocumentHandler->startElement( + "SignatureProperties", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Id", "idOfficeV1Details"); + pAttributeList->AddAttribute("Target", "#idPackageSignature"); + m_xDocumentHandler->startElement( + "SignatureProperty", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + } + writeSignatureInfo(); + m_xDocumentHandler->endElement("SignatureProperty"); + m_xDocumentHandler->endElement("SignatureProperties"); + m_xDocumentHandler->endElement("Object"); +} + +void OOXMLSecExporter::Impl::writeSignatureInfo() +{ + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("xmlns", "http://schemas.microsoft.com/office/2006/digsig"); + m_xDocumentHandler->startElement( + "SignatureInfoV1", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + + m_xDocumentHandler->startElement( + "SetupID", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters(m_rInformation.ouSignatureLineId); + m_xDocumentHandler->endElement("SetupID"); + m_xDocumentHandler->startElement( + "SignatureText", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->endElement("SignatureText"); + m_xDocumentHandler->startElement( + "SignatureImage", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->endElement("SignatureImage"); + m_xDocumentHandler->startElement( + "SignatureComments", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters(m_rInformation.ouDescription); + m_xDocumentHandler->endElement("SignatureComments"); + // Just hardcode something valid according to [MS-OFFCRYPTO]. + m_xDocumentHandler->startElement( + "WindowsVersion", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("6.1"); + m_xDocumentHandler->endElement("WindowsVersion"); + m_xDocumentHandler->startElement( + "OfficeVersion", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("16.0"); + m_xDocumentHandler->endElement("OfficeVersion"); + m_xDocumentHandler->startElement( + "ApplicationVersion", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("16.0"); + m_xDocumentHandler->endElement("ApplicationVersion"); + m_xDocumentHandler->startElement( + "Monitors", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("1"); + m_xDocumentHandler->endElement("Monitors"); + m_xDocumentHandler->startElement( + "HorizontalResolution", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("1280"); + m_xDocumentHandler->endElement("HorizontalResolution"); + m_xDocumentHandler->startElement( + "VerticalResolution", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("800"); + m_xDocumentHandler->endElement("VerticalResolution"); + m_xDocumentHandler->startElement( + "ColorDepth", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("32"); + m_xDocumentHandler->endElement("ColorDepth"); + m_xDocumentHandler->startElement( + "SignatureProviderId", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("{00000000-0000-0000-0000-000000000000}"); + m_xDocumentHandler->endElement("SignatureProviderId"); + m_xDocumentHandler->startElement( + "SignatureProviderUrl", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->endElement("SignatureProviderUrl"); + m_xDocumentHandler->startElement( + "SignatureProviderDetails", + uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters( + "9"); // This is what MSO 2016 writes, though [MS-OFFCRYPTO] doesn't document what the value means. + m_xDocumentHandler->endElement("SignatureProviderDetails"); + m_xDocumentHandler->startElement( + "SignatureType", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + m_xDocumentHandler->characters("2"); + m_xDocumentHandler->endElement("SignatureType"); + + m_xDocumentHandler->endElement("SignatureInfoV1"); +} + +void OOXMLSecExporter::Impl::writePackageSignature() +{ + m_xDocumentHandler->startElement( + "Object", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("xmlns:xd", NS_XD); + pAttributeList->AddAttribute("Target", "#idPackageSignature"); + m_xDocumentHandler->startElement( + "xd:QualifyingProperties", + uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + } + + DocumentSignatureHelper::writeSignedProperties(m_xDocumentHandler, m_rInformation, + m_aSignatureTimeValue, false); + + m_xDocumentHandler->endElement("xd:QualifyingProperties"); + m_xDocumentHandler->endElement("Object"); +} + +void OOXMLSecExporter::Impl::writeSignatureLineImages() +{ + if (m_rInformation.aValidSignatureImage.is()) + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Id", "idValidSigLnImg"); + m_xDocumentHandler->startElement( + "Object", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + OUString aGraphicInBase64; + Graphic aGraphic(m_rInformation.aValidSignatureImage); + if (!XOutBitmap::GraphicToBase64(aGraphic, aGraphicInBase64, false, ConvertDataFormat::EMF)) + SAL_WARN("xmlsecurity.helper", "could not convert graphic to base64"); + m_xDocumentHandler->characters(aGraphicInBase64); + m_xDocumentHandler->endElement("Object"); + } + if (m_rInformation.aInvalidSignatureImage.is()) + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Id", "idInvalidSigLnImg"); + m_xDocumentHandler->startElement( + "Object", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + OUString aGraphicInBase64; + Graphic aGraphic(m_rInformation.aInvalidSignatureImage); + if (!XOutBitmap::GraphicToBase64(aGraphic, aGraphicInBase64, false, ConvertDataFormat::EMF)) + SAL_WARN("xmlsecurity.helper", "could not convert graphic to base64"); + m_xDocumentHandler->characters(aGraphicInBase64); + m_xDocumentHandler->endElement("Object"); + } +} + +OOXMLSecExporter::OOXMLSecExporter( + const uno::Reference<uno::XComponentContext>& xComponentContext, + const uno::Reference<embed::XStorage>& xRootStorage, + const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler, + const SignatureInformation& rInformation) + : m_pImpl( + std::make_unique<Impl>(xComponentContext, xRootStorage, xDocumentHandler, rInformation)) +{ +} + +OOXMLSecExporter::~OOXMLSecExporter() = default; + +void OOXMLSecExporter::writeSignature() +{ + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("xmlns", NS_XMLDSIG); + pAttributeList->AddAttribute("Id", "idPackageSignature"); + m_pImpl->getDocumentHandler()->startElement( + "Signature", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + + m_pImpl->writeSignedInfo(); + m_pImpl->writeSignatureValue(); + m_pImpl->writeKeyInfo(); + m_pImpl->writePackageObject(); + m_pImpl->writeOfficeObject(); + m_pImpl->writePackageSignature(); + m_pImpl->writeSignatureLineImages(); + + m_pImpl->getDocumentHandler()->endElement("Signature"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/ooxmlsecexporter.hxx b/xmlsecurity/source/helper/ooxmlsecexporter.hxx new file mode 100644 index 000000000..5d94da2fd --- /dev/null +++ b/xmlsecurity/source/helper/ooxmlsecexporter.hxx @@ -0,0 +1,59 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_XMLSECURITY_SOURCE_HELPER_OOXMLSECEXPORTER_HXX +#define INCLUDED_XMLSECURITY_SOURCE_HELPER_OOXMLSECEXPORTER_HXX + +#include <memory> + +#include <svl/sigstruct.hxx> + +namespace com +{ +namespace sun +{ +namespace star +{ +namespace embed +{ +class XStorage; +} +namespace uno +{ +class XComponentContext; +} +namespace xml +{ +namespace sax +{ +class XDocumentHandler; +} +} +} +} +} + +/// Writes a single OOXML digital signature. +class OOXMLSecExporter +{ + struct Impl; + std::unique_ptr<Impl> m_pImpl; + +public: + OOXMLSecExporter(const css::uno::Reference<css::uno::XComponentContext>& xComponentContext, + const css::uno::Reference<css::embed::XStorage>& xRootStorage, + const css::uno::Reference<css::xml::sax::XDocumentHandler>& xDocumentHandler, + const SignatureInformation& rInformation); + ~OOXMLSecExporter(); + void writeSignature(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/ooxmlsecparser.cxx b/xmlsecurity/source/helper/ooxmlsecparser.cxx new file mode 100644 index 000000000..c22e8c226 --- /dev/null +++ b/xmlsecurity/source/helper/ooxmlsecparser.cxx @@ -0,0 +1,293 @@ +/* -*- 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/. + */ + + +#include "ooxmlsecparser.hxx" +#include <xmlsignaturehelper.hxx> +#include <xsecctl.hxx> +#include <sal/log.hxx> + +using namespace com::sun::star; + +OOXMLSecParser::OOXMLSecParser(XMLSignatureHelper& rXMLSignatureHelper, XSecController* pXSecController) + : m_pXSecController(pXSecController) + ,m_bInDigestValue(false) + ,m_bInSignatureValue(false) + ,m_bInX509Certificate(false) + ,m_bInMdssiValue(false) + ,m_bInSignatureComments(false) + ,m_bInX509IssuerName(false) + ,m_bInX509SerialNumber(false) + ,m_bInCertDigest(false) + ,m_bInValidSignatureImage(false) + ,m_bInInvalidSignatureImage(false) + ,m_bInSignatureLineId(false) + ,m_bReferenceUnresolved(false) + ,m_rXMLSignatureHelper(rXMLSignatureHelper) +{ +} + +OOXMLSecParser::~OOXMLSecParser() +{ +} + +void SAL_CALL OOXMLSecParser::startDocument() +{ + if (m_xNextHandler.is()) + m_xNextHandler->startDocument(); +} + +void SAL_CALL OOXMLSecParser::endDocument() +{ + if (m_xNextHandler.is()) + m_xNextHandler->endDocument(); +} + +void SAL_CALL OOXMLSecParser::startElement(const OUString& rName, const uno::Reference<xml::sax::XAttributeList>& xAttribs) +{ + OUString aId = xAttribs->getValueByName("Id"); + if (!aId.isEmpty()) + m_pXSecController->collectToVerify(aId); + + if (rName == "Signature") + { + m_rXMLSignatureHelper.StartVerifySignatureElement(); + m_pXSecController->addSignature(); + if (!aId.isEmpty()) + m_pXSecController->setId(aId); + } + else if (rName == "SignatureMethod") + { + OUString ouAlgorithm = xAttribs->getValueByName("Algorithm"); + if (ouAlgorithm == ALGO_ECDSASHA1 || ouAlgorithm == ALGO_ECDSASHA256 + || ouAlgorithm == ALGO_ECDSASHA512) + m_pXSecController->setSignatureMethod(svl::crypto::SignatureMethodAlgorithm::ECDSA); + } + else if (rName == "Reference") + { + OUString aURI = xAttribs->getValueByName("URI"); + if (aURI.startsWith("#")) + m_pXSecController->addReference(aURI.copy(1), xml::crypto::DigestID::SHA1, OUString()); + else + { + m_aReferenceURI = aURI; + m_bReferenceUnresolved = true; + } + } + else if (rName == "Transform") + { + if (m_bReferenceUnresolved) + { + OUString aAlgorithm = xAttribs->getValueByName("Algorithm"); + if (aAlgorithm == ALGO_RELATIONSHIP) + { + m_pXSecController->addStreamReference(m_aReferenceURI, /*isBinary=*/false, /*nDigestID=*/xml::crypto::DigestID::SHA256); + m_bReferenceUnresolved = false; + } + } + } + else if (rName == "DigestValue" && !m_bInCertDigest) + { + m_aDigestValue.clear(); + m_bInDigestValue = true; + } + else if (rName == "SignatureValue") + { + m_aSignatureValue.clear(); + m_bInSignatureValue = true; + } + else if (rName == "X509Certificate") + { + m_aX509Certificate.clear(); + m_bInX509Certificate = true; + } + else if (rName == "mdssi:Value") + { + m_aMdssiValue.clear(); + m_bInMdssiValue = true; + } + else if (rName == "SignatureComments") + { + m_aSignatureComments.clear(); + m_bInSignatureComments = true; + } + else if (rName == "X509IssuerName") + { + m_aX509IssuerName.clear(); + m_bInX509IssuerName = true; + } + else if (rName == "X509SerialNumber") + { + m_aX509SerialNumber.clear(); + m_bInX509SerialNumber = true; + } + else if (rName == "xd:CertDigest") + { + m_aCertDigest.clear(); + m_bInCertDigest = true; + } + else if (rName == "Object") + { + OUString sId = xAttribs->getValueByName("Id"); + if (sId == "idValidSigLnImg") + { + m_aValidSignatureImage.clear(); + m_bInValidSignatureImage = true; + } + else if (sId == "idInvalidSigLnImg") + { + m_aInvalidSignatureImage.clear(); + m_bInInvalidSignatureImage = true; + } + else + { + SAL_INFO("xmlsecurity.ooxml", "Unknown 'Object' child element: " << rName); + } + } + else if (rName == "SetupID") + { + m_aSignatureLineId.clear(); + m_bInSignatureLineId = true; + } + else + { + SAL_INFO("xmlsecurity.ooxml", "Unknown xml element: " << rName); + } + + if (m_xNextHandler.is()) + m_xNextHandler->startElement(rName, xAttribs); +} + +void SAL_CALL OOXMLSecParser::endElement(const OUString& rName) +{ + if (rName == "SignedInfo") + m_pXSecController->setReferenceCount(); + else if (rName == "Reference") + { + if (m_bReferenceUnresolved) + { + // No transform algorithm found, assume binary. + m_pXSecController->addStreamReference(m_aReferenceURI, /*isBinary=*/true, /*nDigestID=*/xml::crypto::DigestID::SHA256); + m_bReferenceUnresolved = false; + } + m_pXSecController->setDigestValue(xml::crypto::DigestID::SHA256, m_aDigestValue); + } + else if (rName == "DigestValue" && !m_bInCertDigest) + m_bInDigestValue = false; + else if (rName == "SignatureValue") + { + m_pXSecController->setSignatureValue(m_aSignatureValue); + m_bInSignatureValue = false; + } + else if (rName == "X509Certificate") + { + m_pXSecController->setX509Certificate(m_aX509Certificate); + m_bInX509Certificate = false; + } + else if (rName == "mdssi:Value") + { + m_pXSecController->setDate(m_aMdssiValue); + m_bInMdssiValue = false; + } + else if (rName == "SignatureComments") + { + m_pXSecController->setDescription(m_aSignatureComments); + m_bInSignatureComments = false; + } + else if (rName == "X509IssuerName") + { + m_pXSecController->setX509IssuerName(m_aX509IssuerName); + m_bInX509IssuerName = false; + } + else if (rName == "X509SerialNumber") + { + m_pXSecController->setX509SerialNumber(m_aX509SerialNumber); + m_bInX509SerialNumber = false; + } + else if (rName == "xd:CertDigest") + { + m_pXSecController->setCertDigest(m_aCertDigest); + m_bInCertDigest = false; + } + else if (rName == "Object") + { + if (m_bInValidSignatureImage) + { + m_pXSecController->setValidSignatureImage(m_aValidSignatureImage); + m_bInValidSignatureImage = false; + } + else if (m_bInInvalidSignatureImage) + { + m_pXSecController->setInvalidSignatureImage(m_aInvalidSignatureImage); + m_bInInvalidSignatureImage = false; + } + } + else if (rName == "SetupID") + { + m_pXSecController->setSignatureLineId(m_aSignatureLineId); + m_bInSignatureLineId = false; + } + + if (m_xNextHandler.is()) + m_xNextHandler->endElement(rName); +} + +void SAL_CALL OOXMLSecParser::characters(const OUString& rChars) +{ + if (m_bInDigestValue && !m_bInCertDigest) + m_aDigestValue += rChars; + else if (m_bInSignatureValue) + m_aSignatureValue += rChars; + else if (m_bInX509Certificate) + m_aX509Certificate += rChars; + else if (m_bInMdssiValue) + m_aMdssiValue += rChars; + else if (m_bInSignatureComments) + m_aSignatureComments += rChars; + else if (m_bInX509IssuerName) + m_aX509IssuerName += rChars; + else if (m_bInX509SerialNumber) + m_aX509SerialNumber += rChars; + else if (m_bInCertDigest) + m_aCertDigest += rChars; + else if (m_bInValidSignatureImage) + m_aValidSignatureImage += rChars; + else if (m_bInInvalidSignatureImage) + m_aInvalidSignatureImage += rChars; + else if (m_bInSignatureLineId) + m_aSignatureLineId += rChars; + + if (m_xNextHandler.is()) + m_xNextHandler->characters(rChars); +} + +void SAL_CALL OOXMLSecParser::ignorableWhitespace(const OUString& rWhitespace) +{ + if (m_xNextHandler.is()) + m_xNextHandler->ignorableWhitespace(rWhitespace); +} + +void SAL_CALL OOXMLSecParser::processingInstruction(const OUString& rTarget, const OUString& rData) +{ + if (m_xNextHandler.is()) + m_xNextHandler->processingInstruction(rTarget, rData); +} + +void SAL_CALL OOXMLSecParser::setDocumentLocator(const uno::Reference<xml::sax::XLocator>& xLocator) +{ + if (m_xNextHandler.is()) + m_xNextHandler->setDocumentLocator(xLocator); +} + +void SAL_CALL OOXMLSecParser::initialize(const uno::Sequence<uno::Any>& rArguments) +{ + rArguments[0] >>= m_xNextHandler; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/ooxmlsecparser.hxx b/xmlsecurity/source/helper/ooxmlsecparser.hxx new file mode 100644 index 000000000..d3c199147 --- /dev/null +++ b/xmlsecurity/source/helper/ooxmlsecparser.hxx @@ -0,0 +1,87 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_XMLSECURITY_SOURCE_HELPER_OOXMLSECPARSER_HXX +#define INCLUDED_XMLSECURITY_SOURCE_HELPER_OOXMLSECPARSER_HXX + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> + +#include <cppuhelper/implbase.hxx> + +class XSecController; +class XMLSignatureHelper; + +/// Parses an OOXML digital signature. +class OOXMLSecParser: public cppu::WeakImplHelper + < + css::xml::sax::XDocumentHandler, + css::lang::XInitialization + > +{ + XSecController* m_pXSecController; + css::uno::Reference<css::xml::sax::XDocumentHandler> m_xNextHandler; + + bool m_bInDigestValue; + OUString m_aDigestValue; + bool m_bInSignatureValue; + OUString m_aSignatureValue; + bool m_bInX509Certificate; + OUString m_aX509Certificate; + bool m_bInMdssiValue; + OUString m_aMdssiValue; + bool m_bInSignatureComments; + OUString m_aSignatureComments; + bool m_bInX509IssuerName; + OUString m_aX509IssuerName; + bool m_bInX509SerialNumber; + OUString m_aX509SerialNumber; + bool m_bInCertDigest; + OUString m_aCertDigest; + bool m_bInValidSignatureImage; + OUString m_aValidSignatureImage; + bool m_bInInvalidSignatureImage; + OUString m_aInvalidSignatureImage; + bool m_bInSignatureLineId; + OUString m_aSignatureLineId; + + /// Last seen <Reference URI="...">. + OUString m_aReferenceURI; + /// Already called addStreamReference() for this reference. + bool m_bReferenceUnresolved; + XMLSignatureHelper& m_rXMLSignatureHelper; + +public: + explicit OOXMLSecParser(XMLSignatureHelper& rXMLSignatureHelper, XSecController* pXSecController); + virtual ~OOXMLSecParser() override; + + // XDocumentHandler + virtual void SAL_CALL startDocument() override; + + virtual void SAL_CALL endDocument() override; + + virtual void SAL_CALL startElement(const OUString& aName, const css::uno::Reference<css::xml::sax::XAttributeList>& xAttribs) override; + + virtual void SAL_CALL endElement(const OUString& aName) override; + + virtual void SAL_CALL characters(const OUString& aChars) override; + + virtual void SAL_CALL ignorableWhitespace(const OUString& aWhitespaces) override; + + virtual void SAL_CALL processingInstruction(const OUString& aTarget, const OUString& aData) override; + + virtual void SAL_CALL setDocumentLocator(const css::uno::Reference<css::xml::sax::XLocator>& xLocator) override; + + // XInitialization + virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/pdfsignaturehelper.cxx b/xmlsecurity/source/helper/pdfsignaturehelper.cxx new file mode 100644 index 000000000..b0795cb8f --- /dev/null +++ b/xmlsecurity/source/helper/pdfsignaturehelper.cxx @@ -0,0 +1,187 @@ +/* -*- 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/. + */ + +#include <pdfsignaturehelper.hxx> + +#include <memory> + +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/security/CertificateValidity.hpp> +#include <com/sun/star/uno/SecurityException.hpp> +#include <com/sun/star/security/DocumentSignatureInformation.hpp> +#include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp> + +#include <unotools/ucbstreamhelper.hxx> +#include <vcl/filter/pdfdocument.hxx> + +#include <pdfio/pdfdocument.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> + +using namespace ::com::sun::star; + +PDFSignatureHelper::PDFSignatureHelper() = default; + +bool PDFSignatureHelper::ReadAndVerifySignature( + const uno::Reference<io::XInputStream>& xInputStream) +{ + if (!xInputStream.is()) + { + SAL_WARN("xmlsecurity.helper", "input stream missing"); + return false; + } + + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true)); + vcl::filter::PDFDocument aDocument; + if (!aDocument.Read(*pStream)) + { + SAL_WARN("xmlsecurity.helper", "failed to read the document"); + return false; + } + + std::vector<vcl::filter::PDFObjectElement*> aSignatures = aDocument.GetSignatureWidgets(); + if (aSignatures.empty()) + return true; + + m_aSignatureInfos.clear(); + + int nMDPPerm = aDocument.GetMDPPerm(); + + for (size_t i = 0; i < aSignatures.size(); ++i) + { + SignatureInformation aInfo(i); + + if (!xmlsecurity::pdfio::ValidateSignature(*pStream, aSignatures[i], aInfo, aDocument, + nMDPPerm)) + SAL_WARN("xmlsecurity.helper", "failed to determine digest match"); + + m_aSignatureInfos.push_back(aInfo); + } + + return true; +} + +SignatureInformations const& PDFSignatureHelper::GetSignatureInformations() const +{ + return m_aSignatureInfos; +} + +uno::Sequence<security::DocumentSignatureInformation> +PDFSignatureHelper::GetDocumentSignatureInformations( + const uno::Reference<xml::crypto::XSecurityEnvironment>& xSecEnv) const +{ + uno::Sequence<security::DocumentSignatureInformation> aRet(m_aSignatureInfos.size()); + + for (size_t i = 0; i < m_aSignatureInfos.size(); ++i) + { + const SignatureInformation& rInternal = m_aSignatureInfos[i]; + security::DocumentSignatureInformation& rExternal = aRet[i]; + rExternal.SignatureIsValid + = rInternal.nStatus == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; + if (!rInternal.ouX509Certificate.isEmpty()) + rExternal.Signer = xSecEnv->createCertificateFromAscii(rInternal.ouX509Certificate); + rExternal.PartialDocumentSignature = rInternal.bPartialDocumentSignature; + + // Verify certificate. + if (rExternal.Signer.is()) + { + try + { + rExternal.CertificateStatus = xSecEnv->verifyCertificate(rExternal.Signer, {}); + } + catch (const uno::SecurityException&) + { + DBG_UNHANDLED_EXCEPTION("xmlsecurity.helper", "failed to verify certificate"); + rExternal.CertificateStatus = security::CertificateValidity::INVALID; + } + } + else + rExternal.CertificateStatus = security::CertificateValidity::INVALID; + } + + return aRet; +} + +sal_Int32 PDFSignatureHelper::GetNewSecurityId() const { return m_aSignatureInfos.size(); } + +void PDFSignatureHelper::SetX509Certificate( + const uno::Reference<security::XCertificate>& xCertificate) +{ + m_xCertificate = xCertificate; +} + +void PDFSignatureHelper::SetDescription(const OUString& rDescription) +{ + m_aDescription = rDescription; +} + +bool PDFSignatureHelper::Sign(const uno::Reference<io::XInputStream>& xInputStream, bool bAdES) +{ + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true)); + vcl::filter::PDFDocument aDocument; + if (!aDocument.Read(*pStream)) + { + SAL_WARN("xmlsecurity.helper", "failed to read the document"); + return false; + } + + if (!aDocument.Sign(m_xCertificate, m_aDescription, bAdES)) + { + SAL_WARN("xmlsecurity.helper", "failed to sign"); + return false; + } + + uno::Reference<io::XStream> xStream(xInputStream, uno::UNO_QUERY); + std::unique_ptr<SvStream> pOutStream(utl::UcbStreamHelper::CreateStream(xStream, true)); + if (!aDocument.Write(*pOutStream)) + { + SAL_WARN("xmlsecurity.helper", "failed to write signed data"); + return false; + } + + return true; +} + +bool PDFSignatureHelper::RemoveSignature(const uno::Reference<io::XInputStream>& xInputStream, + sal_uInt16 nPosition) +{ + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true)); + vcl::filter::PDFDocument aDocument; + if (!aDocument.Read(*pStream)) + { + SAL_WARN("xmlsecurity.helper", "failed to read the document"); + return false; + } + + if (!aDocument.RemoveSignature(nPosition)) + { + SAL_WARN("xmlsecurity.helper", "failed to remove signature"); + return false; + } + + uno::Reference<io::XStream> xStream(xInputStream, uno::UNO_QUERY); + uno::Reference<io::XTruncate> xTruncate(xStream, uno::UNO_QUERY); + if (!xTruncate.is()) + { + SAL_WARN("xmlsecurity.helper", "failed to truncate"); + return false; + } + xTruncate->truncate(); + std::unique_ptr<SvStream> pOutStream(utl::UcbStreamHelper::CreateStream(xStream, true)); + if (!aDocument.Write(*pOutStream)) + { + SAL_WARN("xmlsecurity.helper", "failed to write without signature"); + return false; + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/xmlsignaturehelper.cxx b/xmlsecurity/source/helper/xmlsignaturehelper.cxx new file mode 100644 index 000000000..6ec834053 --- /dev/null +++ b/xmlsecurity/source/helper/xmlsignaturehelper.cxx @@ -0,0 +1,549 @@ +/* -*- 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 <xmlsignaturehelper.hxx> +#include <documentsignaturehelper.hxx> +#include <xsecctl.hxx> + +#include <xmlsignaturehelper2.hxx> + +#include <tools/datetime.hxx> + +#include <xmloff/attrlist.hxx> + +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> + +#include <comphelper/ofopxmlhelper.hxx> +#include <comphelper/sequence.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> + +#define NS_DOCUMENTSIGNATURES "http://openoffice.org/2004/documentsignatures" +#define NS_DOCUMENTSIGNATURES_ODF_1_2 "urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0" +#define OOXML_SIGNATURE_ORIGIN "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin" +#define OOXML_SIGNATURE_SIGNATURE "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::graphic; +using namespace ::com::sun::star::uno; + +XMLSignatureHelper::XMLSignatureHelper( const uno::Reference< uno::XComponentContext >& rxCtx) + : mxCtx(rxCtx), mbODFPre1_2(false) +{ + mpXSecController = new XSecController(rxCtx); + mbError = false; +} + +XMLSignatureHelper::~XMLSignatureHelper() +{ +} + +void XMLSignatureHelper::SetStorage( + const Reference < css::embed::XStorage >& rxStorage, + const OUString& sODFVersion) +{ + SAL_WARN_IF( mxUriBinding.is(), "xmlsecurity.helper", "SetStorage - UriBinding already set!" ); + mxUriBinding = new UriBindingHelper( rxStorage ); + SAL_WARN_IF(!rxStorage.is(), "xmlsecurity.helper", "SetStorage - empty storage!"); + mbODFPre1_2 = DocumentSignatureHelper::isODFPre_1_2(sODFVersion); +} + + +void XMLSignatureHelper::SetStartVerifySignatureHdl( const Link<LinkParamNone*,bool>& rLink ) +{ + maStartVerifySignatureHdl = rLink; +} + + +void XMLSignatureHelper::StartMission(const uno::Reference<xml::crypto::XXMLSecurityContext>& xSecurityContext) +{ + if ( !mxUriBinding.is() ) + mxUriBinding = new UriBindingHelper(); + + mpXSecController->startMission(mxUriBinding, xSecurityContext); +} + +void XMLSignatureHelper::EndMission() +{ + mpXSecController->endMission(); +} + +sal_Int32 XMLSignatureHelper::GetNewSecurityId() +{ + return mpXSecController->getNewSecurityId(); +} + +void XMLSignatureHelper::SetX509Certificate( + sal_Int32 nSecurityId, + const OUString& ouX509IssuerName, + const OUString& ouX509SerialNumber, + const OUString& ouX509Cert, + const OUString& ouX509CertDigest, + svl::crypto::SignatureMethodAlgorithm eAlgorithmID) +{ + mpXSecController->setX509Certificate( + nSecurityId, + ouX509IssuerName, + ouX509SerialNumber, + ouX509Cert, + ouX509CertDigest, + eAlgorithmID); +} + +void XMLSignatureHelper::AddEncapsulatedX509Certificate(const OUString& ouEncapsulatedX509Certificate) +{ + mpXSecController->addEncapsulatedX509Certificate(ouEncapsulatedX509Certificate); +} + +void XMLSignatureHelper::SetGpgCertificate(sal_Int32 nSecurityId, + const OUString& ouGpgCertDigest, + const OUString& ouGpgCert, + const OUString& ouGpgOwner) +{ + mpXSecController->setGpgCertificate( + nSecurityId, + ouGpgCertDigest, + ouGpgCert, + ouGpgOwner); +} + +void XMLSignatureHelper::SetDateTime( sal_Int32 nSecurityId, const ::DateTime& rDateTime ) +{ + css::util::DateTime stDateTime = rDateTime.GetUNODateTime(); + mpXSecController->setDate( nSecurityId, stDateTime ); +} + +void XMLSignatureHelper::SetDescription(sal_Int32 nSecurityId, const OUString& rDescription) +{ + mpXSecController->setDescription(nSecurityId, rDescription); +} + +void XMLSignatureHelper::SetSignatureLineId(sal_Int32 nSecurityId, const OUString& rSignatureLineId) +{ + mpXSecController->setSignatureLineId(nSecurityId, rSignatureLineId); +} + +void XMLSignatureHelper::SetSignatureLineValidGraphic( + sal_Int32 nSecurityId, const css::uno::Reference<XGraphic>& xValidGraphic) +{ + mpXSecController->setSignatureLineValidGraphic(nSecurityId, xValidGraphic); +} + +void XMLSignatureHelper::SetSignatureLineInvalidGraphic( + sal_Int32 nSecurityId, const css::uno::Reference<XGraphic>& xInvalidGraphic) +{ + mpXSecController->setSignatureLineInvalidGraphic(nSecurityId, xInvalidGraphic); +} + +void XMLSignatureHelper::AddForSigning( sal_Int32 nSecurityId, const OUString& uri, bool bBinary, bool bXAdESCompliantIfODF ) +{ + mpXSecController->signAStream( nSecurityId, uri, bBinary, bXAdESCompliantIfODF ); +} + + +uno::Reference<xml::sax::XWriter> XMLSignatureHelper::CreateDocumentHandlerWithHeader( + const css::uno::Reference< css::io::XOutputStream >& xOutputStream ) +{ + /* + * get SAX writer component + */ + uno::Reference< xml::sax::XWriter > xSaxWriter = xml::sax::Writer::create(mxCtx); + + /* + * connect XML writer to output stream + */ + xSaxWriter->setOutputStream( xOutputStream ); + + /* + * write the xml context for signatures + */ + SvXMLAttributeList *pAttributeList = new SvXMLAttributeList(); + OUString sNamespace; + if (mbODFPre1_2) + sNamespace = NS_DOCUMENTSIGNATURES; + else + sNamespace = NS_DOCUMENTSIGNATURES_ODF_1_2; + + pAttributeList->AddAttribute( + "xmlns", + sNamespace); + + xSaxWriter->startDocument(); + xSaxWriter->startElement( + "document-signatures", + uno::Reference< css::xml::sax::XAttributeList > (pAttributeList)); + + return xSaxWriter; +} + +void XMLSignatureHelper::CloseDocumentHandler( const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler ) +{ + xDocumentHandler->endElement( "document-signatures" ); + xDocumentHandler->endDocument(); +} + +void XMLSignatureHelper::ExportSignature( + const uno::Reference< xml::sax::XDocumentHandler >& xDocumentHandler, + const SignatureInformation& signatureInfo, + bool bXAdESCompliantIfODF ) +{ + XSecController::exportSignature(xDocumentHandler, signatureInfo, bXAdESCompliantIfODF); +} + +void XMLSignatureHelper::ExportOOXMLSignature(const uno::Reference<embed::XStorage>& xRootStorage, const uno::Reference<embed::XStorage>& xSignatureStorage, const SignatureInformation& rInformation, int nSignatureIndex) +{ + uno::Reference<io::XOutputStream> xOutputStream(xSignatureStorage->openStreamElement("sig" + OUString::number(nSignatureIndex) + ".xml", embed::ElementModes::READWRITE), uno::UNO_QUERY); + + if (rInformation.aSignatureBytes.hasElements()) + // This is a signature roundtrip, just write back the signature as-is. + xOutputStream->writeBytes(rInformation.aSignatureBytes); + else + { + uno::Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(mxCtx); + xSaxWriter->setOutputStream(xOutputStream); + xSaxWriter->startDocument(); + + mpXSecController->exportOOXMLSignature(xRootStorage, xSaxWriter, rInformation); + + xSaxWriter->endDocument(); + } +} + +void XMLSignatureHelper::CreateAndWriteSignature( const uno::Reference< xml::sax::XDocumentHandler >& xDocumentHandler, bool bXAdESCompliantIfODF ) +{ + mbError = false; + + if ( !mpXSecController->WriteSignature( xDocumentHandler, bXAdESCompliantIfODF ) ) + { + mbError = true; + } +} + +bool XMLSignatureHelper::ReadAndVerifySignature( const css::uno::Reference< css::io::XInputStream >& xInputStream ) +{ + mbError = false; + + SAL_WARN_IF(!xInputStream.is(), "xmlsecurity.helper", "input stream missing"); + + // prepare ParserInputSrouce + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; + + // get SAX parser component + uno::Reference< xml::sax::XParser > xParser = xml::sax::Parser::create(mxCtx); + + // create a signature reader + uno::Reference< xml::sax::XDocumentHandler > xHandler + = mpXSecController->createSignatureReader(*this); + + // setup the connection: + // Parser -> SignatureReader + xParser->setDocumentHandler( xHandler ); + + // parser the stream + try + { + xParser->parseStream( aParserInput ); + } + catch( uno::Exception& ) + { + mbError = true; + } + + // release the signature reader + mpXSecController->releaseSignatureReader( ); + + return !mbError; +} + +SignatureInformation XMLSignatureHelper::GetSignatureInformation( sal_Int32 nSecurityId ) const +{ + return mpXSecController->getSignatureInformation( nSecurityId ); +} + +SignatureInformations XMLSignatureHelper::GetSignatureInformations() const +{ + return mpXSecController->getSignatureInformations(); +} + +void XMLSignatureHelper::StartVerifySignatureElement() +{ + if ( !maStartVerifySignatureHdl.IsSet() || maStartVerifySignatureHdl.Call(nullptr) ) + { + sal_Int32 nSignatureId = mpXSecController->getNewSecurityId(); + mpXSecController->addSignature( nSignatureId ); + } +} + +namespace +{ +bool lcl_isSignatureType(const beans::StringPair& rPair) +{ + return rPair.First == "Type" && rPair.Second == OOXML_SIGNATURE_SIGNATURE; +} +bool lcl_isSignatureOriginType(const beans::StringPair& rPair) +{ + return rPair.First == "Type" && rPair.Second == OOXML_SIGNATURE_ORIGIN; +} +} + +bool XMLSignatureHelper::ReadAndVerifySignatureStorage(const uno::Reference<embed::XStorage>& xStorage, bool bCacheLastSignature) +{ + sal_Int32 nOpenMode = embed::ElementModes::READ; + if (xStorage.is() && !xStorage->hasByName("_rels")) + { + SAL_WARN("xmlsecurity.helper", "expected stream, in signature storage but not found: _rels"); + return false; + } + + uno::Reference<embed::XStorage> xSubStorage = xStorage->openStorageElement("_rels", nOpenMode); + uno::Reference<io::XInputStream> xRelStream(xSubStorage->openStreamElement("origin.sigs.rels", nOpenMode), uno::UNO_QUERY); + uno::Sequence< uno::Sequence<beans::StringPair> > aRelationsInfo = comphelper::OFOPXMLHelper::ReadRelationsInfoSequence(xRelStream, "origin.sigs.rels", mxCtx); + + for (sal_Int32 i = 0; i < aRelationsInfo.getLength(); ++i) + { + const uno::Sequence<beans::StringPair>& rRelation = aRelationsInfo[i]; + auto aRelation = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rRelation); + if (std::any_of(aRelation.begin(), aRelation.end(), lcl_isSignatureType)) + { + std::vector<beans::StringPair>::iterator it = std::find_if(aRelation.begin(), aRelation.end(), [](const beans::StringPair& rPair) { return rPair.First == "Target"; }); + if (it != aRelation.end()) + { + if (xStorage.is() && !xStorage->hasByName(it->Second)) + { + SAL_WARN("xmlsecurity.helper", "expected stream, but not found: " << it->Second); + continue; + } + + uno::Reference<io::XInputStream> xInputStream(xStorage->openStreamElement(it->Second, nOpenMode), uno::UNO_QUERY); + if (!ReadAndVerifySignatureStorageStream(xInputStream)) + return false; + + // By default, we cache. If it's requested, then we don't cache the last signature. + bool bCache = true; + if (!bCacheLastSignature && i == aRelationsInfo.getLength() - 1) + bCache = false; + + if (!bCache) + continue; + // Store the contents of the stream as is, in case we need to write it back later. + xInputStream.clear(); + xInputStream.set(xStorage->openStreamElement(it->Second, nOpenMode), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xPropertySet(xInputStream, uno::UNO_QUERY); + if (!xPropertySet.is()) + continue; + + sal_Int64 nSize = 0; + xPropertySet->getPropertyValue("Size") >>= nSize; + if (nSize < 0 || nSize > SAL_MAX_INT32) + { + SAL_WARN("xmlsecurity.helper", "bogus signature size: " << nSize); + continue; + } + uno::Sequence<sal_Int8> aData; + xInputStream->readBytes(aData, nSize); + mpXSecController->setSignatureBytes(aData); + } + } + } + + return true; +} + +bool XMLSignatureHelper::ReadAndVerifySignatureStorageStream(const css::uno::Reference<css::io::XInputStream>& xInputStream) +{ + mbError = false; + + // Create the input source. + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; + + // Create the sax parser. + uno::Reference<xml::sax::XParser> xParser = xml::sax::Parser::create(mxCtx); + + // Create the signature reader. + uno::Reference<xml::sax::XDocumentHandler> xHandler = mpXSecController->createSignatureReader(*this, embed::StorageFormats::OFOPXML); + + // Parser -> signature reader. + xParser->setDocumentHandler(xHandler); + + // Parse the stream. + try + { + xParser->parseStream(aParserInput); + } + catch(const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("xmlsecurity.helper"); + } + + mpXSecController->releaseSignatureReader(); + + return !mbError; +} + +void XMLSignatureHelper::EnsureSignaturesRelation(const css::uno::Reference<css::embed::XStorage>& xStorage, bool bAdd) +{ + sal_Int32 nOpenMode = embed::ElementModes::READWRITE; + uno::Reference<embed::XStorage> xSubStorage = xStorage->openStorageElement("_rels", nOpenMode); + uno::Reference<io::XInputStream> xRelStream(xSubStorage->openStreamElement(".rels", nOpenMode), uno::UNO_QUERY); + std::vector< uno::Sequence<beans::StringPair> > aRelationsInfo = comphelper::sequenceToContainer< std::vector< uno::Sequence<beans::StringPair> > >(comphelper::OFOPXMLHelper::ReadRelationsInfoSequence(xRelStream, ".rels", mxCtx)); + + // Do we have a relation already? + bool bHaveRelation = false; + int nCount = 0; + for (const uno::Sequence<beans::StringPair>& rRelation : aRelationsInfo) + { + auto aRelation = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rRelation); + if (std::any_of(aRelation.begin(), aRelation.end(), lcl_isSignatureOriginType)) + { + bHaveRelation = true; + break; + } + ++nCount; + } + + if (!bHaveRelation && bAdd) + { + // No, and have to add one. + std::vector<beans::StringPair> aRelation; + aRelation.emplace_back("Id", "rId" + OUString::number(++nCount)); + aRelation.emplace_back("Type", OOXML_SIGNATURE_ORIGIN); + aRelation.emplace_back("Target", "_xmlsignatures/origin.sigs"); + aRelationsInfo.push_back(comphelper::containerToSequence(aRelation)); + } + else if (bHaveRelation && !bAdd) + { + // Yes, and need to remove it. + for (std::vector< uno::Sequence<beans::StringPair> >::iterator it = aRelationsInfo.begin(); it != aRelationsInfo.end();) + { + auto aRelation = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(*it); + if (std::any_of(aRelation.begin(), aRelation.end(), lcl_isSignatureOriginType)) + it = aRelationsInfo.erase(it); + else + ++it; + } + } + + // Write it back. + uno::Reference<io::XTruncate> xTruncate(xRelStream, uno::UNO_QUERY); + xTruncate->truncate(); + uno::Reference<io::XOutputStream> xOutputStream(xRelStream, uno::UNO_QUERY); + comphelper::OFOPXMLHelper::WriteRelationsInfoSequence(xOutputStream, comphelper::containerToSequence(aRelationsInfo), mxCtx); + + // Commit it. + uno::Reference<embed::XTransactedObject> xTransact(xSubStorage, uno::UNO_QUERY); + xTransact->commit(); + xTransact.set(xStorage, uno::UNO_QUERY); + xTransact->commit(); +} + +void XMLSignatureHelper::ExportSignatureRelations(const css::uno::Reference<css::embed::XStorage>& xStorage, int nSignatureCount) +{ + // Write the empty file, its relations will be the signatures. + sal_Int32 nOpenMode = embed::ElementModes::READWRITE; + uno::Reference<io::XOutputStream> xOriginStream(xStorage->openStreamElement("origin.sigs", nOpenMode), uno::UNO_QUERY); + uno::Reference<io::XTruncate> xTruncate(xOriginStream, uno::UNO_QUERY); + xTruncate->truncate(); + xOriginStream->closeOutput(); + + // Write the relations. + uno::Reference<embed::XStorage> xSubStorage = xStorage->openStorageElement("_rels", nOpenMode); + uno::Reference<io::XOutputStream> xRelStream(xSubStorage->openStreamElement("origin.sigs.rels", nOpenMode), uno::UNO_QUERY); + std::vector< uno::Sequence<beans::StringPair> > aRelations; + for (int i = 0; i < nSignatureCount; ++i) + { + std::vector<beans::StringPair> aRelation; + aRelation.emplace_back("Id", "rId" + OUString::number(i + 1)); + aRelation.emplace_back("Type", OOXML_SIGNATURE_SIGNATURE); + aRelation.emplace_back("Target", "sig" + OUString::number(i + 1) + ".xml"); + aRelations.push_back(comphelper::containerToSequence(aRelation)); + } + comphelper::OFOPXMLHelper::WriteRelationsInfoSequence(xRelStream, comphelper::containerToSequence(aRelations), mxCtx); + uno::Reference<embed::XTransactedObject> xTransact(xSubStorage, uno::UNO_QUERY); + xTransact->commit(); +} + +void XMLSignatureHelper::ExportSignatureContentTypes(const css::uno::Reference<css::embed::XStorage>& xStorage, int nSignatureCount) +{ + uno::Reference<io::XStream> xStream = xStorage->openStreamElement("[Content_Types].xml", embed::ElementModes::READWRITE); + uno::Reference<io::XInputStream> xInputStream = xStream->getInputStream(); + uno::Sequence< uno::Sequence<beans::StringPair> > aContentTypeInfo = comphelper::OFOPXMLHelper::ReadContentTypeSequence(xInputStream, mxCtx); + if (aContentTypeInfo.getLength() < 2) + { + SAL_WARN("xmlsecurity.helper", "no defaults or overrides in aContentTypeInfo"); + return; + } + + // Append rels and sigs to defaults, if it's not there already. + uno::Sequence<beans::StringPair>& rDefaults = aContentTypeInfo[0]; + auto aDefaults = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rDefaults); + if (std::none_of(rDefaults.begin(), rDefaults.end(), [](const beans::StringPair& rPair) { return rPair.First == "rels"; })) + aDefaults.emplace_back("rels", "application/vnd.openxmlformats-package.relationships+xml"); + + if (std::none_of(rDefaults.begin(), rDefaults.end(), [](const beans::StringPair& rPair) { return rPair.First == "sigs"; })) + aDefaults.emplace_back("sigs", "application/vnd.openxmlformats-package.digital-signature-origin"); + rDefaults = comphelper::containerToSequence(aDefaults); + + // Remove existing signature overrides. + uno::Sequence<beans::StringPair>& rOverrides = aContentTypeInfo[1]; + auto aOverrides = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rOverrides); + aOverrides.erase(std::remove_if(aOverrides.begin(), aOverrides.end(), [](const beans::StringPair& rPair) + { + return rPair.First.startsWith("/_xmlsignatures/sig"); + }), aOverrides.end()); + + // Add our signature overrides. + for (int i = 1; i <= nSignatureCount; ++i) + aOverrides.emplace_back("/_xmlsignatures/sig" + OUString::number(i) + ".xml", "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"); + + rOverrides = comphelper::containerToSequence(aOverrides); + uno::Reference<io::XOutputStream> xOutputStream = xStream->getOutputStream(); + uno::Reference <io::XTruncate> xTruncate(xOutputStream, uno::UNO_QUERY); + xTruncate->truncate(); + comphelper::OFOPXMLHelper::WriteContentSequence(xOutputStream, rDefaults, rOverrides, mxCtx); + uno::Reference<embed::XTransactedObject> xTransact(xStorage, uno::UNO_QUERY); + xTransact->commit(); +} +void XMLSignatureHelper::CreateAndWriteOOXMLSignature(const uno::Reference<embed::XStorage>& xRootStorage, const uno::Reference<embed::XStorage>& xSignatureStorage, int nSignatureIndex) +{ + uno::Reference<io::XOutputStream> xOutputStream(xSignatureStorage->openStreamElement("sig" + OUString::number(nSignatureIndex) + ".xml", embed::ElementModes::READWRITE), uno::UNO_QUERY); + uno::Reference<xml::sax::XWriter> xSaxWriter = xml::sax::Writer::create(mxCtx); + xSaxWriter->setOutputStream(xOutputStream); + xSaxWriter->startDocument(); + + mbError = false; + if (!mpXSecController->WriteOOXMLSignature(xRootStorage, xSaxWriter)) + mbError = true; + + xSaxWriter->endDocument(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/xmlsignaturehelper2.cxx b/xmlsecurity/source/helper/xmlsignaturehelper2.cxx new file mode 100644 index 000000000..1a7a33459 --- /dev/null +++ b/xmlsecurity/source/helper/xmlsignaturehelper2.cxx @@ -0,0 +1,113 @@ +/* -*- 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 <xmlsignaturehelper2.hxx> + +#include <tools/solar.h> +#include <unotools/streamhelper.hxx> + +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <osl/diagnose.h> +#include <rtl/uri.hxx> +#include <sal/log.hxx> + +using namespace com::sun::star; + +// XUriBinding + +UriBindingHelper::UriBindingHelper() +{ +} + +UriBindingHelper::UriBindingHelper( const css::uno::Reference < css::embed::XStorage >& rxStorage ) +{ + mxStorage = rxStorage; +} + +void SAL_CALL UriBindingHelper::setUriBinding( const OUString& /*uri*/, const uno::Reference< io::XInputStream >&) +{ +} + +uno::Reference< io::XInputStream > SAL_CALL UriBindingHelper::getUriBinding( const OUString& uri ) +{ + uno::Reference< io::XInputStream > xInputStream; + if ( mxStorage.is() ) + { + xInputStream = OpenInputStream( mxStorage, uri ); + } + else + { + SvFileStream* pStream = new SvFileStream( uri, StreamMode::READ ); + sal_uInt64 nBytes = pStream->TellEnd(); + SvLockBytesRef xLockBytes = new SvLockBytes( pStream, true ); + xInputStream = new utl::OInputStreamHelper( xLockBytes, nBytes ); + } + return xInputStream; +} + +uno::Reference < io::XInputStream > UriBindingHelper::OpenInputStream( const uno::Reference < embed::XStorage >& rxStore, const OUString& rURI ) +{ + OSL_ASSERT(!rURI.isEmpty()); + uno::Reference < io::XInputStream > xInStream; + + OUString aURI(rURI); + // Ignore leading slash, don't attempt to open a storage with name "". + if (aURI.startsWith("/")) + aURI = aURI.copy(1); + // Ignore query part of the URI. + sal_Int32 nQueryPos = aURI.indexOf('?'); + if (nQueryPos != -1) + aURI = aURI.copy(0, nQueryPos); + + + sal_Int32 nSepPos = aURI.indexOf( '/' ); + if ( nSepPos == -1 ) + { + // Cloning because of I can't keep all storage references open + // MBA with think about a better API... + const OUString sName = ::rtl::Uri::decode( + aURI, rtl_UriDecodeStrict, rtl_UriCharClassRelSegment); + if (sName.isEmpty() && !aURI.isEmpty()) + throw uno::Exception("Could not decode URI for stream element.", nullptr); + + uno::Reference< io::XStream > xStream; + if (!rxStore->hasByName(sName)) + SAL_WARN("xmlsecurity.helper", "expected stream, but not found: " << sName); + else + xStream = rxStore->cloneStreamElement( sName ); + if ( !xStream.is() ) + throw uno::RuntimeException(); + xInStream = xStream->getInputStream(); + } + else + { + const OUString aStoreName = ::rtl::Uri::decode( + aURI.copy( 0, nSepPos ), rtl_UriDecodeStrict, rtl_UriCharClassRelSegment); + if (aStoreName.isEmpty() && !aURI.isEmpty()) + throw uno::Exception("Could not decode URI for stream element.", nullptr); + + OUString aElement = aURI.copy( nSepPos+1 ); + uno::Reference < embed::XStorage > xSubStore = rxStore->openStorageElement( aStoreName, embed::ElementModes::READ ); + xInStream = OpenInputStream( xSubStore, aElement ); + } + return xInStream; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/xsecctl.cxx b/xmlsecurity/source/helper/xsecctl.cxx new file mode 100644 index 000000000..cac30006b --- /dev/null +++ b/xmlsecurity/source/helper/xsecctl.cxx @@ -0,0 +1,986 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <config_gpgme.h> + +#include <xsecctl.hxx> +#include <documentsignaturehelper.hxx> +#include <framework/saxeventkeeperimpl.hxx> +#include <xmlsec/xmldocumentwrapper_xmlsecimpl.hxx> +#if HAVE_FEATURE_GPGME +# include <gpg/xmlsignature_gpgimpl.hxx> +#endif + +#include <com/sun/star/xml/crypto/sax/XMissionTaker.hpp> +#include <com/sun/star/xml/crypto/SecurityOperationStatus.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/sax/XParser.hpp> +#include <com/sun/star/xml/crypto/XXMLSignature.hpp> + +#include <xmloff/attrlist.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> +#include <unotools/datetime.hxx> +#include "ooxmlsecexporter.hxx" +#include <xmlsignaturehelper2.hxx> + +using namespace com::sun::star; + +namespace +{ +OUString getDigestURI(sal_Int32 nID) +{ + switch( nID ) + { + case css::xml::crypto::DigestID::SHA1: + return ALGO_XMLDSIGSHA1; + case css::xml::crypto::DigestID::SHA256: + return ALGO_XMLDSIGSHA256; + case css::xml::crypto::DigestID::SHA512: + return ALGO_XMLDSIGSHA512; + default: + return ALGO_XMLDSIGSHA1; + } +} +OUString getSignatureURI(svl::crypto::SignatureMethodAlgorithm eAlgorithm, sal_Int32 nDigestID) +{ + OUString aRet; + + if (eAlgorithm == svl::crypto::SignatureMethodAlgorithm::ECDSA) + { + switch (nDigestID) + { + case css::xml::crypto::DigestID::SHA1: + aRet = ALGO_ECDSASHA1; + break; + case css::xml::crypto::DigestID::SHA256: + aRet = ALGO_ECDSASHA256; + break; + case css::xml::crypto::DigestID::SHA512: + aRet = ALGO_ECDSASHA512; + break; + default: + aRet = ALGO_ECDSASHA1; + break; + } + } + if (!aRet.isEmpty()) + return aRet; + + switch (nDigestID) + { + case css::xml::crypto::DigestID::SHA1: + return ALGO_RSASHA1; + case css::xml::crypto::DigestID::SHA256: + return ALGO_RSASHA256; + case css::xml::crypto::DigestID::SHA512: + return ALGO_RSASHA512; + default: + return ALGO_RSASHA1; + } +} +} + +XSecController::XSecController( const css::uno::Reference<css::uno::XComponentContext>& rxCtx ) + : mxCtx(rxCtx) + , m_nNextSecurityId(1) + , m_bIsPreviousNodeInitializable(false) + , m_bIsSAXEventKeeperConnected(false) + , m_bIsCollectingElement(false) + , m_bIsBlocking(false) + , m_eStatusOfSecurityComponents(InitializationState::UNINITIALIZED) + , m_bIsSAXEventKeeperSticky(false) + , m_nReservedSignatureId(0) + , m_bVerifyCurrentSignature(false) +{ +} + +XSecController::~XSecController() +{ +} + + +/* + * private methods + */ +int XSecController::findSignatureInfor( sal_Int32 nSecurityId) const +/****** XSecController/findSignatureInfor ************************************* + * + * NAME + * findSignatureInfor -- find SignatureInformation struct for a particular + * signature + * + * SYNOPSIS + * index = findSignatureInfor( nSecurityId ); + * + * INPUTS + * nSecurityId - the signature's id + * + * RESULT + * index - the index of the signature, or -1 when no such signature + * existing + ******************************************************************************/ +{ + int i; + int size = m_vInternalSignatureInformations.size(); + + for (i=0; i<size; ++i) + { + if (m_vInternalSignatureInformations[i].signatureInfor.nSecurityId == nSecurityId) + { + return i; + } + } + + return -1; +} + +void XSecController::createXSecComponent( ) +/****** XSecController/createXSecComponent ************************************ + * + * NAME + * bResult = createXSecComponent -- creates xml security components + * + * FUNCTION + * Creates xml security components, including: + * 1. an xml signature bridge component + * 2. an XMLDocumentWrapper component + * 3. a SAXEventKeeper component + ******************************************************************************/ +{ + /* + * marks all security components are not available. + */ + m_eStatusOfSecurityComponents = InitializationState::FAILTOINITIALIZED; + m_xXMLSignature = nullptr; + m_xXMLDocumentWrapper = nullptr; + m_xSAXEventKeeper = nullptr; + + css::uno::Reference< css::lang::XMultiComponentFactory > xMCF( mxCtx->getServiceManager() ); + +#if HAVE_FEATURE_GPGME + uno::Reference< lang::XServiceInfo > xServiceInfo( m_xSecurityContext, css::uno::UNO_QUERY ); + if (xServiceInfo->getImplementationName() == "com.sun.star.xml.security.gpg.XMLSecurityContext_GpgImpl") + m_xXMLSignature.set(new XMLSignature_GpgImpl()); + else // xmlsec or mscrypt +#endif + m_xXMLSignature.set(xMCF->createInstanceWithContext("com.sun.star.xml.crypto.XMLSignature", mxCtx), css::uno::UNO_QUERY); + + bool bSuccess = m_xXMLSignature.is(); + if ( bSuccess ) + /* + * XMLSignature created successfully. + */ + m_xXMLDocumentWrapper = new XMLDocumentWrapper_XmlSecImpl(); + + bSuccess &= m_xXMLDocumentWrapper.is(); + if ( bSuccess ) + m_xSAXEventKeeper = new SAXEventKeeperImpl(); + + bSuccess &= m_xSAXEventKeeper.is(); + + if (bSuccess) + /* + * SAXEventKeeper created successfully. + */ + { + css::uno::Sequence <css::uno::Any> arg(1); + arg[0] <<= uno::Reference<xml::wrapper::XXMLDocumentWrapper>(m_xXMLDocumentWrapper.get()); + m_xSAXEventKeeper->initialize(arg); + + css::uno::Reference< css::xml::crypto::sax::XSAXEventKeeperStatusChangeListener > + xStatusChangeListener = this; + + m_xSAXEventKeeper->addSAXEventKeeperStatusChangeListener( xStatusChangeListener ); + + m_eStatusOfSecurityComponents = InitializationState::INITIALIZED; + } +} + +bool XSecController::chainOn() +/****** XSecController/chainOn ************************************************ + * + * NAME + * chainOn -- tries to connect the SAXEventKeeper with the SAX chain. + * + * SYNOPSIS + * bJustChainingOn = chainOn(); + * + * FUNCTION + * First, checks whether the SAXEventKeeper is on the SAX chain. If not, + * creates xml security components, and chains the SAXEventKeeper into + * the SAX chain. + * Before being chained in, the SAXEventKeeper needs to receive all + * missed key SAX events, which can promise the DOM tree buffered by the + * SAXEventKeeper has the same structure with the original document. + * + * RESULT + * bJustChainingOn - whether the SAXEventKeeper is just chained into the + * SAX chain. + * + * NOTES + * Sometimes, the last key SAX event can't be transferred to the + * SAXEventKeeper together. + * For instance, at the time a referenced element is detected, the + * startElement event has already been reserved by the ElementStackKeeper. + * Meanwhile, an ElementCollector needs to be created before the + * SAXEventKeeper receives that startElement event. + * So for the SAXEventKeeper, it needs to receive all missed key SAX + * events except that startElement event, then adds a new + * ElementCollector, then receives that startElement event. + ******************************************************************************/ +{ + bool rc = false; + + if (!m_bIsSAXEventKeeperSticky && !m_bIsSAXEventKeeperConnected) + { + if ( m_eStatusOfSecurityComponents == InitializationState::UNINITIALIZED ) + { + createXSecComponent(); + } + + if ( m_eStatusOfSecurityComponents == InitializationState::INITIALIZED ) + /* + * if all security components are ready, chains on the SAXEventKeeper + */ + { + /* + * disconnect the SAXEventKeeper with its current output handler, + * to make sure no SAX event is forwarded during the connecting + * phase. + */ + m_xSAXEventKeeper->setNextHandler( nullptr ); + + css::uno::Reference< css::xml::sax::XDocumentHandler > xSEKHandler(static_cast<cppu::OWeakObject*>(m_xSAXEventKeeper.get()), css::uno::UNO_QUERY); + + /* + * connects the previous document handler on the SAX chain + */ + if ( m_xPreviousNodeOnSAXChain.is() ) + { + if ( m_bIsPreviousNodeInitializable ) + { + css::uno::Reference< css::lang::XInitialization > xInitialization + (m_xPreviousNodeOnSAXChain, css::uno::UNO_QUERY); + + css::uno::Sequence<css::uno::Any> aArgs( 1 ); + aArgs[0] <<= xSEKHandler; + xInitialization->initialize(aArgs); + } + else + { + css::uno::Reference< css::xml::sax::XParser > xParser + (m_xPreviousNodeOnSAXChain, css::uno::UNO_QUERY); + xParser->setDocumentHandler( xSEKHandler ); + } + } + + /* + * connects the next document handler on the SAX chain + */ + m_xSAXEventKeeper->setNextHandler(uno::Reference<xml::sax::XDocumentHandler>()); + + m_bIsSAXEventKeeperConnected = true; + + rc = true; + } + } + + return rc; +} + +void XSecController::chainOff() +/****** XSecController/chainOff *********************************************** + * + * NAME + * chainOff -- disconnects the SAXEventKeeper from the SAX chain. + ******************************************************************************/ +{ + if (!m_bIsSAXEventKeeperSticky ) + { + if (m_bIsSAXEventKeeperConnected) + { + m_xSAXEventKeeper->setNextHandler( nullptr ); + + if ( m_xPreviousNodeOnSAXChain.is() ) + { + if ( m_bIsPreviousNodeInitializable ) + { + css::uno::Reference< css::lang::XInitialization > xInitialization + (m_xPreviousNodeOnSAXChain, css::uno::UNO_QUERY); + + css::uno::Sequence<css::uno::Any> aArgs( 1 ); + aArgs[0] <<= uno::Reference<xml::sax::XDocumentHandler>(); + xInitialization->initialize(aArgs); + } + else + { + css::uno::Reference< css::xml::sax::XParser > xParser(m_xPreviousNodeOnSAXChain, css::uno::UNO_QUERY); + xParser->setDocumentHandler(uno::Reference<xml::sax::XDocumentHandler>()); + } + } + + m_bIsSAXEventKeeperConnected = false; + } + } +} + +void XSecController::checkChainingStatus() +/****** XSecController/checkChainingStatus ************************************ + * + * NAME + * checkChainingStatus -- connects or disconnects the SAXEventKeeper + * according to the current situation. + * + * SYNOPSIS + * checkChainingStatus( ); + * + * FUNCTION + * The SAXEventKeeper is chained into the SAX chain, when: + * 1. some element is being collected, or + * 2. the SAX event stream is blocking. + * Otherwise, chain off the SAXEventKeeper. + ******************************************************************************/ +{ + if ( m_bIsCollectingElement || m_bIsBlocking ) + { + chainOn(); + } + else + { + chainOff(); + } +} + +void XSecController::initializeSAXChain() +/****** XSecController/initializeSAXChain ************************************* + * + * NAME + * initializeSAXChain -- initializes the SAX chain according to the + * current setting. + * + * FUNCTION + * Initializes the SAX chain, if the SAXEventKeeper is asked to be always + * on the SAX chain, chains it on. Otherwise, starts the + * ElementStackKeeper to reserve key SAX events. + ******************************************************************************/ +{ + m_bIsSAXEventKeeperConnected = false; + m_bIsCollectingElement = false; + m_bIsBlocking = false; + + chainOff(); +} + +css::uno::Reference< css::io::XInputStream > + XSecController::getObjectInputStream( const OUString& objectURL ) +/****** XSecController/getObjectInputStream ************************************ + * + * NAME + * getObjectInputStream -- get a XInputStream interface from a SotStorage + * + * SYNOPSIS + * xInputStream = getObjectInputStream( objectURL ); + * + * INPUTS + * objectURL - the object uri + * + * RESULT + * xInputStream - the XInputStream interface + ******************************************************************************/ +{ + css::uno::Reference< css::io::XInputStream > xObjectInputStream; + + SAL_WARN_IF( !m_xUriBinding.is(), "xmlsecurity.helper", "Need XUriBinding!" ); + + xObjectInputStream = m_xUriBinding->getUriBinding(objectURL); + + return xObjectInputStream; +} + +/* + * public methods + */ + +sal_Int32 XSecController::getNewSecurityId( ) +{ + sal_Int32 nId = m_nNextSecurityId; + m_nNextSecurityId++; + return nId; +} + +void XSecController::startMission(const rtl::Reference<UriBindingHelper>& xUriBinding, const css::uno::Reference< css::xml::crypto::XXMLSecurityContext >& xSecurityContext ) +/****** XSecController/startMission ******************************************* + * + * NAME + * startMission -- starts a new security mission. + * + * FUNCTION + * get ready for a new mission. + * + * INPUTS + * xUriBinding - the Uri binding that provide maps between uris and + * XInputStreams + * xSecurityContext - the security context component which can provide + * cryptoken + ******************************************************************************/ +{ + m_xUriBinding = xUriBinding; + + m_eStatusOfSecurityComponents = InitializationState::UNINITIALIZED; + m_xSecurityContext = xSecurityContext; + + m_vInternalSignatureInformations.clear(); + + m_bVerifyCurrentSignature = false; +} + +void XSecController::setSAXChainConnector(const css::uno::Reference< css::lang::XInitialization >& xInitialization) +/****** XSecController/setSAXChainConnector *********************************** + * + * NAME + * setSAXChainConnector -- configures the components which will + * collaborate with the SAXEventKeeper on the SAX chain. + * + * SYNOPSIS + * setSAXChainConnector(xInitialization); + * + * INPUTS + * xInitialization - the previous node on the SAX chain + ******************************************************************************/ +{ + m_bIsPreviousNodeInitializable = true; + m_xPreviousNodeOnSAXChain = xInitialization; + + initializeSAXChain( ); +} + +void XSecController::clearSAXChainConnector() +/****** XSecController/clearSAXChainConnector ********************************* + * + * NAME + * clearSAXChainConnector -- resets the collaborating components. + ******************************************************************************/ +{ + chainOff(); + + m_xPreviousNodeOnSAXChain = nullptr; +} + +void XSecController::endMission() +/****** XSecController/endMission ********************************************* + * + * NAME + * endMission -- forces to end all missions + * + * FUNCTION + * Deletes all signature information and forces all missions to an end. + ******************************************************************************/ +{ + sal_Int32 size = m_vInternalSignatureInformations.size(); + + for (int i=0; i<size; ++i) + { + if ( m_eStatusOfSecurityComponents == InitializationState::INITIALIZED ) + /* + * ResolvedListener only exist when the security components are created. + */ + { + css::uno::Reference< css::xml::crypto::sax::XMissionTaker > xMissionTaker + ( m_vInternalSignatureInformations[i].xReferenceResolvedListener, css::uno::UNO_QUERY ); + + /* + * asks the SignatureCreator/SignatureVerifier to release + * all resources it uses. + */ + xMissionTaker->endMission(); + } + } + + m_xUriBinding = nullptr; + m_xSecurityContext = nullptr; + + /* + * free the status change listener reference to this object + */ + if (m_xSAXEventKeeper.is()) + m_xSAXEventKeeper->addSAXEventKeeperStatusChangeListener( nullptr ); +} + +namespace +{ +void writeUnsignedProperties( + const css::uno::Reference<css::xml::sax::XDocumentHandler>& xDocumentHandler, + const SignatureInformation& signatureInfo) +{ + { + rtl::Reference<SvXMLAttributeList> pAttributeList(new SvXMLAttributeList()); + pAttributeList->AddAttribute("Id", "idUnsignedProperties"); + xDocumentHandler->startElement("xd:UnsignedProperties", uno::Reference<xml::sax::XAttributeList>(pAttributeList.get())); + } + + { + xDocumentHandler->startElement("xd:UnsignedSignatureProperties", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + + { + xDocumentHandler->startElement("xd:CertificateValues", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + + { + for (const auto& i: signatureInfo.maEncapsulatedX509Certificates) + { + xDocumentHandler->startElement("xd:EncapsulatedX509Certificate", uno::Reference<xml::sax::XAttributeList>(new SvXMLAttributeList())); + xDocumentHandler->characters(i); + xDocumentHandler->endElement("xd:EncapsulatedX509Certificate"); + } + } + + xDocumentHandler->endElement("xd:CertificateValues"); + } + + xDocumentHandler->endElement("xd:UnsignedSignatureProperties"); + } + + xDocumentHandler->endElement("xd:UnsignedProperties"); +} + +} + +void XSecController::exportSignature( + const css::uno::Reference<css::xml::sax::XDocumentHandler>& xDocumentHandler, + const SignatureInformation& signatureInfo, + bool bXAdESCompliantIfODF ) +/****** XSecController/exportSignature **************************************** + * + * NAME + * exportSignature -- export a signature structure to an XDocumentHandler + * + * SYNOPSIS + * exportSignature( xDocumentHandler, signatureInfo); + * + * INPUTS + * xDocumentHandler - the document handler to receive the signature + * signatureInfo - signature to be exported + ******************************************************************************/ +{ + const SignatureReferenceInformations& vReferenceInfors = signatureInfo.vSignatureReferenceInfors; + SvXMLAttributeList *pAttributeList; + + /* + * Write Signature element + */ + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute( + "xmlns", + NS_XMLDSIG); + + if (!signatureInfo.ouSignatureId.isEmpty()) + { + pAttributeList->AddAttribute( + "Id", + signatureInfo.ouSignatureId); + } + + xDocumentHandler->startElement( "Signature", css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList)); + { + /* Write SignedInfo element */ + xDocumentHandler->startElement( + "SignedInfo", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + { + /* Write CanonicalizationMethod element */ + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute( + "Algorithm", + ALGO_C14N); + xDocumentHandler->startElement( "CanonicalizationMethod", css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList) ); + xDocumentHandler->endElement( "CanonicalizationMethod" ); + + /* Write SignatureMethod element */ + pAttributeList = new SvXMLAttributeList(); + + // TODO: actually roundtrip this value from parsing documentsignatures.xml - entirely + // broken to assume this would in any way relate to the 1st reference's digest algo + + // Assume that all Reference elements use the same DigestMethod:Algorithm, and that the + // SignatureMethod:Algorithm should be the corresponding one. + pAttributeList->AddAttribute( + "Algorithm", + getSignatureURI(signatureInfo.eAlgorithmID, vReferenceInfors[0].nDigestID)); + xDocumentHandler->startElement( "SignatureMethod", css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList) ); + xDocumentHandler->endElement( "SignatureMethod" ); + + /* Write Reference element */ + int j; + int refNum = vReferenceInfors.size(); + + for(j=0; j<refNum; ++j) + { + const SignatureReferenceInformation& refInfor = vReferenceInfors[j]; + + pAttributeList = new SvXMLAttributeList(); + if ( refInfor.nType != SignatureReferenceType::SAMEDOCUMENT ) + /* + * stream reference + */ + { + pAttributeList->AddAttribute( + "URI", + refInfor.ouURI); + } + else + /* + * same-document reference + */ + { + pAttributeList->AddAttribute( + "URI", + "#" + refInfor.ouURI); + + if (bXAdESCompliantIfODF && refInfor.ouURI == "idSignedProperties" && !refInfor.ouType.isEmpty()) + { + // The reference which points to the SignedProperties + // shall have this specific type. + pAttributeList->AddAttribute("Type", + refInfor.ouType); + } + } + + xDocumentHandler->startElement( "Reference", css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList) ); + { + /* Write Transforms element */ + if (refInfor.nType == SignatureReferenceType::XMLSTREAM) + /* + * xml stream, so c14n transform is needed + */ + { + xDocumentHandler->startElement( + "Transforms", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + { + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute( + "Algorithm", + ALGO_C14N); + xDocumentHandler->startElement( + "Transform", + css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList) ); + xDocumentHandler->endElement( "Transform" ); + } + xDocumentHandler->endElement( "Transforms" ); + } + + /* Write DigestMethod element */ + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute( + "Algorithm", + getDigestURI(refInfor.nDigestID)); + xDocumentHandler->startElement( + "DigestMethod", + css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList) ); + xDocumentHandler->endElement( "DigestMethod" ); + + /* Write DigestValue element */ + xDocumentHandler->startElement( + "DigestValue", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + xDocumentHandler->characters( refInfor.ouDigestValue ); + xDocumentHandler->endElement( "DigestValue" ); + } + xDocumentHandler->endElement( "Reference" ); + } + } + xDocumentHandler->endElement( "SignedInfo" ); + + /* Write SignatureValue element */ + xDocumentHandler->startElement( + "SignatureValue", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + xDocumentHandler->characters( signatureInfo.ouSignatureValue ); + xDocumentHandler->endElement( "SignatureValue" ); + + /* Write KeyInfo element */ + xDocumentHandler->startElement( + "KeyInfo", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + { + // GPG or X509 key? + if (!signatureInfo.ouGpgCertificate.isEmpty()) + { + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute("xmlns:loext", NS_LOEXT); + /* Write PGPData element */ + xDocumentHandler->startElement( + "PGPData", + css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList)); + { + /* Write keyid element */ + xDocumentHandler->startElement( + "PGPKeyID", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + xDocumentHandler->characters( signatureInfo.ouCertDigest ); + xDocumentHandler->endElement( "PGPKeyID" ); + + /* Write PGPKeyPacket element */ + if (!signatureInfo.ouGpgCertificate.isEmpty()) + { + xDocumentHandler->startElement( + "PGPKeyPacket", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + xDocumentHandler->characters( signatureInfo.ouGpgCertificate ); + xDocumentHandler->endElement( "PGPKeyPacket" ); + } + + /* Write PGPOwner element */ + xDocumentHandler->startElement( + "loext:PGPOwner", + css::uno::Reference< css::xml::sax::XAttributeList >(new SvXMLAttributeList())); + xDocumentHandler->characters( signatureInfo.ouGpgOwner ); + xDocumentHandler->endElement( "loext:PGPOwner" ); + } + xDocumentHandler->endElement( "PGPData" ); + } + else + { + /* Write X509Data element */ + xDocumentHandler->startElement( + "X509Data", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + { + /* Write X509IssuerSerial element */ + xDocumentHandler->startElement( + "X509IssuerSerial", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + { + /* Write X509IssuerName element */ + xDocumentHandler->startElement( + "X509IssuerName", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + xDocumentHandler->characters( signatureInfo.ouX509IssuerName ); + xDocumentHandler->endElement( "X509IssuerName" ); + + /* Write X509SerialNumber element */ + xDocumentHandler->startElement( + "X509SerialNumber", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + xDocumentHandler->characters( signatureInfo.ouX509SerialNumber ); + xDocumentHandler->endElement( "X509SerialNumber" ); + } + xDocumentHandler->endElement( "X509IssuerSerial" ); + + /* Write X509Certificate element */ + if (!signatureInfo.ouX509Certificate.isEmpty()) + { + xDocumentHandler->startElement( + "X509Certificate", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + xDocumentHandler->characters( signatureInfo.ouX509Certificate ); + xDocumentHandler->endElement( "X509Certificate" ); + } + } + xDocumentHandler->endElement( "X509Data" ); + } + } + xDocumentHandler->endElement( "KeyInfo" ); + + OUString sDate; + + /* Write Object element */ + xDocumentHandler->startElement( + "Object", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + { + /* Write SignatureProperties element */ + xDocumentHandler->startElement( + "SignatureProperties", + css::uno::Reference< css::xml::sax::XAttributeList > (new SvXMLAttributeList())); + { + /* Write SignatureProperty element */ + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute( + "Id", + signatureInfo.ouPropertyId); + pAttributeList->AddAttribute( + "Target", + "#" + signatureInfo.ouSignatureId); + xDocumentHandler->startElement( + "SignatureProperty", + css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList)); + { + /* Write timestamp element */ + + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute( + "xmlns:dc", + NS_DC); + + xDocumentHandler->startElement( + "dc:date", + css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList)); + + OUStringBuffer buffer; + //If the xml signature was already contained in the document, + //then we use the original date and time string, rather than the + //converted one. This avoids writing a different string due to + //e.g. rounding issues and thus breaking the signature. + if (!signatureInfo.ouDateTime.isEmpty()) + buffer = signatureInfo.ouDateTime; + else + { + buffer = utl::toISO8601(signatureInfo.stDateTime); + // xsd:dateTime must use period as separator for fractional seconds, while + // utl::toISO8601 uses comma (as allowed, and even recommended, by ISO8601). + buffer.replace(',', '.'); + } + sDate = buffer.makeStringAndClear(); + xDocumentHandler->characters( sDate ); + + xDocumentHandler->endElement( + "dc:date"); + } + xDocumentHandler->endElement( "SignatureProperty" ); + } + + // Write signature description. + if (!signatureInfo.ouDescription.isEmpty()) + { + // SignatureProperty element. + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute("Id", signatureInfo.ouDescriptionPropertyId); + pAttributeList->AddAttribute("Target", "#" + signatureInfo.ouSignatureId); + xDocumentHandler->startElement("SignatureProperty", uno::Reference<xml::sax::XAttributeList>(pAttributeList)); + + { + // Description element. + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute("xmlns:dc", NS_DC); + + xDocumentHandler->startElement("dc:description", uno::Reference<xml::sax::XAttributeList>(pAttributeList)); + xDocumentHandler->characters(signatureInfo.ouDescription); + xDocumentHandler->endElement("dc:description"); + } + + xDocumentHandler->endElement("SignatureProperty"); + } + + xDocumentHandler->endElement( "SignatureProperties" ); + } + xDocumentHandler->endElement( "Object" ); + + // In XAdES, write another Object element for the QualifyingProperties + if (bXAdESCompliantIfODF) + { + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute("xmlns:xd", NS_XD); + xDocumentHandler->startElement( + "Object", + css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList)); + { + pAttributeList = new SvXMLAttributeList(); + pAttributeList->AddAttribute("Target", "#" + signatureInfo.ouSignatureId); + xDocumentHandler->startElement( + "xd:QualifyingProperties", + css::uno::Reference< css::xml::sax::XAttributeList > (pAttributeList)); + DocumentSignatureHelper::writeSignedProperties(xDocumentHandler, signatureInfo, sDate, true); + writeUnsignedProperties(xDocumentHandler, signatureInfo); + xDocumentHandler->endElement( "xd:QualifyingProperties" ); + } + xDocumentHandler->endElement( "Object" ); + } + } + xDocumentHandler->endElement( "Signature" ); +} + +void XSecController::exportOOXMLSignature(const uno::Reference<embed::XStorage>& xRootStorage, const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler, const SignatureInformation& rInformation) +{ + OOXMLSecExporter aExporter(mxCtx, xRootStorage, xDocumentHandler, rInformation); + aExporter.writeSignature(); +} + +SignatureInformation XSecController::getSignatureInformation( sal_Int32 nSecurityId ) const +{ + SignatureInformation aInf( 0 ); + int nIndex = findSignatureInfor(nSecurityId); + SAL_WARN_IF( nIndex == -1, "xmlsecurity.helper", "getSignatureInformation - SecurityId is invalid!" ); + if ( nIndex != -1) + { + aInf = m_vInternalSignatureInformations[nIndex].signatureInfor; + } + return aInf; +} + +SignatureInformations XSecController::getSignatureInformations() const +{ + SignatureInformations vInfors; + int sigNum = m_vInternalSignatureInformations.size(); + + for (int i=0; i<sigNum; ++i) + { + SignatureInformation si = m_vInternalSignatureInformations[i].signatureInfor; + vInfors.push_back(si); + } + + return vInfors; +} + +/* + * XSAXEventKeeperStatusChangeListener + */ + +void SAL_CALL XSecController::blockingStatusChanged( sal_Bool isBlocking ) +{ + m_bIsBlocking = isBlocking; + checkChainingStatus(); +} + +void SAL_CALL XSecController::collectionStatusChanged( + sal_Bool isInsideCollectedElement ) +{ + m_bIsCollectingElement = isInsideCollectedElement; + checkChainingStatus(); +} + +void SAL_CALL XSecController::bufferStatusChanged( sal_Bool /*isBufferEmpty*/) +{ + +} + +/* + * XSignatureCreationResultListener + */ +void SAL_CALL XSecController::signatureCreated( sal_Int32 securityId, css::xml::crypto::SecurityOperationStatus nResult ) +{ + int index = findSignatureInfor(securityId); + assert(index != -1 && "Signature Not Found!"); + SignatureInformation& signatureInfor = m_vInternalSignatureInformations.at(index).signatureInfor; + signatureInfor.nStatus = nResult; +} + +/* + * XSignatureVerifyResultListener + */ +void SAL_CALL XSecController::signatureVerified( sal_Int32 securityId, css::xml::crypto::SecurityOperationStatus nResult ) +{ + int index = findSignatureInfor(securityId); + assert(index != -1 && "Signature Not Found!"); + SignatureInformation& signatureInfor = m_vInternalSignatureInformations.at(index).signatureInfor; + signatureInfor.nStatus = nResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/xsecparser.cxx b/xmlsecurity/source/helper/xsecparser.cxx new file mode 100644 index 000000000..9f2bbe074 --- /dev/null +++ b/xmlsecurity/source/helper/xsecparser.cxx @@ -0,0 +1,530 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "xsecparser.hxx" +#include <xsecctl.hxx> +#include <xmlsignaturehelper.hxx> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <sal/log.hxx> + + +XSecParser::XSecParser(XMLSignatureHelper& rXMLSignatureHelper, + XSecController* pXSecController) + : m_bInX509IssuerName(false) + , m_bInX509SerialNumber(false) + , m_bInX509Certificate(false) + , m_bInGpgCertificate(false) + , m_bInGpgKeyID(false) + , m_bInGpgOwner(false) + , m_bInCertDigest(false) + , m_bInEncapsulatedX509Certificate(false) + , m_bInSigningTime(false) + , m_bInDigestValue(false) + , m_bInSignatureValue(false) + , m_bInDate(false) + , m_bInDescription(false) + , m_bInSignatureLineId(false) + , m_bInSignatureLineValidImage(false) + , m_bInSignatureLineInvalidImage(false) + , m_pXSecController(pXSecController) + , m_bReferenceUnresolved(false) + , m_nReferenceDigestID(css::xml::crypto::DigestID::SHA1) + , m_rXMLSignatureHelper(rXMLSignatureHelper) +{ +} + +OUString XSecParser::getIdAttr(const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) +{ + OUString ouIdAttr = xAttribs->getValueByName("id"); + + if (ouIdAttr.isEmpty()) + { + ouIdAttr = xAttribs->getValueByName("Id"); + } + + return ouIdAttr; +} + +/* + * XDocumentHandler + */ +void SAL_CALL XSecParser::startDocument( ) +{ + m_bInX509IssuerName = false; + m_bInX509SerialNumber = false; + m_bInX509Certificate = false; + m_bInGpgCertificate = false; + m_bInGpgKeyID = false; + m_bInGpgOwner = false; + m_bInSignatureValue = false; + m_bInDigestValue = false; + m_bInDate = false; + m_bInDescription = false; + + if (m_xNextHandler.is()) + { + m_xNextHandler->startDocument(); + } +} + +void SAL_CALL XSecParser::endDocument( ) +{ + if (m_xNextHandler.is()) + { + m_xNextHandler->endDocument(); + } +} + +void SAL_CALL XSecParser::startElement( + const OUString& aName, + const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) +{ + try + { + OUString ouIdAttr = getIdAttr(xAttribs); + if (!ouIdAttr.isEmpty()) + { + m_pXSecController->collectToVerify( ouIdAttr ); + } + + if ( aName == "Signature" ) + { + m_rXMLSignatureHelper.StartVerifySignatureElement(); + m_pXSecController->addSignature(); + if (!ouIdAttr.isEmpty()) + { + m_pXSecController->setId( ouIdAttr ); + } + } + else if (aName == "SignatureMethod") + { + OUString ouAlgorithm = xAttribs->getValueByName("Algorithm"); + if (ouAlgorithm == ALGO_ECDSASHA1 || ouAlgorithm == ALGO_ECDSASHA256 + || ouAlgorithm == ALGO_ECDSASHA512) + m_pXSecController->setSignatureMethod(svl::crypto::SignatureMethodAlgorithm::ECDSA); + } + else if ( aName == "Reference" ) + { + OUString ouUri = xAttribs->getValueByName("URI"); + SAL_WARN_IF( ouUri.isEmpty(), "xmlsecurity.helper", "URI is empty" ); + // Remember the type of this reference. + OUString ouType = xAttribs->getValueByName("Type"); + if (ouUri.startsWith("#")) + { + /* + * remove the first character '#' from the attribute value + */ + m_pXSecController->addReference( ouUri.copy(1), m_nReferenceDigestID, ouType ); + } + else + { + /* + * remember the uri + */ + m_currentReferenceURI = ouUri; + m_bReferenceUnresolved = true; + } + } + else if (aName == "DigestMethod") + { + OUString ouAlgorithm = xAttribs->getValueByName("Algorithm"); + + SAL_WARN_IF( ouAlgorithm.isEmpty(), "xmlsecurity.helper", "no Algorithm in Reference" ); + if (!ouAlgorithm.isEmpty()) + { + SAL_WARN_IF( ouAlgorithm != ALGO_XMLDSIGSHA1 + && ouAlgorithm != ALGO_XMLDSIGSHA256 + && ouAlgorithm != ALGO_XMLDSIGSHA512, + "xmlsecurity.helper", "Algorithm neither SHA1, SHA256 nor SHA512"); + if (ouAlgorithm == ALGO_XMLDSIGSHA1) + m_nReferenceDigestID = css::xml::crypto::DigestID::SHA1; + else if (ouAlgorithm == ALGO_XMLDSIGSHA256) + m_nReferenceDigestID = css::xml::crypto::DigestID::SHA256; + else if (ouAlgorithm == ALGO_XMLDSIGSHA512) + m_nReferenceDigestID = css::xml::crypto::DigestID::SHA512; + else + m_nReferenceDigestID = 0; + } + } + else if (aName == "Transform") + { + if ( m_bReferenceUnresolved ) + { + OUString ouAlgorithm = xAttribs->getValueByName("Algorithm"); + + if (ouAlgorithm == ALGO_C14N) + /* + * a xml stream + */ + { + m_pXSecController->addStreamReference( m_currentReferenceURI, false, m_nReferenceDigestID ); + m_bReferenceUnresolved = false; + } + } + } + else if (aName == "X509IssuerName") + { + m_ouX509IssuerName.clear(); + m_bInX509IssuerName = true; + } + else if (aName == "X509SerialNumber") + { + m_ouX509SerialNumber.clear(); + m_bInX509SerialNumber = true; + } + else if (aName == "X509Certificate") + { + m_ouX509Certificate.clear(); + m_bInX509Certificate = true; + } + else if (aName == "PGPData") + { + m_pXSecController->switchGpgSignature(); + } + else if (aName == "PGPKeyID") + { + m_ouGpgKeyID.clear(); + m_bInGpgKeyID = true; + } + else if (aName == "PGPKeyPacket") + { + m_ouGpgCertificate.clear(); + m_bInGpgCertificate = true; + } + else if (aName == "loext:PGPOwner") + { + m_ouGpgOwner.clear(); + m_bInGpgOwner = true; + } + else if (aName == "SignatureValue") + { + m_ouSignatureValue.clear(); + m_bInSignatureValue = true; + } + else if (aName == "DigestValue" && !m_bInCertDigest) + { + m_ouDigestValue.clear(); + m_bInDigestValue = true; + } + else if (aName == "xd:CertDigest") + { + m_ouCertDigest.clear(); + m_bInCertDigest = true; + } + // FIXME: Existing code here in xmlsecurity uses "xd" as the namespace prefix for XAdES, + // while the sample document attached to tdf#76142 uses "xades". So accept either here. Of + // course this is idiotic and wrong, the right thing would be to use a proper way to parse + // XML that would handle namespaces correctly. I have no idea how substantial re-plumbing of + // this code that would require. + else if (aName == "xd:EncapsulatedX509Certificate" || aName == "xades:EncapsulatedX509Certificate") + { + m_ouEncapsulatedX509Certificate.clear(); + m_bInEncapsulatedX509Certificate = true; + } + else if (aName == "xd:SigningTime" || aName == "xades:SigningTime") + { + m_ouDate.clear(); + m_bInSigningTime = true; + } + else if ( aName == "SignatureProperty" ) + { + if (!ouIdAttr.isEmpty()) + { + m_pXSecController->setPropertyId( ouIdAttr ); + } + } + else if (aName == "dc:date") + { + if (m_ouDate.isEmpty()) + m_bInDate = true; + } + else if (aName == "dc:description") + { + m_ouDescription.clear(); + m_bInDescription = true; + } + else if (aName == "loext:SignatureLineId") + { + m_ouSignatureLineId.clear(); + m_bInSignatureLineId = true; + } + else if (aName == "loext:SignatureLineValidImage") + { + m_ouSignatureLineValidImage.clear(); + m_bInSignatureLineValidImage = true; + } + else if (aName == "loext:SignatureLineInvalidImage") + { + m_ouSignatureLineInvalidImage.clear(); + m_bInSignatureLineInvalidImage = true; + } + + if (m_xNextHandler.is()) + { + m_xNextHandler->startElement(aName, xAttribs); + } + } + catch (css::uno::Exception& ) + {//getCaughtException MUST be the first line in the catch block + css::uno::Any exc = cppu::getCaughtException(); + throw css::xml::sax::SAXException( + "xmlsecurity: Exception in XSecParser::startElement", + nullptr, exc); + } + catch (...) + { + throw css::xml::sax::SAXException( + "xmlsecurity: unexpected exception in XSecParser::startElement", nullptr, + css::uno::Any()); + } +} + +void SAL_CALL XSecParser::endElement( const OUString& aName ) +{ + try + { + if (aName == "DigestValue" && !m_bInCertDigest) + { + m_bInDigestValue = false; + } + else if ( aName == "Reference" ) + { + if ( m_bReferenceUnresolved ) + /* + * it must be an octet stream + */ + { + m_pXSecController->addStreamReference( m_currentReferenceURI, true, m_nReferenceDigestID ); + m_bReferenceUnresolved = false; + } + + m_pXSecController->setDigestValue( m_nReferenceDigestID, m_ouDigestValue ); + } + else if ( aName == "SignedInfo" ) + { + m_pXSecController->setReferenceCount(); + } + else if ( aName == "SignatureValue" ) + { + m_pXSecController->setSignatureValue( m_ouSignatureValue ); + m_bInSignatureValue = false; + } + else if (aName == "X509IssuerName") + { + m_pXSecController->setX509IssuerName( m_ouX509IssuerName ); + m_bInX509IssuerName = false; + } + else if (aName == "X509SerialNumber") + { + m_pXSecController->setX509SerialNumber( m_ouX509SerialNumber ); + m_bInX509SerialNumber = false; + } + else if (aName == "X509Certificate") + { + m_pXSecController->setX509Certificate( m_ouX509Certificate ); + m_bInX509Certificate = false; + } + else if (aName == "PGPKeyID") + { + m_pXSecController->setGpgKeyID( m_ouGpgKeyID ); + m_bInGpgKeyID = false; + } + else if (aName == "PGPKeyPacket") + { + m_pXSecController->setGpgCertificate( m_ouGpgCertificate ); + m_bInGpgCertificate = false; + } + else if (aName == "loext:PGPOwner") + { + m_pXSecController->setGpgOwner( m_ouGpgOwner ); + m_bInGpgOwner = false; + } + else if (aName == "xd:CertDigest") + { + m_pXSecController->setCertDigest( m_ouCertDigest ); + m_bInCertDigest = false; + } + else if (aName == "xd:EncapsulatedX509Certificate" || aName == "xades:EncapsulatedX509Certificate") + { + m_pXSecController->addEncapsulatedX509Certificate( m_ouEncapsulatedX509Certificate ); + m_bInEncapsulatedX509Certificate = false; + } + else if (aName == "xd:SigningTime" || aName == "xades:SigningTime") + { + m_pXSecController->setDate( m_ouDate ); + m_bInSigningTime = false; + } + else if (aName == "dc:date") + { + if (m_bInDate) + { + m_pXSecController->setDate( m_ouDate ); + m_bInDate = false; + } + } + else if (aName == "dc:description") + { + m_pXSecController->setDescription( m_ouDescription ); + m_bInDescription = false; + } + else if (aName == "loext:SignatureLineId") + { + m_pXSecController->setSignatureLineId( m_ouSignatureLineId ); + m_bInSignatureLineId = false; + } + else if (aName == "loext:SignatureLineValidImage") + { + m_pXSecController->setValidSignatureImage( m_ouSignatureLineValidImage ); + m_bInSignatureLineValidImage = false; + } + else if (aName == "loext:SignatureLineInvalidImage") + { + m_pXSecController->setInvalidSignatureImage( m_ouSignatureLineInvalidImage ); + m_bInSignatureLineInvalidImage = false; + } + + if (m_xNextHandler.is()) + { + m_xNextHandler->endElement(aName); + } + } + catch (css::uno::Exception& ) + {//getCaughtException MUST be the first line in the catch block + css::uno::Any exc = cppu::getCaughtException(); + throw css::xml::sax::SAXException( + "xmlsecurity: Exception in XSecParser::endElement", + nullptr, exc); + } + catch (...) + { + throw css::xml::sax::SAXException( + "xmlsecurity: unexpected exception in XSecParser::endElement", nullptr, + css::uno::Any()); + } +} + +void SAL_CALL XSecParser::characters( const OUString& aChars ) +{ + if (m_bInX509IssuerName) + { + m_ouX509IssuerName += aChars; + } + else if (m_bInX509SerialNumber) + { + m_ouX509SerialNumber += aChars; + } + else if (m_bInX509Certificate) + { + m_ouX509Certificate += aChars; + } + else if (m_bInGpgCertificate) + { + m_ouGpgCertificate += aChars; + } + else if (m_bInGpgKeyID) + { + m_ouGpgKeyID += aChars; + } + else if (m_bInGpgOwner) + { + m_ouGpgOwner += aChars; + } + else if (m_bInSignatureValue) + { + m_ouSignatureValue += aChars; + } + else if (m_bInDigestValue && !m_bInCertDigest) + { + m_ouDigestValue += aChars; + } + else if (m_bInDate) + { + m_ouDate += aChars; + } + else if (m_bInDescription) + { + m_ouDescription += aChars; + } + else if (m_bInCertDigest) + { + m_ouCertDigest += aChars; + } + else if (m_bInEncapsulatedX509Certificate) + { + m_ouEncapsulatedX509Certificate += aChars; + } + else if (m_bInSigningTime) + { + m_ouDate += aChars; + } + else if (m_bInSignatureLineId) + { + m_ouSignatureLineId += aChars; + } + else if (m_bInSignatureLineValidImage) + { + m_ouSignatureLineValidImage += aChars; + } + else if (m_bInSignatureLineInvalidImage) + { + m_ouSignatureLineInvalidImage += aChars; + } + + if (m_xNextHandler.is()) + { + m_xNextHandler->characters(aChars); + } +} + +void SAL_CALL XSecParser::ignorableWhitespace( const OUString& aWhitespaces ) +{ + if (m_xNextHandler.is()) + { + m_xNextHandler->ignorableWhitespace( aWhitespaces ); + } +} + +void SAL_CALL XSecParser::processingInstruction( const OUString& aTarget, const OUString& aData ) +{ + if (m_xNextHandler.is()) + { + m_xNextHandler->processingInstruction(aTarget, aData); + } +} + +void SAL_CALL XSecParser::setDocumentLocator( const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) +{ + if (m_xNextHandler.is()) + { + m_xNextHandler->setDocumentLocator( xLocator ); + } +} + +/* + * XInitialization + */ +void SAL_CALL XSecParser::initialize( + const css::uno::Sequence< css::uno::Any >& aArguments ) +{ + aArguments[0] >>= m_xNextHandler; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/xsecparser.hxx b/xmlsecurity/source/helper/xsecparser.hxx new file mode 100644 index 000000000..d9b079aa3 --- /dev/null +++ b/xmlsecurity/source/helper/xsecparser.hxx @@ -0,0 +1,162 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_XMLSECURITY_SOURCE_HELPER_XSECPARSER_HXX +#define INCLUDED_XMLSECURITY_SOURCE_HELPER_XSECPARSER_HXX + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> + +#include <cppuhelper/implbase.hxx> + +class XMLSignatureHelper; +class XSecController; + +class XSecParser: public cppu::WeakImplHelper +< + css::xml::sax::XDocumentHandler, + css::lang::XInitialization +> +/****** XSecController.hxx/CLASS XSecParser *********************************** + * + * NAME + * XSecParser -- a SAX parser that can detect security elements + * + * FUNCTION + * The XSecParser object is connected on the SAX chain and detects + * security elements in the SAX event stream, then notifies + * the XSecController. + * + * NOTES + * This class is used when importing a document. + ******************************************************************************/ +{ + friend class XSecController; +private: + /* + * the following members are used to reserve the signature information, + * including X509IssuerName, X509SerialNumber, and X509Certificate,etc. + */ + OUString m_ouX509IssuerName; + OUString m_ouX509SerialNumber; + OUString m_ouX509Certificate; + OUString m_ouGpgCertificate; + OUString m_ouGpgKeyID; + OUString m_ouGpgOwner; + OUString m_ouCertDigest; + OUString m_ouEncapsulatedX509Certificate; + OUString m_ouDigestValue; + OUString m_ouSignatureValue; + OUString m_ouDate; + /// Characters of a <dc:description> element, as just read from XML. + OUString m_ouDescription; + OUString m_ouSignatureLineId; + OUString m_ouSignatureLineValidImage; + OUString m_ouSignatureLineInvalidImage; + + /* + * whether inside a particular element + */ + bool m_bInX509IssuerName; + bool m_bInX509SerialNumber; + bool m_bInX509Certificate; + bool m_bInGpgCertificate; + bool m_bInGpgKeyID; + bool m_bInGpgOwner; + bool m_bInCertDigest; + bool m_bInEncapsulatedX509Certificate; + bool m_bInSigningTime; + bool m_bInDigestValue; + bool m_bInSignatureValue; + bool m_bInDate; + bool m_bInDescription; + bool m_bInSignatureLineId; + bool m_bInSignatureLineValidImage; + bool m_bInSignatureLineInvalidImage; + + /* + * the XSecController collaborating with XSecParser + */ + XSecController* m_pXSecController; + + /* + * the next XDocumentHandler on the SAX chain + */ + css::uno::Reference< + css::xml::sax::XDocumentHandler > m_xNextHandler; + + /* + * this string is used to remember the current handled reference's URI, + * + * because it can be decided whether a stream reference is xml based or binary based + * only after the Transforms element is read in, so we have to reserve the reference's + * URI when the startElement event is met. + */ + OUString m_currentReferenceURI; + bool m_bReferenceUnresolved; + + // Relevant for ODF. The digest algorithm selected by the current DigestMethod element's + // Algorithm attribute in the current Reference element. From css::xml::crypto::DigestID. + sal_Int32 m_nReferenceDigestID; + XMLSignatureHelper& m_rXMLSignatureHelper; + +private: + static OUString getIdAttr(const css::uno::Reference< + css::xml::sax::XAttributeList >& xAttribs ); + +public: + XSecParser(XMLSignatureHelper& rXMLSignatureHelper, XSecController* pXSecController); + + /* + * XDocumentHandler + */ + virtual void SAL_CALL startDocument( ) override; + + virtual void SAL_CALL endDocument( ) override; + + virtual void SAL_CALL startElement( + const OUString& aName, + const css::uno::Reference< + css::xml::sax::XAttributeList >& xAttribs ) override; + + virtual void SAL_CALL endElement( const OUString& aName ) override; + + virtual void SAL_CALL characters( const OUString& aChars ) override; + + virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override; + + virtual void SAL_CALL processingInstruction( + const OUString& aTarget, + const OUString& aData ) override; + + virtual void SAL_CALL setDocumentLocator( + const css::uno::Reference< + css::xml::sax::XLocator >& xLocator ) override; + + /* + * XInitialization + */ + virtual void SAL_CALL initialize( + const css::uno::Sequence< css::uno::Any >& aArguments ) override; +}; + +#endif + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/xsecsign.cxx b/xmlsecurity/source/helper/xsecsign.cxx new file mode 100644 index 000000000..b9648ed64 --- /dev/null +++ b/xmlsecurity/source/helper/xsecsign.cxx @@ -0,0 +1,447 @@ +/* -*- 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 <xsecctl.hxx> + +#include <com/sun/star/xml/crypto/sax/ElementMarkPriority.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <rtl/uuid.h> +#include <sal/log.hxx> + +#include <framework/signaturecreatorimpl.hxx> +#include <framework/saxeventkeeperimpl.hxx> + +namespace com::sun::star::graphic { class XGraphic; } + +using namespace css; +using namespace css::uno; +using namespace css::graphic; + +/* protected: for signature generation */ +OUString XSecController::createId() +{ + sal_uInt8 aSeq[16]; + rtl_createUuid( aSeq, nullptr, true ); + + char str[68]="ID_"; + int length = 3; + for (sal_uInt8 i : aSeq) + { + length += sprintf(str+length, "%04x", i); + } + + return OUString::createFromAscii(str); +} + +css::uno::Reference< css::xml::crypto::sax::XReferenceResolvedListener > XSecController::prepareSignatureToWrite( + InternalSignatureInformation& internalSignatureInfor, + sal_Int32 nStorageFormat, + bool bXAdESCompliantIfODF) +{ + sal_Int32 nSecurityId = internalSignatureInfor.signatureInfor.nSecurityId; + SignatureReferenceInformations& vReferenceInfors = internalSignatureInfor.signatureInfor.vSignatureReferenceInfors; + + sal_Int32 nIdOfSignatureElementCollector; + + nIdOfSignatureElementCollector = + m_xSAXEventKeeper->addSecurityElementCollector( css::xml::crypto::sax::ElementMarkPriority_AFTERMODIFY, true ); + + m_xSAXEventKeeper->setSecurityId(nIdOfSignatureElementCollector, nSecurityId); + + rtl::Reference<SignatureCreatorImpl> xSignatureCreator(new SignatureCreatorImpl); + + css::uno::Sequence<css::uno::Any> args(5); + args[0] <<= OUString::number(nSecurityId); + args[1] <<= uno::Reference<xml::crypto::sax::XSecuritySAXEventKeeper>(static_cast<cppu::OWeakObject*>(m_xSAXEventKeeper.get()), uno::UNO_QUERY); + args[2] <<= OUString::number(nIdOfSignatureElementCollector); + + //for nss, the internal module is used for signing, which needs to be improved later + args[3] <<= m_xSecurityContext->getSecurityEnvironment(); + + args[4] <<= m_xXMLSignature; + xSignatureCreator->initialize(args); + + sal_Int32 nBlockerId = m_xSAXEventKeeper->addBlocker(); + m_xSAXEventKeeper->setSecurityId(nBlockerId, nSecurityId); + + xSignatureCreator->setBlockerId(nBlockerId); + + xSignatureCreator->addSignatureCreationResultListener(this); + + m_xSAXEventKeeper->addReferenceResolvedListener(nIdOfSignatureElementCollector, xSignatureCreator.get()); + + int size = vReferenceInfors.size(); + sal_Int32 nReferenceCount = 0; + + for(int i=0; i<size; ++i) + { + sal_Int32 keeperId = internalSignatureInfor.vKeeperIds[i]; + + if ( keeperId != -1) + { + m_xSAXEventKeeper->setSecurityId(keeperId, nSecurityId); + m_xSAXEventKeeper->addReferenceResolvedListener( keeperId, xSignatureCreator.get()); + xSignatureCreator->setReferenceId( keeperId ); + nReferenceCount++; + } + } + + xSignatureCreator->setReferenceCount( nReferenceCount ); + + /* + * adds all URI binding + */ + for(int i=0; i<size; ++i) + { + const SignatureReferenceInformation& refInfor = vReferenceInfors[i]; + + css::uno::Reference< css::io::XInputStream > xInputStream + = getObjectInputStream( refInfor.ouURI ); + + if (xInputStream.is()) + xSignatureCreator->setUriBinding(refInfor.ouURI,xInputStream); + } + + xSignatureCreator->setKeyId(0); + + // use sha512 for gpg signing unconditionally + const sal_Int32 digestID = !internalSignatureInfor.signatureInfor.ouGpgCertificate.isEmpty()? + css::xml::crypto::DigestID::SHA512 : (bXAdESCompliantIfODF ? css::xml::crypto::DigestID::SHA256 : css::xml::crypto::DigestID::SHA1); + + if (nStorageFormat != embed::StorageFormats::OFOPXML) + { + internalSignatureInfor.signatureInfor.ouSignatureId = createId(); + internalSignatureInfor.signatureInfor.ouPropertyId = createId(); + internalSignatureInfor.addReference(SignatureReferenceType::SAMEDOCUMENT, digestID, internalSignatureInfor.signatureInfor.ouPropertyId, -1, OUString() ); + size++; + + if (bXAdESCompliantIfODF) + { + // We write a new reference, so it's possible to use the correct type URI. + internalSignatureInfor.addReference(SignatureReferenceType::SAMEDOCUMENT, digestID, "idSignedProperties", -1, "http://uri.etsi.org/01903#SignedProperties"); + size++; + } + + if (!internalSignatureInfor.signatureInfor.ouDescription.isEmpty()) + { + // Only mention the hash of the description in the signature if it's non-empty. + internalSignatureInfor.signatureInfor.ouDescriptionPropertyId = createId(); + internalSignatureInfor.addReference(SignatureReferenceType::SAMEDOCUMENT, digestID, internalSignatureInfor.signatureInfor.ouDescriptionPropertyId, -1, OUString()); + size++; + } + } + else + { + internalSignatureInfor.addReference(SignatureReferenceType::SAMEDOCUMENT, digestID, "idPackageObject", -1, OUString()); + size++; + internalSignatureInfor.addReference(SignatureReferenceType::SAMEDOCUMENT, digestID, "idOfficeObject", -1, OUString()); + size++; + internalSignatureInfor.addReference(SignatureReferenceType::SAMEDOCUMENT, digestID, "idSignedProperties", -1, OUString()); + size++; + } + + /* + * replace both digestValues and signatureValue to " " + */ + for(int i=0; i<size; ++i) + { + SignatureReferenceInformation& refInfor = vReferenceInfors[i]; + refInfor.ouDigestValue = " "; + } + + internalSignatureInfor.signatureInfor.ouSignatureValue = " "; + + return xSignatureCreator.get(); +} + +void XSecController::signAStream( sal_Int32 securityId, const OUString& uri, bool isBinary, bool bXAdESCompliantIfODF) +{ + const SignatureReferenceType type = isBinary ? SignatureReferenceType::BINARYSTREAM : SignatureReferenceType::XMLSTREAM; + sal_Int32 digestID = bXAdESCompliantIfODF ? css::xml::crypto::DigestID::SHA256 : css::xml::crypto::DigestID::SHA1; + + int index = findSignatureInfor( securityId ); + + if (index == -1) + { + InternalSignatureInformation isi(securityId, nullptr); + isi.addReference(type, digestID, uri, -1, OUString()); + m_vInternalSignatureInformations.push_back( isi ); + } + else + { + // use sha512 for gpg signing unconditionally + if (!m_vInternalSignatureInformations[index].signatureInfor.ouGpgCertificate.isEmpty()) + digestID = css::xml::crypto::DigestID::SHA512; + m_vInternalSignatureInformations[index].addReference(type, digestID, uri, -1, OUString()); + } +} + +void XSecController::setX509Certificate( + sal_Int32 nSecurityId, + const OUString& ouX509IssuerName, + const OUString& ouX509SerialNumber, + const OUString& ouX509Cert, + const OUString& ouX509CertDigest, + svl::crypto::SignatureMethodAlgorithm eAlgorithmID) +{ + int index = findSignatureInfor( nSecurityId ); + + if ( index == -1 ) + { + InternalSignatureInformation isi(nSecurityId, nullptr); + isi.signatureInfor.ouX509IssuerName = ouX509IssuerName; + isi.signatureInfor.ouX509SerialNumber = ouX509SerialNumber; + isi.signatureInfor.ouX509Certificate = ouX509Cert; + isi.signatureInfor.ouCertDigest = ouX509CertDigest; + isi.signatureInfor.eAlgorithmID = eAlgorithmID; + m_vInternalSignatureInformations.push_back( isi ); + } + else + { + SignatureInformation &si + = m_vInternalSignatureInformations[index].signatureInfor; + si.ouX509IssuerName = ouX509IssuerName; + si.ouX509SerialNumber = ouX509SerialNumber; + si.ouX509Certificate = ouX509Cert; + si.ouCertDigest = ouX509CertDigest; + } +} + +void XSecController::setGpgCertificate( + sal_Int32 nSecurityId, + const OUString& ouCertDigest, + const OUString& ouCert, + const OUString& ouOwner) +{ + int index = findSignatureInfor( nSecurityId ); + + if ( index == -1 ) + { + InternalSignatureInformation isi(nSecurityId, nullptr); + isi.signatureInfor.ouGpgCertificate = ouCert; + isi.signatureInfor.ouGpgOwner = ouOwner; + isi.signatureInfor.ouCertDigest = ouCertDigest; + m_vInternalSignatureInformations.push_back( isi ); + } + else + { + SignatureInformation &si + = m_vInternalSignatureInformations[index].signatureInfor; + si.ouGpgCertificate = ouCert; + si.ouGpgOwner = ouOwner; + si.ouCertDigest = ouCertDigest; + } +} + +void XSecController::setDate( + sal_Int32 nSecurityId, + const css::util::DateTime& rDateTime ) +{ + int index = findSignatureInfor( nSecurityId ); + + if ( index == -1 ) + { + InternalSignatureInformation isi(nSecurityId, nullptr); + isi.signatureInfor.stDateTime = rDateTime; + m_vInternalSignatureInformations.push_back( isi ); + } + else + { + SignatureInformation &si + = m_vInternalSignatureInformations[index].signatureInfor; + si.stDateTime = rDateTime; + } +} + +void XSecController::setDescription(sal_Int32 nSecurityId, const OUString& rDescription) +{ + int nIndex = findSignatureInfor(nSecurityId); + + if (nIndex == -1) + { + InternalSignatureInformation aInformation(nSecurityId, nullptr); + aInformation.signatureInfor.ouDescription = rDescription; + m_vInternalSignatureInformations.push_back(aInformation); + } + else + { + SignatureInformation& rInformation = m_vInternalSignatureInformations[nIndex].signatureInfor; + rInformation.ouDescription = rDescription; + } +} + +void XSecController::setSignatureLineId(sal_Int32 nSecurityId, const OUString& rSignatureLineId) +{ + int nIndex = findSignatureInfor(nSecurityId); + + if (nIndex == -1) + { + InternalSignatureInformation aInformation(nSecurityId, nullptr); + aInformation.signatureInfor.ouSignatureLineId = rSignatureLineId; + m_vInternalSignatureInformations.push_back(aInformation); + } + else + { + SignatureInformation& rInformation = m_vInternalSignatureInformations[nIndex].signatureInfor; + rInformation.ouSignatureLineId = rSignatureLineId; + } +} + +void XSecController::setSignatureLineValidGraphic(sal_Int32 nSecurityId, + const Reference<XGraphic>& xValidGraphic) +{ + int nIndex = findSignatureInfor(nSecurityId); + + if (nIndex == -1) + { + InternalSignatureInformation aInformation(nSecurityId, nullptr); + aInformation.signatureInfor.aValidSignatureImage = xValidGraphic; + m_vInternalSignatureInformations.push_back(aInformation); + } + else + { + SignatureInformation& rInformation + = m_vInternalSignatureInformations[nIndex].signatureInfor; + rInformation.aValidSignatureImage = xValidGraphic; + } +} + +void XSecController::setSignatureLineInvalidGraphic( + sal_Int32 nSecurityId, const Reference<XGraphic>& xInvalidGraphic) +{ + int nIndex = findSignatureInfor(nSecurityId); + + if (nIndex == -1) + { + InternalSignatureInformation aInformation(nSecurityId, nullptr); + aInformation.signatureInfor.aInvalidSignatureImage = xInvalidGraphic; + m_vInternalSignatureInformations.push_back(aInformation); + } + else + { + SignatureInformation& rInformation + = m_vInternalSignatureInformations[nIndex].signatureInfor; + rInformation.aInvalidSignatureImage = xInvalidGraphic; + } +} + +bool XSecController::WriteSignature( + const css::uno::Reference<css::xml::sax::XDocumentHandler>& xDocumentHandler, + bool bXAdESCompliantIfODF ) +{ + bool rc = false; + + SAL_WARN_IF( !xDocumentHandler.is(), "xmlsecurity.helper", "I really need a document handler!" ); + + /* + * chain the SAXEventKeeper to the SAX chain + */ + chainOn(); + + if ( m_eStatusOfSecurityComponents == InitializationState::INITIALIZED ) + /* + * if all security components are ready, add the signature + * stream. + */ + { + m_bIsSAXEventKeeperSticky = true; + m_xSAXEventKeeper->setNextHandler(xDocumentHandler); + + try + { + /* + * export the signature template + */ + css::uno::Reference<css::xml::sax::XDocumentHandler> xSEKHandler(static_cast<cppu::OWeakObject*>(m_xSAXEventKeeper.get()),css::uno::UNO_QUERY); + + int i; + int sigNum = m_vInternalSignatureInformations.size(); + + for (i=0; i<sigNum; ++i) + { + InternalSignatureInformation &isi = m_vInternalSignatureInformations[i]; + + // Prepare the signature creator. + // 0 is not a documented value of embed::StorageFormats, ugh + isi.xReferenceResolvedListener = prepareSignatureToWrite( isi, 0, bXAdESCompliantIfODF ); + + exportSignature( xSEKHandler, isi.signatureInfor, bXAdESCompliantIfODF ); + } + + m_bIsSAXEventKeeperSticky = false; + chainOff(); + + rc = true; + } + catch( css::uno::Exception& ) + { + } + + m_xSAXEventKeeper->setNextHandler( nullptr ); + m_bIsSAXEventKeeperSticky = false; + } + + return rc; +} + +bool XSecController::WriteOOXMLSignature(const uno::Reference<embed::XStorage>& xRootStorage, const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler) +{ + bool bRet = false; + + SAL_WARN_IF(!xDocumentHandler.is(), "xmlsecurity.helper", "empty xDocumentHandler reference"); + + // Chain the SAXEventKeeper to the SAX chain. + chainOn(); + + if (m_eStatusOfSecurityComponents == InitializationState::INITIALIZED) + { + m_bIsSAXEventKeeperSticky = true; + m_xSAXEventKeeper->setNextHandler(xDocumentHandler); + + try + { + // Export the signature template. + css::uno::Reference<xml::sax::XDocumentHandler> xSEKHandler(static_cast<cppu::OWeakObject*>(m_xSAXEventKeeper.get()), uno::UNO_QUERY); + + for (InternalSignatureInformation & rInformation : m_vInternalSignatureInformations) + { + // Prepare the signature creator. + rInformation.xReferenceResolvedListener = prepareSignatureToWrite(rInformation, embed::StorageFormats::OFOPXML, false); + + exportOOXMLSignature(xRootStorage, xSEKHandler, rInformation.signatureInfor); + } + + m_bIsSAXEventKeeperSticky = false; + chainOff(); + + bRet = true; + } + catch(const uno::Exception&) + { + } + + m_xSAXEventKeeper->setNextHandler(nullptr); + m_bIsSAXEventKeeperSticky = false; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmlsecurity/source/helper/xsecverify.cxx b/xmlsecurity/source/helper/xsecverify.cxx new file mode 100644 index 000000000..c826971b1 --- /dev/null +++ b/xmlsecurity/source/helper/xsecverify.cxx @@ -0,0 +1,537 @@ +/* -*- 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 <config_gpgme.h> + +#include <xsecctl.hxx> +#include "xsecparser.hxx" +#include "ooxmlsecparser.hxx" +#include <framework/signatureverifierimpl.hxx> +#include <framework/saxeventkeeperimpl.hxx> +#include <gpg/xmlsignature_gpgimpl.hxx> +#include <gpg/SEInitializer.hxx> + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/xml/crypto/sax/XKeyCollector.hpp> +#include <com/sun/star/xml/crypto/sax/ElementMarkPriority.hpp> +#include <com/sun/star/xml/crypto/sax/XReferenceCollector.hpp> +#include <com/sun/star/xml/crypto/sax/XSignatureVerifyResultBroadcaster.hpp> +#include <com/sun/star/xml/crypto/XSEInitializer.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/embed/StorageFormats.hpp> +#include <sal/log.hxx> +#include <unotools/datetime.hxx> +#include <comphelper/base64.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/seqstream.hxx> + +namespace com::sun::star::graphic { class XGraphic; } + +using namespace css; +using namespace css::uno; +using namespace css::beans; + +/* protected: for signature verify */ +css::uno::Reference< css::xml::crypto::sax::XReferenceResolvedListener > XSecController::prepareSignatureToRead( + sal_Int32 nSecurityId) +{ + if ( m_eStatusOfSecurityComponents != InitializationState::INITIALIZED ) + { + return nullptr; + } + + sal_Int32 nIdOfSignatureElementCollector; + css::uno::Reference< css::xml::crypto::sax::XReferenceResolvedListener > xReferenceResolvedListener; + + nIdOfSignatureElementCollector = + m_xSAXEventKeeper->addSecurityElementCollector( css::xml::crypto::sax::ElementMarkPriority_BEFOREMODIFY, false); + + m_xSAXEventKeeper->setSecurityId(nIdOfSignatureElementCollector, nSecurityId); + + /* + * create a SignatureVerifier + */ + xReferenceResolvedListener = new SignatureVerifierImpl; + + css::uno::Reference<css::lang::XInitialization> xInitialization(xReferenceResolvedListener, css::uno::UNO_QUERY); + + css::uno::Sequence<css::uno::Any> args(5); + args[0] <<= OUString::number(nSecurityId); + args[1] <<= uno::Reference<xml::crypto::sax::XSecuritySAXEventKeeper>(static_cast<cppu::OWeakObject*>(m_xSAXEventKeeper.get()), uno::UNO_QUERY); + args[2] <<= OUString::number(nIdOfSignatureElementCollector); + args[3] <<= m_xSecurityContext; + args[4] <<= m_xXMLSignature; + xInitialization->initialize(args); + + css::uno::Reference< css::xml::crypto::sax::XSignatureVerifyResultBroadcaster > + signatureVerifyResultBroadcaster(xReferenceResolvedListener, css::uno::UNO_QUERY); + + signatureVerifyResultBroadcaster->addSignatureVerifyResultListener( this ); + + m_xSAXEventKeeper->addReferenceResolvedListener( + nIdOfSignatureElementCollector, + xReferenceResolvedListener); + + css::uno::Reference<css::xml::crypto::sax::XKeyCollector> keyCollector (xReferenceResolvedListener, css::uno::UNO_QUERY); + keyCollector->setKeyId(0); + + return xReferenceResolvedListener; +} + +void XSecController::addSignature() +{ + css::uno::Reference< css::xml::crypto::sax::XReferenceResolvedListener > xReferenceResolvedListener; + sal_Int32 nSignatureId = 0; + + + if (m_bVerifyCurrentSignature) + { + chainOn(); + xReferenceResolvedListener = prepareSignatureToRead( m_nReservedSignatureId ); + m_bVerifyCurrentSignature = false; + nSignatureId = m_nReservedSignatureId; + } + + InternalSignatureInformation isi( nSignatureId, xReferenceResolvedListener ); + m_vInternalSignatureInformations.push_back( isi ); +} + +void XSecController::setSignatureMethod(svl::crypto::SignatureMethodAlgorithm eAlgorithmID) +{ + if (m_vInternalSignatureInformations.empty()) + return; + + m_vInternalSignatureInformations.back().signatureInfor.eAlgorithmID = eAlgorithmID; +} + +void XSecController::switchGpgSignature() +{ +#if HAVE_FEATURE_GPGME + // swap signature verifier for the Gpg one + m_xXMLSignature.set(new XMLSignature_GpgImpl()); + if (!m_vInternalSignatureInformations.empty()) + { + SignatureVerifierImpl* pImpl= + dynamic_cast<SignatureVerifierImpl*>( + m_vInternalSignatureInformations.back().xReferenceResolvedListener.get()); + if (pImpl) + { + css::uno::Reference<css::xml::crypto::XSEInitializer> xGpgSEInitializer( + new SEInitializerGpg()); + pImpl->updateSignature(new XMLSignature_GpgImpl(), + xGpgSEInitializer->createSecurityContext(OUString())); + } + } +#else + (void) this; +#endif +} + +void XSecController::addReference( const OUString& ouUri, sal_Int32 nDigestID, const OUString& ouType ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::addReference: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.addReference(SignatureReferenceType::SAMEDOCUMENT, nDigestID, ouUri, -1, ouType ); +} + +void XSecController::addStreamReference( + const OUString& ouUri, + bool isBinary, + sal_Int32 nDigestID ) +{ + SignatureReferenceType type = (isBinary?SignatureReferenceType::BINARYSTREAM:SignatureReferenceType::XMLSTREAM); + + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::addStreamReference: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + + if ( isi.xReferenceResolvedListener.is() ) + { + /* + * get the input stream + */ + css::uno::Reference< css::io::XInputStream > xObjectInputStream + = getObjectInputStream( ouUri ); + + if ( xObjectInputStream.is() ) + { + css::uno::Reference<css::xml::crypto::XUriBinding> xUriBinding + (isi.xReferenceResolvedListener, css::uno::UNO_QUERY); + xUriBinding->setUriBinding(ouUri, xObjectInputStream); + } + } + + isi.addReference(type, nDigestID, ouUri, -1, OUString()); +} + +void XSecController::setReferenceCount() const +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setReferenceCount: no signature"); + return; + } + const InternalSignatureInformation &isi = + m_vInternalSignatureInformations.back(); + + if ( isi.xReferenceResolvedListener.is() ) + { + const SignatureReferenceInformations &refInfors = isi.signatureInfor.vSignatureReferenceInfors; + + int refNum = refInfors.size(); + sal_Int32 referenceCount = 0; + + for(int i=0 ; i<refNum; ++i) + { + if (refInfors[i].nType == SignatureReferenceType::SAMEDOCUMENT ) + /* + * same-document reference + */ + { + referenceCount++; + } + } + + css::uno::Reference<css::xml::crypto::sax::XReferenceCollector> xReferenceCollector + (isi.xReferenceResolvedListener, css::uno::UNO_QUERY); + xReferenceCollector->setReferenceCount( referenceCount ); + } +} + +void XSecController::setX509IssuerName( OUString const & ouX509IssuerName ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setX509IssuerName: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.signatureInfor.ouX509IssuerName = ouX509IssuerName; +} + +void XSecController::setX509SerialNumber( OUString const & ouX509SerialNumber ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setX509SerialNumber: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.signatureInfor.ouX509SerialNumber = ouX509SerialNumber; +} + +void XSecController::setX509Certificate( OUString const & ouX509Certificate ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setX509Certificate: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.signatureInfor.ouX509Certificate = ouX509Certificate; +} + +void XSecController::setSignatureValue( OUString const & ouSignatureValue ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setSignatureValue: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.signatureInfor.ouSignatureValue = ouSignatureValue; +} + +void XSecController::setDigestValue( sal_Int32 nDigestID, OUString const & ouDigestValue ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setDigestValue: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + if (isi.signatureInfor.vSignatureReferenceInfors.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setDigestValue: no signature reference"); + return; + } + SignatureReferenceInformation &reference = + isi.signatureInfor.vSignatureReferenceInfors.back(); + reference.nDigestID = nDigestID; + reference.ouDigestValue = ouDigestValue; +} + +void XSecController::setGpgKeyID( OUString const & ouKeyID ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setGpgKeyID: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.signatureInfor.ouGpgKeyID = ouKeyID; +} + +void XSecController::setGpgCertificate( OUString const & ouGpgCert ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setGpgCertificate: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.signatureInfor.ouGpgCertificate = ouGpgCert; +} + +void XSecController::setGpgOwner( OUString const & ouGpgOwner ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setGpgOwner: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.signatureInfor.ouGpgOwner = ouGpgOwner; +} + +void XSecController::setDate( OUString const & ouDate ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setDate: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + (void)utl::ISO8601parseDateTime( ouDate, isi.signatureInfor.stDateTime); + isi.signatureInfor.ouDateTime = ouDate; +} + +void XSecController::setDescription(const OUString& rDescription) +{ + if (m_vInternalSignatureInformations.empty()) + return; + + InternalSignatureInformation& rInformation = m_vInternalSignatureInformations.back(); + rInformation.signatureInfor.ouDescription = rDescription; +} + +void XSecController::setSignatureBytes(const uno::Sequence<sal_Int8>& rBytes) +{ + if (m_vInternalSignatureInformations.empty()) + return; + + InternalSignatureInformation& rInformation = m_vInternalSignatureInformations.back(); + rInformation.signatureInfor.aSignatureBytes = rBytes; +} + +void XSecController::setCertDigest(const OUString& rCertDigest) +{ + if (m_vInternalSignatureInformations.empty()) + return; + + InternalSignatureInformation& rInformation = m_vInternalSignatureInformations.back(); + rInformation.signatureInfor.ouCertDigest = rCertDigest; +} + +namespace { +Reference<css::graphic::XGraphic> lcl_getGraphicFromString(const OUString& rImage) +{ + Sequence<sal_Int8> seq; + comphelper::Base64::decode(seq, rImage); + + Reference< graphic::XGraphic > xGraphic; + if( !seq.hasElements() ) + return Reference<css::graphic::XGraphic>(); + + Reference< graphic::XGraphicProvider > xGraphicProvider( + graphic::GraphicProvider::create(comphelper::getProcessComponentContext()) ); + Reference< io::XInputStream > xInputStream( new ::comphelper::SequenceInputStream( seq ) ); + + Sequence< PropertyValue > aArgs( 1 ); + aArgs[ 0 ].Name = "InputStream"; + aArgs[ 0 ].Value <<= xInputStream; + xGraphic = xGraphicProvider->queryGraphic(aArgs); + + return xGraphic; +} +} + +void XSecController::setValidSignatureImage(const OUString& rValidSigImg) +{ + if (m_vInternalSignatureInformations.empty() || rValidSigImg.isEmpty()) + return; + + InternalSignatureInformation& rInformation = m_vInternalSignatureInformations.back(); + rInformation.signatureInfor.aValidSignatureImage = lcl_getGraphicFromString(rValidSigImg); +} + +void XSecController::setInvalidSignatureImage(const OUString& rInvalidSigImg) +{ + if (m_vInternalSignatureInformations.empty() || rInvalidSigImg.isEmpty()) + return; + + InternalSignatureInformation& rInformation = m_vInternalSignatureInformations.back(); + rInformation.signatureInfor.aInvalidSignatureImage = lcl_getGraphicFromString(rInvalidSigImg); +} + +void XSecController::setSignatureLineId(const OUString& rSignatureLineId) +{ + if (m_vInternalSignatureInformations.empty()) + return; + + InternalSignatureInformation& rInformation = m_vInternalSignatureInformations.back(); + rInformation.signatureInfor.ouSignatureLineId = rSignatureLineId; +} + +void XSecController::addEncapsulatedX509Certificate(const OUString& rEncapsulatedX509Certificate) +{ + if (m_vInternalSignatureInformations.empty()) + return; + + if (rEncapsulatedX509Certificate.isEmpty()) + return; + + InternalSignatureInformation& rInformation = m_vInternalSignatureInformations.back(); + rInformation.signatureInfor.maEncapsulatedX509Certificates.insert(rEncapsulatedX509Certificate); +} + +void XSecController::setId( OUString const & ouId ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setId: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + isi.signatureInfor.ouSignatureId = ouId; +} + +void XSecController::setPropertyId( OUString const & ouPropertyId ) +{ + if (m_vInternalSignatureInformations.empty()) + { + SAL_INFO("xmlsecurity.helper","XSecController::setPropertyId: no signature"); + return; + } + InternalSignatureInformation &isi = m_vInternalSignatureInformations.back(); + + if (isi.signatureInfor.ouPropertyId.isEmpty()) + { + // <SignatureProperty> ID attribute is for the date. + isi.signatureInfor.ouPropertyId = ouPropertyId; + } + else + { + // <SignatureProperty> ID attribute is for the description. + isi.signatureInfor.ouDescriptionPropertyId = ouPropertyId; + } +} + +/* public: for signature verify */ +void XSecController::collectToVerify( const OUString& referenceId ) +{ + /* SAL_WARN_IF( !m_xSAXEventKeeper.is(), "xmlsecurity", "the SAXEventKeeper is NULL" ); */ + + if ( m_eStatusOfSecurityComponents == InitializationState::INITIALIZED ) + /* + * if all security components are ready, verify the signature. + */ + { + bool bJustChainingOn = false; + css::uno::Reference< css::xml::sax::XDocumentHandler > xHandler; + + int i,j; + int sigNum = m_vInternalSignatureInformations.size(); + + for (i=0; i<sigNum; ++i) + { + InternalSignatureInformation& isi = m_vInternalSignatureInformations[i]; + SignatureReferenceInformations& vReferenceInfors = isi.signatureInfor.vSignatureReferenceInfors; + int refNum = vReferenceInfors.size(); + + for (j=0; j<refNum; ++j) + { + SignatureReferenceInformation &refInfor = vReferenceInfors[j]; + + if (refInfor.ouURI == referenceId) + { + if (chainOn()) + { + bJustChainingOn = true; + xHandler = m_xSAXEventKeeper->setNextHandler(nullptr); + } + + sal_Int32 nKeeperId = m_xSAXEventKeeper->addSecurityElementCollector( + css::xml::crypto::sax::ElementMarkPriority_BEFOREMODIFY, false ); + + css::uno::Reference<css::xml::crypto::sax::XReferenceCollector> xReferenceCollector + ( isi.xReferenceResolvedListener, css::uno::UNO_QUERY ); + + m_xSAXEventKeeper->setSecurityId(nKeeperId, isi.signatureInfor.nSecurityId); + m_xSAXEventKeeper->addReferenceResolvedListener( nKeeperId, isi.xReferenceResolvedListener); + xReferenceCollector->setReferenceId( nKeeperId ); + + isi.vKeeperIds[j] = nKeeperId; + break; + } + } + } + + if ( bJustChainingOn ) + { + m_xSAXEventKeeper->setNextHandler(xHandler); + } + } +} + +void XSecController::addSignature( sal_Int32 nSignatureId ) +{ + SAL_WARN_IF( !m_xSecParser.is(), "xmlsecurity.helper", "No XSecParser initialized" ); + + m_nReservedSignatureId = nSignatureId; + m_bVerifyCurrentSignature = true; +} + +css::uno::Reference< css::xml::sax::XDocumentHandler > const & XSecController::createSignatureReader(XMLSignatureHelper& rXMLSignatureHelper, sal_Int32 nType) +{ + if (nType == embed::StorageFormats::OFOPXML) + m_xSecParser = new OOXMLSecParser(rXMLSignatureHelper, this); + else + m_xSecParser = new XSecParser(rXMLSignatureHelper, this); + css::uno::Reference< css::lang::XInitialization > xInitialization(m_xSecParser, uno::UNO_QUERY); + + setSAXChainConnector(xInitialization); + + return m_xSecParser; +} + +void XSecController::releaseSignatureReader() +{ + clearSAXChainConnector( ); + m_xSecParser.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |