schema/libreoffice/OpenDocument-v1.3+libreoffice-manifest-schema.rng |   70 +-
 test/source/xmltesttools.cxx                                         |    2 
 xmlsecurity/qa/unit/signing/data/encrypted_scriptsig_lo242.odt       |binary
 xmlsecurity/qa/unit/signing/data/encrypted_scriptsig_odf13.odt       |binary
 xmlsecurity/qa/unit/signing/signing2.cxx                             |  308 
++++++++++
 xmlsecurity/source/helper/xmlsignaturehelper.cxx                     |   68 ++
 6 files changed, 426 insertions(+), 22 deletions(-)

New commits:
commit 45f3d436d0a1271b42c22d9244cc24d237c94ff9
Author:     Michael Stahl <[email protected]>
AuthorDate: Wed Jan 10 20:37:50 2024 +0100
Commit:     Caolán McNamara <[email protected]>
CommitDate: Sat Jan 20 17:39:30 2024 +0100

    tdf#105844 add test for ODF wholesome encryption with macro signature
    
    ... plus manifest schema extension.
    
    Change-Id: I73721db8620e97bd58556f9a71afcb0a33f6c7e8
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161898
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <[email protected]>
    (cherry picked from commit 4d6e9d5e155da1dde05233eb87691e2a454162f6)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161866
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git 
a/schema/libreoffice/OpenDocument-v1.3+libreoffice-manifest-schema.rng 
b/schema/libreoffice/OpenDocument-v1.3+libreoffice-manifest-schema.rng
index a2631facc7f5..77b87101b04a 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-manifest-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-manifest-schema.rng
@@ -14,7 +14,9 @@
 -->
 <!-- https://issues.oasis-open.org/browse/OFFICE-2153 -->
 
-<rng:grammar 
xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" 
xmlns:rng="http://relaxng.org/ns/structure/1.0"; 
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes";>
+<rng:grammar 
xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
+xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
+xmlns:rng="http://relaxng.org/ns/structure/1.0"; 
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes";>
   <rng:start>
     <rng:choice>
       <rng:ref name="manifest"/>
@@ -103,17 +105,19 @@
     </rng:element>
   </rng:define>
   <rng:define name="encryption-data-attlist">
-    <rng:interleave>
-      <rng:attribute name="manifest:checksum-type">
-        <rng:choice>
-          <rng:value>SHA1/1K</rng:value>
-          <rng:ref name="anyURI"/>
-        </rng:choice>
-      </rng:attribute>
-      <rng:attribute name="manifest:checksum">
-        <rng:ref name="base64Binary"/>
-      </rng:attribute>
-    </rng:interleave>
+    <rng:optional>
+      <rng:interleave>
+        <rng:attribute name="manifest:checksum-type">
+          <rng:choice>
+            <rng:value>SHA1/1K</rng:value>
+            <rng:ref name="anyURI"/>
+         </rng:choice>
+        </rng:attribute>
+        <rng:attribute name="manifest:checksum">
+          <rng:ref name="base64Binary"/>
+        </rng:attribute>
+      </rng:interleave>
+    </rng:optional>
   </rng:define>
   <rng:define name="file-entry">
     <rng:element name="manifest:file-entry">
@@ -165,18 +169,39 @@
         <rng:value>PGP</rng:value>
       </rng:attribute>
       <rng:interleave>
-        <rng:attribute name="manifest:key-derivation-name">
-          <rng:choice>
-            <rng:value>PBKDF2</rng:value>
-            <rng:ref name="anyURI"/>
-          </rng:choice>
-        </rng:attribute>
+        <rng:choice>
+          <rng:interleave>
+            <rng:attribute name="manifest:key-derivation-name">
+              <rng:choice>
+                <rng:value>PBKDF2</rng:value>
+                <rng:ref name="anyURI"/>
+              </rng:choice>
+            </rng:attribute>
+            <rng:attribute name="manifest:iteration-count">
+              <rng:ref name="nonNegativeInteger"/>
+            </rng:attribute>
+          </rng:interleave>
+          <rng:interleave>
+            <rng:attribute name="manifest:key-derivation-name">
+            <!--
+              
<rng:value>urn:oasis:names:tc:opendocument:xmlns:manifest:1.5#argon2id</rng:value>
+              -->
+              
<rng:value>urn:org:documentfoundation:names:experimental:office:manifest:argon2id</rng:value>
+            </rng:attribute>
+            <rng:attribute name="loext:argon2-iterations">
+              <rng:ref name="positiveInteger"/>
+            </rng:attribute>
+            <rng:attribute name="loext:argon2-memory">
+              <rng:ref name="positiveInteger"/>
+            </rng:attribute>
+            <rng:attribute name="loext:argon2-lanes">
+              <rng:ref name="positiveInteger"/>
+            </rng:attribute>
+          </rng:interleave>
+        </rng:choice>
         <rng:attribute name="manifest:salt">
           <rng:ref name="base64Binary"/>
         </rng:attribute>
-        <rng:attribute name="manifest:iteration-count">
-          <rng:ref name="nonNegativeInteger"/>
-        </rng:attribute>
         <rng:optional>
           <rng:attribute name="manifest:key-size">
             <rng:ref name="nonNegativeInteger"/>
@@ -214,6 +239,9 @@
   <rng:define name="nonNegativeInteger">
     <rng:data type="nonNegativeInteger"/>
   </rng:define>
+  <rng:define name="positiveInteger">
+    <rng:data type="positiveInteger"/>
+  </rng:define>
   <rng:define name="start-key-generation">
     <rng:element name="manifest:start-key-generation">
       <rng:ref name="start-key-generation-attlist"/>
diff --git a/test/source/xmltesttools.cxx b/test/source/xmltesttools.cxx
index 14f953557f5f..eff0247c8511 100644
--- a/test/source/xmltesttools.cxx
+++ b/test/source/xmltesttools.cxx
@@ -294,6 +294,8 @@ int XmlTestTools::getXPathPosition(const xmlDocUniquePtr& 
pXmlDoc, const OString
 
 void XmlTestTools::registerODFNamespaces(xmlXPathContextPtr& pXmlXpathCtx)
 {
+    xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("manifest"),
+                       
BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"));
     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("office"),
                        
BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("style"),
diff --git a/xmlsecurity/qa/unit/signing/data/encrypted_scriptsig_lo242.odt 
b/xmlsecurity/qa/unit/signing/data/encrypted_scriptsig_lo242.odt
new file mode 100644
index 000000000000..1747448ac5ad
Binary files /dev/null and 
b/xmlsecurity/qa/unit/signing/data/encrypted_scriptsig_lo242.odt differ
diff --git a/xmlsecurity/qa/unit/signing/data/encrypted_scriptsig_odf13.odt 
b/xmlsecurity/qa/unit/signing/data/encrypted_scriptsig_odf13.odt
new file mode 100644
index 000000000000..fb2b978bc032
Binary files /dev/null and 
b/xmlsecurity/qa/unit/signing/data/encrypted_scriptsig_odf13.odt differ
diff --git a/xmlsecurity/qa/unit/signing/signing2.cxx 
b/xmlsecurity/qa/unit/signing/signing2.cxx
index aef3c7f08885..88b3ab99a705 100644
--- a/xmlsecurity/qa/unit/signing/signing2.cxx
+++ b/xmlsecurity/qa/unit/signing/signing2.cxx
@@ -9,14 +9,27 @@
 
 #include <sal/config.h>
 
+#include <config_crypto.h>
+
+#if USE_CRYPTO_NSS
+#include <secoid.h>
+#endif
+
 #include <test/unoapixml_test.hxx>
 
+#include <com/sun/star/beans/XPropertySet.hpp>
 #include <com/sun/star/embed/XStorage.hpp>
 #include <com/sun/star/frame/Desktop.hpp>
 #include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/text/XTextDocument.hpp>
 #include <com/sun/star/util/XCloseable.hpp>
 #include <com/sun/star/xml/crypto/SEInitializer.hpp>
 
+#include <officecfg/Office/Common.hxx>
+
+#include <sfx2/sfxbasemodel.hxx>
+#include <sfx2/objsh.hxx>
+#include <comphelper/documentconstants.hxx>
 #include <comphelper/propertysequence.hxx>
 #include <unotools/tempfile.hxx>
 #include <unotools/ucbstreamhelper.hxx>
@@ -30,8 +43,14 @@ using namespace css;
 /// Testsuite for the document signing feature.
 class SigningTest2 : public UnoApiXmlTest
 {
+protected:
+    uno::Reference<xml::crypto::XSEInitializer> mxSEInitializer;
+    uno::Reference<xml::crypto::XXMLSecurityContext> mxSecurityContext;
+
 public:
     SigningTest2();
+    virtual void setUp() override;
+    virtual void tearDown() override;
     void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override;
 };
 
@@ -40,6 +59,31 @@ SigningTest2::SigningTest2()
 {
 }
 
+void SigningTest2::setUp()
+{
+    UnoApiXmlTest::setUp();
+
+    MacrosTest::setUpNssGpg(m_directories, "xmlsecurity_signing2");
+
+    // Initialize crypto after setting up the environment variables.
+    mxSEInitializer = xml::crypto::SEInitializer::create(mxComponentContext);
+    mxSecurityContext = mxSEInitializer->createSecurityContext(OUString());
+#if USE_CRYPTO_NSS
+#ifdef NSS_USE_ALG_IN_ANY_SIGNATURE
+    // policy may disallow using SHA1 for signatures but unit test documents
+    // have such existing signatures (call this after createSecurityContext!)
+    NSS_SetAlgorithmPolicy(SEC_OID_SHA1, NSS_USE_ALG_IN_ANY_SIGNATURE, 0);
+#endif
+#endif
+}
+
+void SigningTest2::tearDown()
+{
+    MacrosTest::tearDownNssGpg();
+
+    UnoApiXmlTest::tearDown();
+}
+
 /// Test if a macro signature from a ODF Database is preserved when saving
 CPPUNIT_TEST_FIXTURE(SigningTest2, testPreserveMacroSignatureODB)
 {
@@ -66,6 +110,263 @@ CPPUNIT_TEST_FIXTURE(SigningTest2, 
testPreserveMacroSignatureODB)
                 
"ID_00a7002f009000bc00ce00f7004400460080002f002e00e400e0003700df00e8");
 }
 
+CPPUNIT_TEST_FIXTURE(SigningTest2, testPasswordPreserveMacroSignatureODF13)
+{
+    // load ODF 1.3 encrypted document
+    load(createFileURL(u"encrypted_scriptsig_odf13.odt"), "password");
+    {
+        uno::Reference<text::XTextDocument> xTextDoc(mxComponent, 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(OUString("secret"), 
xTextDoc->getText()->getString());
+        // test macro signature
+        SfxBaseModel* 
pBaseModel(dynamic_cast<SfxBaseModel*>(mxComponent.get()));
+        CPPUNIT_ASSERT(pBaseModel);
+        SfxObjectShell* pObjectShell(pBaseModel->GetObjectShell());
+        uno::Reference<beans::XPropertySet> 
xPropSet(pObjectShell->GetStorage(),
+                                                     uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(ODFVER_013_TEXT,
+                             
xPropSet->getPropertyValue("Version").get<OUString>());
+        CPPUNIT_ASSERT_EQUAL(SignatureState::OK, 
pObjectShell->GetScriptingSignatureState());
+    }
+
+    saveAndReload("writer8", "password");
+    {
+        // test standard ODF 1.2/1.3/1.4 encryption
+        xmlDocUniquePtr pXmlDoc = parseExport("META-INF/manifest.xml");
+        assertXPath(pXmlDoc, "/manifest:manifest"_ostr, "version"_ostr, "1.3");
+        assertXPath(pXmlDoc, 
"/manifest:manifest/manifest:file-entry[@manifest:size != '0']"_ostr,
+                    8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data[@manifest:checksum-type
 and @manifest:checksum]"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:algorithm[@manifest:algorithm-name='http://www.w3.org/2001/04/xmlenc#aes256-cbc']"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:algorithm[string-length(@manifest:initialisation-vector)
 = 24]"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:start-key-generation[@manifest:start-key-generation-name='http://www.w3.org/2000/09/xmldsig#sha256'
 and @manifest:key-size='32']"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@manifest:key-derivation-name='PBKDF2'
 and @manifest:key-size='32']"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@manifest:iteration-count='100000']"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[string-length(@manifest:salt)
 = 24]"_ostr,
+            8);
+        // test reimport
+        uno::Reference<text::XTextDocument> xTextDoc(mxComponent, 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(OUString("secret"), 
xTextDoc->getText()->getString());
+        // test macro signature - this didn't actually work!
+        // using Zip Storage means the encrypted streams are signed, so
+        // after encrypting again the sigature didn't match and was dropped
+        //        assertDocument(CPPUNIT_SOURCELINE(), "writer8", 
SignatureState::NOSIGNATURES,
+        //                       SignatureState::OK, ODFVER_013_TEXT);
+    }
+
+    {
+        Resetter resetter([]() {
+            std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+                comphelper::ConfigurationChanges::create());
+            officecfg::Office::Common::Misc::ExperimentalMode::set(false, 
pBatch);
+            return pBatch->commit();
+        });
+        std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+            comphelper::ConfigurationChanges::create());
+        officecfg::Office::Common::Misc::ExperimentalMode::set(true, pBatch);
+        pBatch->commit();
+
+        // store it experimental - reload
+        saveAndReload("writer8", "password");
+
+        // test wholesome ODF extended encryption
+        xmlDocUniquePtr pXmlDoc = parseExport("META-INF/manifest.xml");
+        assertXPath(pXmlDoc, "/manifest:manifest"_ostr, "version"_ostr, "1.3");
+        assertXPath(pXmlDoc, "/manifest:manifest/manifest:file-entry"_ostr, 1);
+        assertXPath(pXmlDoc, "/manifest:manifest/manifest:file-entry"_ostr, 
"full-path"_ostr,
+                    "encrypted-package");
+        assertXPath(pXmlDoc, 
"/manifest:manifest/manifest:file-entry[@manifest:size != '0']"_ostr,
+                    1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data[@manifest:checksum-type
 or @manifest:checksum]"_ostr,
+            0);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:algorithm[@manifest:algorithm-name='http://www.w3.org/2009/xmlenc11#aes256-gcm']"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:algorithm[string-length(@manifest:initialisation-vector)
 = 16]"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:start-key-generation[@manifest:start-key-generation-name='http://www.w3.org/2001/04/xmlenc#sha256'
 and @manifest:key-size='32']"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@manifest:key-derivation-name='urn:org:documentfoundation:names:experimental:office:manifest:argon2id'
 and @manifest:key-size='32']"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@manifest:iteration-count]"_ostr,
+            0);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[string-length(@manifest:salt)
 = 24]"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@loext:argon2-iterations='3'
 and @loext:argon2-memory='65536' and @loext:argon2-lanes='4']"_ostr,
+            1);
+        // test reimport
+        uno::Reference<text::XTextDocument> xTextDoc(mxComponent, 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(OUString("secret"), 
xTextDoc->getText()->getString());
+    }
+}
+
+CPPUNIT_TEST_FIXTURE(SigningTest2, 
testPasswordPreserveMacroSignatureODFWholesomeLO242)
+{
+    // load wholesome ODF (extended) encrypted document
+    load(createFileURL(u"encrypted_scriptsig_lo242.odt"), "password");
+    {
+        uno::Reference<text::XTextDocument> xTextDoc(mxComponent, 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(OUString("secret"), 
xTextDoc->getText()->getString());
+        // test macro signature
+        SfxBaseModel* 
pBaseModel(dynamic_cast<SfxBaseModel*>(mxComponent.get()));
+        CPPUNIT_ASSERT(pBaseModel);
+        SfxObjectShell* pObjectShell(pBaseModel->GetObjectShell());
+        uno::Reference<beans::XPropertySet> 
xPropSet(pObjectShell->GetStorage(),
+                                                     uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(ODFVER_013_TEXT,
+                             
xPropSet->getPropertyValue("Version").get<OUString>());
+        CPPUNIT_ASSERT_EQUAL(SignatureState::OK, 
pObjectShell->GetScriptingSignatureState());
+    }
+
+    {
+        Resetter resetter([]() {
+            std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+                comphelper::ConfigurationChanges::create());
+            officecfg::Office::Common::Misc::ExperimentalMode::set(false, 
pBatch);
+            return pBatch->commit();
+        });
+        std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+            comphelper::ConfigurationChanges::create());
+        officecfg::Office::Common::Misc::ExperimentalMode::set(true, pBatch);
+        pBatch->commit();
+
+        // store it experimental - reload
+        saveAndReload("writer8", "password");
+
+        // test wholesome ODF extended encryption
+        xmlDocUniquePtr pXmlDoc = parseExport("META-INF/manifest.xml");
+        assertXPath(pXmlDoc, "/manifest:manifest"_ostr, "version"_ostr, "1.3");
+        assertXPath(pXmlDoc, "/manifest:manifest/manifest:file-entry"_ostr, 1);
+        assertXPath(pXmlDoc, "/manifest:manifest/manifest:file-entry"_ostr, 
"full-path"_ostr,
+                    "encrypted-package");
+        assertXPath(pXmlDoc, 
"/manifest:manifest/manifest:file-entry[@manifest:size != '0']"_ostr,
+                    1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data[@manifest:checksum-type
 or @manifest:checksum]"_ostr,
+            0);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:algorithm[@manifest:algorithm-name='http://www.w3.org/2009/xmlenc11#aes256-gcm']"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:algorithm[string-length(@manifest:initialisation-vector)
 = 16]"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:start-key-generation[@manifest:start-key-generation-name='http://www.w3.org/2001/04/xmlenc#sha256'
 and @manifest:key-size='32']"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@manifest:key-derivation-name='urn:org:documentfoundation:names:experimental:office:manifest:argon2id'
 and @manifest:key-size='32']"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@manifest:iteration-count]"_ostr,
