diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /xmlsecurity/source/helper/xmlsignaturehelper.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xmlsecurity/source/helper/xmlsignaturehelper.cxx')
-rw-r--r-- | xmlsecurity/source/helper/xmlsignaturehelper.cxx | 714 |
1 files changed, 714 insertions, 0 deletions
diff --git a/xmlsecurity/source/helper/xmlsignaturehelper.cxx b/xmlsecurity/source/helper/xmlsignaturehelper.cxx new file mode 100644 index 000000000..0d9755d0e --- /dev/null +++ b/xmlsecurity/source/helper/xmlsignaturehelper.cxx @@ -0,0 +1,714 @@ +/* -*- 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 <biginteger.hxx> + +#include <UriBindingHelper.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 <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include <optional> + +constexpr OUStringLiteral NS_DOCUMENTSIGNATURES = u"http://openoffice.org/2004/documentsignatures"; +constexpr OUStringLiteral NS_DOCUMENTSIGNATURES_ODF_1_2 = u"urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0"; +constexpr OUStringLiteral OOXML_SIGNATURE_ORIGIN = u"http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin"; +constexpr OUStringLiteral OOXML_SIGNATURE_SIGNATURE = u"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 + */ + rtl::Reference<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", + 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 ParserInputSource + 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 ); + + // Parse the stream. + try + { + xParser->parseStream( aParserInput ); + } + catch( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("xmlsecurity.helper"); + 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, u"origin.sigs.rels", mxCtx); + + for (sal_Int32 i = 0; i < aRelationsInfo.getLength(); ++i) + { + const uno::Sequence<beans::StringPair>& rRelation = aRelationsInfo[i]; + if (std::any_of(rRelation.begin(), rRelation.end(), lcl_isSignatureType)) + { + auto it = std::find_if(rRelation.begin(), rRelation.end(), [](const beans::StringPair& rPair) { return rPair.First == "Target"; }); + if (it != rRelation.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"); + mbError = true; + } + + // release the signature reader + 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, u".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; + } + auto pContentTypeInfo = aContentTypeInfo.getArray(); + + // Append rels and sigs to defaults, if it's not there already. + uno::Sequence<beans::StringPair>& rDefaults = pContentTypeInfo[0]; + auto aDefaults = comphelper::sequenceToContainer< std::vector<beans::StringPair> >(rDefaults); + if (std::none_of(std::cbegin(rDefaults), std::cend(rDefaults), [](const beans::StringPair& rPair) { return rPair.First == "rels"; })) + aDefaults.emplace_back("rels", "application/vnd.openxmlformats-package.relationships+xml"); + + if (std::none_of(std::cbegin(rDefaults), std::cend(rDefaults), [](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 = pContentTypeInfo[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(); +} + +/** check this constraint from xmldsig-core 4.5.4: + + All certificates appearing in an X509Data element MUST relate to the + validation key by either containing it or being part of a certification + chain that terminates in a certificate containing the validation key. + */ +static auto CheckX509Data( + uno::Reference<xml::crypto::XSecurityEnvironment> const& xSecEnv, + std::vector<SignatureInformation::X509CertInfo> const& rX509CertInfos, + std::vector<uno::Reference<security::XCertificate>> & rCerts, + std::vector<SignatureInformation::X509CertInfo> & rSorted) -> bool +{ + assert(rCerts.empty()); + assert(rSorted.empty()); + if (rX509CertInfos.empty()) + { + SAL_WARN("xmlsecurity.comp", "no X509Data"); + return false; + } + std::vector<uno::Reference<security::XCertificate>> certs; + for (SignatureInformation::X509CertInfo const& it : rX509CertInfos) + { + if (!it.X509Certificate.isEmpty()) + { + certs.emplace_back(xSecEnv->createCertificateFromAscii(it.X509Certificate)); + } + else + { + certs.emplace_back(xSecEnv->getCertificate( + it.X509IssuerName, + xmlsecurity::numericStringToBigInteger(it.X509SerialNumber))); + } + if (!certs.back().is()) + { + SAL_WARN("xmlsecurity.comp", "X509Data cannot be parsed"); + return false; + } + } + + // first, search one whose issuer isn't in the list, or a self-signed one + std::optional<size_t> start; + for (size_t i = 0; i < certs.size(); ++i) + { + for (size_t j = 0; ; ++j) + { + if (j == certs.size()) + { + if (start) + { + SAL_WARN("xmlsecurity.comp", "X509Data do not form a chain: certificate has no issuer but already have start of chain: " << certs[i]->getSubjectName()); + return false; + } + start = i; // issuer isn't in the list + break; + } + if (xmlsecurity::EqualDistinguishedNames(certs[i]->getIssuerName(), certs[j]->getSubjectName(), xmlsecurity::NOCOMPAT)) + { + if (i == j) // self signed + { + if (start) + { + SAL_WARN("xmlsecurity.comp", "X509Data do not form a chain: certificate is self-signed but already have start of chain: " << certs[i]->getSubjectName()); + return false; + } + start = i; + } + break; + } + } + } + std::vector<size_t> chain; + if (!start) + { + // this can only be a cycle? + SAL_WARN("xmlsecurity.comp", "X509Data do not form a chain: cycle detected"); + return false; + } + chain.emplace_back(*start); + + // second, check that there is a chain, no tree or cycle... + for (size_t i = 0; i < certs.size(); ++i) + { + assert(chain.size() == i + 1); + for (size_t j = 0; j < certs.size(); ++j) + { + if (chain[i] != j) + { + if (xmlsecurity::EqualDistinguishedNames( + certs[chain[i]]->getSubjectName(), certs[j]->getIssuerName(), xmlsecurity::NOCOMPAT)) + { + if (chain.size() != i + 1) // already found issue? + { + SAL_WARN("xmlsecurity.comp", "X509Data do not form a chain: certificate issued 2 others: " << certs[chain[i]]->getSubjectName()); + return false; + } + chain.emplace_back(j); + } + } + } + if (i == certs.size() - 1) + { // last one: must be a leaf + if (chain.size() != i + 1) + { + SAL_WARN("xmlsecurity.comp", "X509Data do not form a chain: certificate in cycle: " << certs[chain[i]]->getSubjectName()); + return false; + } + } + else if (chain.size() != i + 2) + { // not issuer of another? + SAL_WARN("xmlsecurity.comp", "X509Data do not form a chain: certificate issued 0 others: " << certs[chain[i]]->getSubjectName()); + return false; + } + } + + // success + assert(chain.size() == rX509CertInfos.size()); + for (auto const& it : chain) + { + rSorted.emplace_back(rX509CertInfos[it]); + rCerts.emplace_back(certs[it]); + } + return true; +} + +std::vector<uno::Reference<security::XCertificate>> +XMLSignatureHelper::CheckAndUpdateSignatureInformation( + uno::Reference<xml::crypto::XSecurityEnvironment> const& xSecEnv, + SignatureInformation const& rInfo) +{ + // if the check fails, it's not possible to determine which X509Data + // contained the signing certificate - the UI cannot display something + // useful in this case, so prevent anything misleading by clearing the + // X509Datas. + + std::vector<uno::Reference<security::XCertificate>> certs; + std::vector<SignatureInformation::X509Data> datas; + // TODO: for now, just merge all X509Datas together for checking... + // (this will probably break round-trip of signature with multiple X509Data, + // no idea if that is a problem) + SignatureInformation::X509Data temp; + SignatureInformation::X509Data tempResult; + for (auto const& rData : rInfo.X509Datas) + { + for (auto const& it : rData) + { + temp.emplace_back(it); + } + } + if (CheckX509Data(xSecEnv, temp, certs, tempResult)) + { + datas.emplace_back(tempResult); + } + + // rInfo is a copy, update the original + mpXSecController->UpdateSignatureInformation(rInfo.nSecurityId, std::move(datas)); + return certs; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |