From bf55fc27b634152c758479b2fd1ba1353c69031d Mon Sep 17 00:00:00 2001
From: Raul Metsma <raul@metsma.ee>
Date: Wed, 22 Mar 2023 13:13:49 +0200
Subject: [PATCH] Validate CAdES signature in SiVa service

IB-6671

Signed-off-by: Raul Metsma <raul@metsma.ee>
---
 .github/workflows/build.yml |  4 +--
 src/ASiContainer.cpp        | 20 ++++++---------
 src/ASiContainer.h          |  4 +--
 src/SiVaContainer.cpp       | 50 +++++++++++++++++++++++++++++--------
 src/SiVaContainer.h         |  2 +-
 5 files changed, 52 insertions(+), 28 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6f116d504..1e45146e1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -82,7 +82,7 @@ jobs:
     steps:
     - name: Install Deps
       run: |
-        dnf install -y \
+        dnf install -y --setopt=install_weak_deps=False \
           git gcc-c++ cmake rpm-build xml-security-c-devel zlib-devel vim-common doxygen boost-test swig python3-devel java-1.8.0-openjdk-devel \
           https://www.codesynthesis.com/download/xsd/4.0/linux-gnu/x86_64/xsd-4.0.0-1.x86_64.rpm
     - name: Checkout
@@ -105,7 +105,7 @@ jobs:
     container: ${{ matrix.container }}
     strategy:
       matrix:
-        container: ['ubuntu:20.04', 'ubuntu:22.04', 'ubuntu:23.04']
+        container: ['ubuntu:20.04', 'ubuntu:22.04', 'ubuntu:23.04', 'ubuntu:23.10']
     env:
       DEBIAN_FRONTEND: noninteractive
       DEBFULLNAME: github-actions
diff --git a/src/ASiContainer.cpp b/src/ASiContainer.cpp
index 5f158ce8d..2a6440482 100644
--- a/src/ASiContainer.cpp
+++ b/src/ASiContainer.cpp
@@ -300,21 +300,15 @@ string ASiContainer::readMimetype(const ZipSerialize &z)
     DEBUG("ASiContainer::readMimetype()");
     stringstream is;
     z.extract("mimetype", is);
-
-    array<unsigned char,3> bom{};
-    is.read((char*)bom.data(), bom.size());
-    // Contains UTF-16 BOM
-    if((bom[0] == 0xFF && bom[1] == 0xEF) ||
-       (bom[0] == 0xEF && bom[1] == 0xFF))
-        THROW("Mimetype file must be UTF-8 format.");
-    // does not contain UTF-8 BOM reset pos
-    if(bom[0] != 0xEF || bom[1] != 0xBB || bom[2] != 0xBF)
-        is.seekg(0, ios::beg);
-
     string text;
     is >> text;
-    if(is.fail())
+    if(!is)
         THROW("Failed to read mimetype.");
-
+    // Contains UTF-16 BOM
+    if(text.find("\xFF\xEF") == 0 || text.find("\xEF\xFF") == 0)
+        THROW("Mimetype file must be UTF-8 format.");
+    // contains UTF-8 BOM, remove
+    if(text.find("\xEF\xBB\xBF") == 0)
+        text.erase(text.cbegin(), text.cbegin() + 3);
     return text;
 }
diff --git a/src/ASiContainer.h b/src/ASiContainer.h
index 56c6de0bf..73ddfb3c2 100644
--- a/src/ASiContainer.h
+++ b/src/ASiContainer.h
@@ -58,6 +58,8 @@ namespace digidoc
           void removeSignature(unsigned int id) override;
           std::vector<Signature*> signatures() const override;
 
+          static std::string readMimetype(const ZipSerialize &z);
+
       protected:
           ASiContainer(const std::string &mimetype);
 
@@ -72,8 +74,6 @@ namespace digidoc
           ZipSerialize::Properties zproperty(const std::string &file) const;
           void zproperty(const std::string &file, ZipSerialize::Properties &&prop);
 
-          static std::string readMimetype(const ZipSerialize &z);
-
       private:
           DISABLE_COPY(ASiContainer);
 
diff --git a/src/SiVaContainer.cpp b/src/SiVaContainer.cpp
index 49420c3ff..180612146 100644
--- a/src/SiVaContainer.cpp
+++ b/src/SiVaContainer.cpp
@@ -24,6 +24,7 @@
 
 #include "SiVaContainer.h"
 
+#include "ASiContainer.h"
 #include "Conf.h"
 #include "DataFile_p.h"
 #include "Signature.h"
@@ -31,6 +32,7 @@
 #include "crypto/Digest.h"
 #include "util/File.h"
 #include "util/log.h"
+#include "util/ZipSerialize.h"
 #include "xml/xml.hxx"
 #include "xml/SecureDOMParser.h"
 