+            0);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[string-length(@manifest:salt)
 = 24]"_ostr,
+            1);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@loext:argon2-iterations='3'
 and @loext:argon2-memory='65536' and @loext:argon2-lanes='4']"_ostr,
+            1);
+        // test reimport
+        uno::Reference<text::XTextDocument> xTextDoc(mxComponent, 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(OUString("secret"), 
xTextDoc->getText()->getString());
+        // test macro signature - this should work now
+        SfxBaseModel* 
pBaseModel(dynamic_cast<SfxBaseModel*>(mxComponent.get()));
+        CPPUNIT_ASSERT(pBaseModel);
+        SfxObjectShell* pObjectShell(pBaseModel->GetObjectShell());
+        uno::Reference<beans::XPropertySet> 
xPropSet(pObjectShell->GetStorage(),
+                                                     uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(ODFVER_013_TEXT,
+                             
xPropSet->getPropertyValue("Version").get<OUString>());
+        CPPUNIT_ASSERT_EQUAL(SignatureState::OK, 
pObjectShell->GetScriptingSignatureState());
+    }
+
+    saveAndReload("writer8", "password");
+    {
+        // test standard ODF 1.2/1.3/1.4 encryption
+        xmlDocUniquePtr pXmlDoc = parseExport("META-INF/manifest.xml");
+        assertXPath(pXmlDoc, "/manifest:manifest"_ostr, "version"_ostr, "1.3");
+        assertXPath(pXmlDoc, 
"/manifest:manifest/manifest:file-entry[@manifest:size != '0']"_ostr,
+                    8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data[@manifest:checksum-type
 and @manifest:checksum]"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:algorithm[@manifest:algorithm-name='http://www.w3.org/2001/04/xmlenc#aes256-cbc']"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:algorithm[string-length(@manifest:initialisation-vector)
 = 24]"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:start-key-generation[@manifest:start-key-generation-name='http://www.w3.org/2000/09/xmldsig#sha256'
 and @manifest:key-size='32']"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@manifest:key-derivation-name='PBKDF2'
 and @manifest:key-size='32']"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[@manifest:iteration-count='100000']"_ostr,
+            8);
+        assertXPath(
+            pXmlDoc,
+            
"/manifest:manifest/manifest:file-entry/manifest:encryption-data/manifest:key-derivation[string-length(@manifest:salt)
 = 24]"_ostr,
+            8);
+        // test reimport
+        uno::Reference<text::XTextDocument> xTextDoc(mxComponent, 
uno::UNO_QUERY_THROW);
+        CPPUNIT_ASSERT_EQUAL(OUString("secret"), 
xTextDoc->getText()->getString());
+        // test macro signature - this didn't actually work!
+        // using Zip Storage means the encrypted streams are signed, so
+        // after encrypting again the sigature didn't match and was dropped
+        //        assertDocument(CPPUNIT_SOURCELINE(), "writer8", 
SignatureState::NOSIGNATURES,
+        //                       SignatureState::OK, ODFVER_013_TEXT);
+    }
+}
+
 void SigningTest2::registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx)
 {
     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("odfds"),
@@ -73,6 +374,13 @@ void SigningTest2::registerNamespaces(xmlXPathContextPtr& 
pXmlXpathCtx)
     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("dsig"),
                        BAD_CAST("http://www.w3.org/2000/09/xmldsig#";));
     xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xd"), 
BAD_CAST("http://uri.etsi.org/01903/v1.3.2#";));
+
+    // manifest.xml
+    xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("manifest"),
+                       
BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"));
+    xmlXPathRegisterNs(
+        pXmlXpathCtx, BAD_CAST("loext"),
+        
BAD_CAST("urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"));
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
commit 3f9979db2331e9cdf73743cc68a3ce7174ea54fa
Author:     Michael Stahl <[email protected]>
AuthorDate: Mon Jan 15 20:55:07 2024 +0100
Commit:     Caolán McNamara <[email protected]>
CommitDate: Sat Jan 20 17:39:21 2024 +0100

    tdf#105844 xmlsecurity: fix test failure on WNT
    
    Commit 4d6e9d5e155da1dde05233eb87691e2a454162f6 added 2 tests that
    always fail on WNT, unfortunately Jenkins doesn't actually run the
    tests.
    
    There are 3 certificates involved:
    "Xmlsecurity RSA Test Root CA"
    "Xmlsecurity Intermediate Root CA"
    "Xmlsecurity RSA Test example Alice"
    
    In the signature XML, there are 3 elements that contain or reference
    certificates:
    
    1. X509Data - xmlsecurity produces only the signing certificate here
    2. xd:SigningCertificate (XAdES) - again only the signing certificate
    3. xd:EncapsulatedX509Certificate (XAdES) - xmlsecurity produces the
       full certificate chain here
    
    All of these elements *could* contain the full certificate chain, but in
    LO-produced XML signatures only 3. does.
    
    The problem is that the function CheckUnitTestStore() that looks up
    a certificate in a unit-test-specific CA store via
    $LIBO_TEST_CRYPTOAPI_PKCS7 can only handle a root certificate, it does
    not recursively retrieve and check a certificate chain.
    
    The SecurityEnvironment_MSCryptImpl::verifyCertificate() already has a
    parameter "seqCerts" to pass in the full certificate chain, but due to
    the way the data from the XML is processed, it gets passed only the
    content of the X509Data element(s), which, for LO-produced signatures,
    do not contain the full certificate chain.
    
    Instead of improving the unit-test-specific function, let's try to get
    all the certificates out of the XML signature, and then pass them to
    verifyCertificate().
    
    Of course this requires some consistency checks so that the verification
    can't be fooled by different certificates in different XML elements.
    
    Change-Id: I8ca541887ceac2dfb6af5d96a5565cfa58d7f682
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162170
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <[email protected]>
    (cherry picked from commit 3e9a700091872480dd085f0928d1d30b7d74cfd7)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162139
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/xmlsecurity/source/helper/xmlsignaturehelper.cxx 
b/xmlsecurity/source/helper/xmlsignaturehelper.cxx
index 0b5825b125a1..3b13f79f33f1 100644
--- a/xmlsecurity/source/helper/xmlsignaturehelper.cxx
+++ b/xmlsecurity/source/helper/xmlsignaturehelper.cxx
@@ -22,6 +22,7 @@
 #include <documentsignaturehelper.hxx>
 #include <xsecctl.hxx>
 #include <biginteger.hxx>