@@ -142,13 +144,15 @@ void SignatureSiVa::validate(const string &policy) const
 }
 
 
-SiVaContainer::SiVaContainer(const string &path, const string &ext, bool useHashCode)
+SiVaContainer::SiVaContainer(const string &path, bool useHashCode)
     : d(make_unique<Private>())
 {
+    string ext = File::fileExtension(path);
     DEBUG("SiVaContainer::SiVaContainer(%s, %s, %d)", path.c_str(), ext.c_str(), useHashCode);
     unique_ptr<istream> ifs = make_unique<ifstream>(File::encodeName(d->path = path), ifstream::binary);
     auto fileName = File::fileName(path);
     istream *is = ifs.get();
+    static const array asic {"asice", "sce", "asics", "scs"};
     if(ext == "ddoc")
     {
         d->mediaType = "application/x-ddoc";
@@ -156,13 +160,41 @@ SiVaContainer::SiVaContainer(const string &path, const string &ext, bool useHash
         ifs = parseDDoc(useHashCode);
         is = ifs.get();
     }
-    else
+    else if(ext == "pdf")
     {
         d->mediaType = "application/pdf";
         d->dataFiles.push_back(new DataFilePrivate(std::move(ifs), fileName, "application/pdf"));
     }
+    else if(find(asic.cbegin(), asic.cend(), ext) != asic.cend())
+    {
+        ZipSerialize z(path, false);
+        vector<string> list = z.list();
+        if(list.empty() || list.front() != "mimetype")
+            THROW("Missing mimetype");
+        if(d->mediaType = ASiContainer::readMimetype(z);
+            d->mediaType != ASiContainer::MIMETYPE_ASIC_E && d->mediaType != ASiContainer::MIMETYPE_ASIC_S)
+            THROW("Unknown file");
+        if(none_of(list.cbegin(), list.cend(), [](const string &file) { return file.find("p7s") != string::npos; }))
+            THROW("Unknown file");
+
+        static const string metaInf = "META-INF/";
+        for(const string &file: list)
+        {
+            if(file == "mimetype" || file.substr(0, metaInf.size()) == metaInf)
+                continue;
+            const auto directory = File::directory(file);
+            if(directory.empty() || directory == "/" || directory == "./")
+            {
+                auto data = make_unique<stringstream>();
+                z.extract(file, *data);
+                d->dataFiles.push_back(new DataFilePrivate(std::move(data), file, "application/octet-stream"));
+            }
+        }
+    }
+    else
+        THROW("Unknown file");
 
-    array<XMLByte, 48*100> buf{};
+    array<XMLByte, 4800> buf{};
     string b64;
     is->clear();
     is->seekg(0);
@@ -314,15 +346,13 @@ vector<DataFile *> SiVaContainer::dataFiles() const
 
 unique_ptr<Container> SiVaContainer::openInternal(const string &path)
 {
-    static const array supported {"pdf", "ddoc"};
-    string ext = File::fileExtension(path);
-    if(find(supported.cbegin(), supported.cend(), ext) == supported.cend())
-        return {};
     try {
-        return unique_ptr<Container>(new SiVaContainer(path, ext, true));
+        return unique_ptr<Container>(new SiVaContainer(path, true));
     } catch(const Exception &e) {
         if(e.msg().find("Bad digest for DataFile") == 0)
-            return unique_ptr<Container>(new SiVaContainer(path, ext, false));
+            return unique_ptr<Container>(new SiVaContainer(path, false));
+        if(e.msg() == "Unknown file")
+           return {};
         throw;
     }
 }
@@ -337,7 +367,7 @@ unique_ptr<istream> SiVaContainer::parseDDoc(bool useHashCode)
         DOMNodeList *nodeList = dom->getElementsByTagName(cpXMLCh(u"DataFile"));
         for(XMLSize_t i = 0; i < nodeList->getLength(); ++i)
         {
-            DOMElement *item = static_cast<DOMElement*>(nodeList->item(i));
+            auto *item = static_cast<DOMElement*>(nodeList->item(i));
             if(!item)
                 continue;
 
diff --git a/src/SiVaContainer.h b/src/SiVaContainer.h
index 060796615..ce93edff7 100644
--- a/src/SiVaContainer.h
+++ b/src/SiVaContainer.h
@@ -102,7 +102,7 @@ class SiVaContainer final: public Container
     static std::unique_ptr<Container> openInternal(const std::string &path);
 
 private:
-    SiVaContainer(const std::string &path, const std::string &ext, bool useHashCode);
+	SiVaContainer(const std::string &path, bool useHashCode);
     DISABLE_COPY(SiVaContainer);
 
 	std::unique_ptr<std::istream> parseDDoc(bool useHashCode);