+#include <certificate.hxx>
 
 #include <UriBindingHelper.hxx>
 
@@ -702,7 +703,72 @@ XMLSignatureHelper::CheckAndUpdateSignatureInformation(
     }
     if (CheckX509Data(xSecEnv, temp, certs, tempResult))
     {
-        datas.emplace_back(tempResult);
+        if (rInfo.maEncapsulatedX509Certificates.empty()) // optional, XAdES
+        {
+            datas.emplace_back(tempResult);
+        }
+        else
+        {
+            // check for consistency between X509Data and 
EncapsulatedX509Certificate
+            // (LO produces just the signing certificate in X509Data and
+            // the entire chain in EncapsulatedX509Certificate so in this case
+            // using EncapsulatedX509Certificate yields additional intermediate
+            // certificates that may help in verifying)
+            std::vector<SignatureInformation::X509CertInfo> 
encapsulatedCertInfos;
+            for (OUString const& it : rInfo.maEncapsulatedX509Certificates)
+            {
+                encapsulatedCertInfos.emplace_back();
+                encapsulatedCertInfos.back().X509Certificate = it;
+            }
+            std::vector<uno::Reference<security::XCertificate>> 
encapsulatedCerts;
+            SignatureInformation::X509Data encapsulatedResult;
+            if (CheckX509Data(xSecEnv, encapsulatedCertInfos, 
encapsulatedCerts, encapsulatedResult))
+            {
+                auto const 
pXCertificate(dynamic_cast<xmlsecurity::Certificate*>(certs.back().get()));
+                auto const 
pECertificate(dynamic_cast<xmlsecurity::Certificate*>(encapsulatedCerts.back().get()));
+                assert(pXCertificate && pECertificate); // was just created by 
CheckX509Data
+                if (pXCertificate->getSHA256Thumbprint() == 
pECertificate->getSHA256Thumbprint())
+                {
+                    // both are chains - take the longer one
+                    if (encapsulatedCerts.size() < certs.size())
+                    {
+                        datas.emplace_back(tempResult);
+                    }
+                    else
+                    {
+#if 0
+                        // extra info needed in testSigningMultipleTimes_ODT
+                        // ... but with it, it fails with BROKEN signature?
+                        // fails even on the first signature, because somehow
+                        // the xd:SigningCertificate element was signed
+                        // containing only one certificate, but in the final
+                        // file it contains all 3 certificates due to this 
here.
+                        for (size_t i = 0; i < encapsulatedResult.size(); ++i)
+                        {
+                            encapsulatedResult[i].X509IssuerName = 
encapsulatedCerts[i]->getIssuerName();
+                            encapsulatedResult[i].X509SerialNumber = 
xmlsecurity::bigIntegerToNumericString(encapsulatedCerts[i]->getSerialNumber());
+                            encapsulatedResult[i].X509Subject = 
encapsulatedCerts[i]->getSubjectName();
+                            auto const 
pCertificate(dynamic_cast<xmlsecurity::Certificate*>(encapsulatedCerts[i].get()));
+                            assert(pCertificate); // this was just created by 
CheckX509Data
+                            OUStringBuffer aBuffer;
+                            comphelper::Base64::encode(aBuffer, 
pCertificate->getSHA256Thumbprint());
+                            encapsulatedResult[i].CertDigest = 
aBuffer.makeStringAndClear();
+                        }
+                        datas.emplace_back(encapsulatedResult);
+#else
+                        // keep the X509Data stuff in datas but return the
+                        // longer EncapsulatedX509Certificate chain
+                        datas.emplace_back(tempResult);
+#endif
+                        certs = encapsulatedCerts; // overwrite this seems 
easier
+                    }
+                }
+                else
+                {
+                    SAL_WARN("xmlsecurity.comp", "X509Data and 
EncapsulatedX509Certificate contain different certificates");
+                }
+            }
+        }
     }
 
     // rInfo is a copy, update the original

Reply via email to