diff --git a/src/ASiC_E.cpp b/src/ASiC_E.cpp index 4f3f98759..5b2b584b3 100644 --- a/src/ASiC_E.cpp +++ b/src/ASiC_E.cpp @@ -35,8 +35,6 @@ using namespace digidoc; using namespace digidoc::util; using namespace std; -constexpr string_view MANIFEST_NS {"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"}; - class ASiC_E::Private { public: @@ -88,28 +86,10 @@ vector ASiC_E::metaFiles() const * document does not exist. * @throws Exception is thrown if ASiC_E class is not correctly initialized. */ -void ASiC_E::save(const string &path) +void ASiC_E::save(const ZipSerialize &s) { - if(dataFiles().empty()) - THROW("Can not save, container is empty."); - if(mediaType() != MIMETYPE_ASIC_E) - THROW("'%s' format is not supported", mediaType().c_str()); - - if(!path.empty()) - zpath(path); - ZipSerialize s(zpath(), true); - - stringstream mimetype; - mimetype << mediaType(); - s.addFile("mimetype", mimetype, zproperty("mimetype"), false); - - stringstream manifest; - if(!createManifest().save(manifest)) + if(!createManifest().save(s.addFile("META-INF/manifest.xml", zproperty("META-INF/manifest.xml")), true)) THROW("Failed to create manifest XML"); - s.addFile("META-INF/manifest.xml", manifest, zproperty("META-INF/manifest.xml")); - - for(const DataFile *file: dataFiles()) - s.addFile(file->fileName(), *(static_cast(file)->m_is), zproperty(file->fileName())); std::set saved; for(Signature *iter: signatures()) @@ -122,10 +102,8 @@ void ASiC_E::save(const string &path) }); if(name == d->signatures.cend()) THROW("Unkown signature object"); - stringstream ofs; - if(!signatures->save(ofs)) + if(!signatures->save(s.addFile(name->first, zproperty(name->first)))) THROW("Failed to create signature XML file."); - s.addFile(name->first, ofs, zproperty(name->first)); } } @@ -159,32 +137,16 @@ void ASiC_E::addAdESSignature(istream &data) } } -unique_ptr ASiC_E::openInternal(const string &path) +void ASiC_E::canSave() { - DEBUG("ASiC_E::openInternal(%s)", path.c_str()); - return unique_ptr(new ASiC_E(path)); + if(mediaType() != MIMETYPE_ASIC_E) + THROW("'%s' format is not supported", mediaType().c_str()); } -/** - * Creates BDoc container manifest file and returns its path. - * - * @return returns created manifest file path. - * @throws Exception exception is thrown if manifest file creation failed. - */ -XMLDocument ASiC_E::createManifest() const +unique_ptr ASiC_E::openInternal(const string &path) { - DEBUG("ASiC_E::createManifest()"); - auto doc = XMLDocument::create("manifest", MANIFEST_NS, "manifest"); - doc.setProperty("version", "1.2", MANIFEST_NS); - auto add = [&doc](string_view path, string_view mime) { - auto file = doc+"file-entry"; - file.setProperty("full-path", path, MANIFEST_NS); - file.setProperty("media-type", mime, MANIFEST_NS); - }; - add("/", mediaType()); - for(const DataFile *file: dataFiles()) - add(file->fileName(), file->mediaType()); - return doc; + DEBUG("ASiC_E::openInternal(%s)", path.c_str()); + return unique_ptr(new ASiC_E(path)); } void ASiC_E::loadSignatures(istream &data, const string &file) diff --git a/src/ASiC_E.h b/src/ASiC_E.h index c7887d9fb..88b0d93a1 100644 --- a/src/ASiC_E.h +++ b/src/ASiC_E.h @@ -23,8 +23,6 @@ namespace digidoc { - struct XMLDocument; - /** * Implements the BDOC specification of the signed digital document container. * Container can contain several files and all these files can be signed using @@ -42,7 +40,6 @@ namespace digidoc static constexpr std::string_view ASIC_TSA_PROFILE = "time-stamp-archive"; ~ASiC_E() final; - void save(const std::string &path = {}) final; std::vector metaFiles() const; void addAdESSignature(std::istream &data) final; @@ -56,9 +53,10 @@ namespace digidoc ASiC_E(); ASiC_E(const std::string &path); DISABLE_COPY(ASiC_E); - XMLDocument createManifest() const; + void canSave() final; void loadSignatures(std::istream &data, const std::string &file); void parseManifestAndLoadFiles(const ZipSerialize &z); + void save(const ZipSerialize &s) final; class Private; std::unique_ptr d; diff --git a/src/ASiC_S.cpp b/src/ASiC_S.cpp index 6cd83fc4a..ac64f9be3 100644 --- a/src/ASiC_S.cpp +++ b/src/ASiC_S.cpp @@ -34,47 +34,51 @@ using namespace std; /** * Initialize ASiCS container. */ -ASiC_S::ASiC_S(): ASiContainer(MIMETYPE_ASIC_S) +ASiC_S::ASiC_S() + : ASiContainer(MIMETYPE_ASIC_S) {} /** * Opens ASiC-S container from a file */ -ASiC_S::ASiC_S(const string &path): ASiContainer(MIMETYPE_ASIC_S) +ASiC_S::ASiC_S(const string &path) + : ASiContainer(MIMETYPE_ASIC_S) { auto z = load(path, false, {mediaType()}); - static const string_view metaInf = "META-INF/"; + auto starts_with = [](string_view str, string_view needle) constexpr { + return str.size() >= needle.size() && str.compare(0, needle.size(), needle) == 0; + }; for(const string &file: z.list()) { - if(file == "mimetype" || - (metaInf.size() < file.size() && file.compare(0, metaInf.size(), metaInf) == 0)) - { - if(file == "META-INF/timestamp.tst") - { - if(!signatures().empty()) - THROW("Can not add signature to ASiC-S container which already contains a signature."); - addSignature(make_unique(z.extract(file).str(), this)); - } - if(file == "META-INF/signatures.xml") - { - if(!signatures().empty()) - THROW("Can not add signature to ASiC-S container which already contains a signature."); - auto data = z.extract(file); - auto signatures = make_shared(data, mediaType()); - for(auto s = signatures->signature(); s; s++) - addSignature(make_unique(signatures, s, this)); - } + if(file == "mimetype") continue; + if(file == "META-INF/timestamp.tst") + { + if(!signatures().empty()) + THROW("Can not add signature to ASiC-S container which already contains a signature."); + addSignature(make_unique(z.extract(file).str(), this)); } - - const auto directory = File::directory(file); - if(directory.empty() || directory == "/" || directory == "./") + else if(file == "META-INF/signatures.xml") { - if(!dataFiles().empty()) - THROW("Can not add document to ASiC-S container which already contains a document."); - addDataFile(dataStream(file, z), file, "application/octet-stream"); + if(!signatures().empty()) + THROW("Can not add signature to ASiC-S container which already contains a signature."); + auto data = z.extract(file); + auto signatures = make_shared(data, mediaType()); + for(auto s = signatures->signature(); s; s++) + addSignature(make_unique(signatures, s, this)); } + else if(file == "META-INF/ASiCArchiveManifest.xml") + THROW("ASiCArchiveManifest are not supported."); + else if(starts_with(file, "META-INF/")) + continue; + else if(const auto directory = File::directory(file); + !directory.empty() && directory != "/" && directory != "./") + THROW("Subfolders are not supported %s", directory.c_str()); + else if(!dataFiles().empty()) + THROW("Can not add document to ASiC-S container which already contains a document."); + else + addDataFile(dataStream(file, z), file, "application/octet-stream"); } if(dataFiles().empty()) @@ -83,11 +87,6 @@ ASiC_S::ASiC_S(const string &path): ASiContainer(MIMETYPE_ASIC_S) THROW("ASiC-S container does not contain any signatures."); } -void ASiC_S::save(const string & /*path*/) -{ - THROW("Not implemented."); -} - unique_ptr ASiC_S::createInternal(const string & /*path*/) { return {}; @@ -98,6 +97,12 @@ void ASiC_S::addAdESSignature(istream & /*signature*/) THROW("Not implemented."); } +void ASiC_S::canSave() +{ + if(auto list = signatures(); !list.empty() && list.front()->profile() != ASIC_TST_PROFILE) + THROW("ASiC-S container supports only saving TimeStampToken signatures."); +} + unique_ptr ASiC_S::openInternal(const string &path, ContainerOpenCB * /*cb*/) { if (!isContainerSimpleFormat(path)) @@ -111,6 +116,14 @@ Signature* ASiC_S::prepareSignature(Signer * /*signer*/) THROW("Not implemented."); } +void ASiC_S::save(const ZipSerialize &s) +{ + if(zproperty("META-INF/manifest.xml").size && !createManifest().save(s.addFile("META-INF/manifest.xml", zproperty("META-INF/manifest.xml")), true)) + THROW("Failed to create manifest XML"); + if(auto list = signatures(); !list.empty()) + s.addFile("META-INF/timestamp.tst", zproperty("META-INF/timestamp.tst"))(static_cast(list.front())->save()); +} + Signature *ASiC_S::sign(Signer * /*signer*/) { THROW("Not implemented."); diff --git a/src/ASiC_S.h b/src/ASiC_S.h index e1a91be46..f6eba5a20 100644 --- a/src/ASiC_S.h +++ b/src/ASiC_S.h @@ -30,9 +30,8 @@ namespace digidoc */ class ASiC_S : public ASiContainer { - public: - void save(const std::string &path = {}) override; + static constexpr std::string_view ASIC_TST_PROFILE = "TimeStampToken"; void addAdESSignature(std::istream &sigdata) override; Signature* prepareSignature(Signer *signer) override; @@ -46,6 +45,9 @@ namespace digidoc ASiC_S(const std::string &path); DISABLE_COPY(ASiC_S); + void canSave() final; + void save(const ZipSerialize &s) final; + static bool isContainerSimpleFormat(const std::string &path); }; } diff --git a/src/ASiContainer.cpp b/src/ASiContainer.cpp index 49cc96fd4..a4c1b0ce9 100644 --- a/src/ASiContainer.cpp +++ b/src/ASiContainer.cpp @@ -21,10 +21,12 @@ #include "DataFile_p.h" #include "Signature.h" +#include "XMLDocument.h" #include "util/File.h" #include "util/log.h" #include +#include #include #include #include @@ -51,7 +53,23 @@ class ASiContainer::Private ASiContainer::ASiContainer(string_view mimetype) : d(make_unique()) { - d->mimetype = mimetype; + d->mimetype = string(mimetype); +} + +XMLDocument ASiContainer::createManifest() const +{ + DEBUG("ASiContainer::createManifest()"); + auto doc = XMLDocument::create("manifest", MANIFEST_NS, "manifest"); + doc.setProperty("version", "1.2", MANIFEST_NS); + auto add = [&doc](string_view path, string_view mime) { + auto file = doc+"file-entry"; + file.setProperty("full-path", path, MANIFEST_NS); + file.setProperty("media-type", mime, MANIFEST_NS); + }; + add("/", mediaType()); + for(const DataFile *file: dataFiles()) + add(file->fileName(), file->mediaType()); + return doc; } /** @@ -166,7 +184,7 @@ void ASiContainer::addDataFile(const string &path, const string &mediaType) is = std::move(data); } d->properties[fileName] = { appInfo(), File::modifiedTime(path), size }; - addDataFilePrivate(std::move(is), std::move(fileName), mediaType); + d->documents.push_back(new DataFilePrivate(std::move(is), std::move(fileName), mediaType)); } void ASiContainer::addDataFile(unique_ptr is, const string &fileName, const string &mediaType) @@ -174,7 +192,7 @@ void ASiContainer::addDataFile(unique_ptr is, const string &fileName, c addDataFileChecks(fileName, mediaType); if(fileName.find_last_of("/\\") != string::npos) THROW("Document file '%s' cannot contain directory path.", fileName.c_str()); - addDataFilePrivate(std::move(is), fileName, mediaType); + d->documents.push_back(new DataFilePrivate(std::move(is), fileName, mediaType)); } void ASiContainer::addDataFileChecks(const string &fileName, const string &mediaType) @@ -241,6 +259,34 @@ void ASiContainer::deleteSignature(Signature* s) delete s; } +void ASiContainer::save(const string &path) +{ + if(dataFiles().empty()) + THROW("Can not save, container is empty."); + canSave(); + if(!path.empty()) + zpath(path); + ZipSerialize s(zpath(), true); + s.addFile("mimetype", zproperty("mimetype"), false)(mediaType()); + + array buf{}; + for(const DataFile *file: dataFiles()) + { + auto f = s.addFile(file->fileName(), zproperty(file->fileName())); + const auto &is = static_cast(file)->m_is; + is->clear(); + is->seekg(0); + while(*is) + { + is->read(buf.data(), buf.size()); + if(auto size = is->gcount(); size > 0) + f(buf.data(), size_t(size)); + } + } + + save(s); +} + void ASiContainer::zpath(const string &file) { d->path = file; @@ -251,11 +297,11 @@ string ASiContainer::zpath() const return d->path; } -const ZipSerialize::Properties& ASiContainer::zproperty(const string &file) const +const ZipSerialize::Properties& ASiContainer::zproperty(string_view file) const { if(auto i = d->properties.find(file); i != d->properties.cend()) return i->second; - return d->properties[file] = { appInfo(), time(nullptr), 0 }; + return d->properties[string(file)] = { appInfo(), time(nullptr), 0 }; } /** diff --git a/src/ASiContainer.h b/src/ASiContainer.h index b71a076f5..cdc0b952a 100644 --- a/src/ASiContainer.h +++ b/src/ASiContainer.h @@ -26,6 +26,8 @@ namespace digidoc { + struct XMLDocument; + /** * Base class for the ASiC (Associated Signature Container) documents. * Implements the operations and data structures common for more specific ASiC @@ -42,6 +44,7 @@ namespace digidoc static constexpr std::string_view MIMETYPE_ASIC_S = "application/vnd.etsi.asic-s+zip"; //https://signa.mitsoft.lt/static/signa-web/webResources/docs/ADOC_specification_approved20090907_EN.pdf static constexpr std::string_view MIMETYPE_ADOC = "application/vnd.lt.archyvai.adoc-2008"; + static constexpr std::string_view MANIFEST_NS = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"; ~ASiContainer() override; std::string mediaType() const override; @@ -51,6 +54,7 @@ namespace digidoc std::vector dataFiles() const override; void removeDataFile(unsigned int id) override; void removeSignature(unsigned int id) override; + void save(const std::string &path) override; std::vector signatures() const override; static std::string readMimetype(const ZipSerialize &z); @@ -58,21 +62,23 @@ namespace digidoc protected: ASiContainer(std::string_view mimetype); + virtual void addDataFileChecks(const std::string &path, const std::string &mediaType); void addDataFilePrivate(std::unique_ptr is, std::string fileName, std::string mediaType); Signature* addSignature(std::unique_ptr &&signature); + virtual void canSave() = 0; + XMLDocument createManifest() const; std::unique_ptr dataStream(std::string_view path, const ZipSerialize &z) const; ZipSerialize load(const std::string &path, bool requireMimetype, const std::set &supported); + virtual void save(const ZipSerialize &s) = 0; void deleteSignature(Signature* s); void zpath(const std::string &file); std::string zpath() const; - const ZipSerialize::Properties &zproperty(const std::string &file) const; + const ZipSerialize::Properties& zproperty(std::string_view file) const; private: DISABLE_COPY(ASiContainer); - void addDataFileChecks(const std::string &path, const std::string &mediaType); - class Private; std::unique_ptr d; }; diff --git a/src/SiVaContainer.cpp b/src/SiVaContainer.cpp index 81557dbd1..234c17ae9 100644 --- a/src/SiVaContainer.cpp +++ b/src/SiVaContainer.cpp @@ -365,7 +365,8 @@ unique_ptr SiVaContainer::parseDDoc(bool useHashCode) dataFile = std::string_view{}; } auto result = make_unique(); - doc.save(*result); + if(!doc.save([&result](const char *data, size_t size) { result->write(data, streamsize(size)); })) + THROW("Failed to save DDoc"); return result; } catch(const Exception &) diff --git a/src/SignatureTST.cpp b/src/SignatureTST.cpp index 6665da184..3061efdbe 100644 --- a/src/SignatureTST.cpp +++ b/src/SignatureTST.cpp @@ -83,10 +83,9 @@ void SignatureTST::validate() const } try { - const string digestMethod = timestampToken->digestMethod(); - timestampToken->verify(asicSDoc->dataFiles().front()->calcDigest(digestMethod)); - - if(!Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) && + timestampToken->verify(dataToSign()); + if(auto digestMethod = signatureMethod(); + !Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) && Digest::isWeakDigest(digestMethod)) { Exception e(EXCEPTION_PARAMS("TimeStamp '%s' digest weak", digestMethod.c_str())); @@ -105,7 +104,12 @@ void SignatureTST::validate() const std::vector SignatureTST::dataToSign() const { - THROW("Not implemented."); + return asicSDoc->dataFiles().front()->calcDigest(signatureMethod()); +} + +vector SignatureTST::messageImprint() const +{ + return timestampToken->messageImprint(); } void SignatureTST::setSignatureValue(const std::vector & /*signatureValue*/) @@ -116,5 +120,10 @@ void SignatureTST::setSignatureValue(const std::vector & /*signat // Xades properties string SignatureTST::profile() const { - return "TimeStampToken"; + return string(ASiC_S::ASIC_TST_PROFILE); +} + +std::vector SignatureTST::save() const +{ + return *timestampToken; } diff --git a/src/SignatureTST.h b/src/SignatureTST.h index 52f159eea..b70940207 100644 --- a/src/SignatureTST.h +++ b/src/SignatureTST.h @@ -34,6 +34,7 @@ class SignatureTST final: public Signature SignatureTST(const std::string &data, ASiC_S *asicSDoc); ~SignatureTST(); + std::vector messageImprint() const override; std::string trustedSigningTime() const final; X509Cert TimeStampCertificate() const final; @@ -51,9 +52,11 @@ class SignatureTST final: public Signature // Xades properties std::string profile() const final; + std::vector save() const; + private: DISABLE_COPY(SignatureTST); - ASiC_S *asicSDoc = nullptr; + ASiC_S *asicSDoc {}; std::unique_ptr timestampToken; }; diff --git a/src/XMLDocument.h b/src/XMLDocument.h index 63820d912..5ea27733f 100644 --- a/src/XMLDocument.h +++ b/src/XMLDocument.h @@ -34,7 +34,6 @@ #include #include -#include namespace digidoc { @@ -398,18 +397,21 @@ struct XMLDocument: public unique_xml_t, public XMLNode THROW("Failed to canonicalizate input"); } - bool save(std::string_view path) const noexcept + bool save(std::string_view path, bool format = false) const noexcept { - return xmlSaveFormatFileEnc(path.data(), get(), "UTF-8", 0) > 0; + return xmlSaveFormatFileEnc(path.data(), get(), "UTF-8", format) > 0; } - bool save(std::ostream &os) const noexcept + template + std::enable_if_t, bool> + save(F &&f, bool format = false) const noexcept { auto *buf = xmlOutputBufferCreateIO([](void *context, const char *buffer, int len) { - auto *os = static_cast(context); - return os->write(buffer, len) ? len : -1; - }, nullptr, &os, nullptr); - return xmlSaveFormatFileTo(buf, get(), "UTF-8", 0) > 0; + auto *f = static_cast(context); + (*f)(buffer, size_t(len)); + return len; + }, nullptr, &f, nullptr); + return xmlSaveFormatFileTo(buf, get(), "UTF-8", format) > 0; } void validateSchema(const std::string &schemaPath) const diff --git a/src/XmlConf.cpp b/src/XmlConf.cpp index 1a1ace3d6..38608d7b1 100644 --- a/src/XmlConf.cpp +++ b/src/XmlConf.cpp @@ -248,7 +248,7 @@ void XmlConf::Private::setUserConf(XmlConfParam ¶m, A value) } File::createDirectory(File::directory(USER_CONF_LOC)); - if(!doc.save(USER_CONF_LOC)) + if(!doc.save(USER_CONF_LOC, true)) ERR("Failed to save configuration: %s (%s)", USER_CONF_LOC.c_str(), SCHEMA_LOC.c_str()); } diff --git a/src/util/ZipSerialize.cpp b/src/util/ZipSerialize.cpp index 74e5ac0f6..644633cd0 100644 --- a/src/util/ZipSerialize.cpp +++ b/src/util/ZipSerialize.cpp @@ -19,9 +19,10 @@ #include "ZipSerialize.h" +#include "Container.h" #include "DateTime.h" #include "log.h" -#include "util/File.h" +#include "File.h" #include #include @@ -45,7 +46,7 @@ using namespace std; * @param path */ ZipSerialize::ZipSerialize(const string &path, bool create) - : d{nullptr, create ? [](void *handle) { return zipClose(handle, nullptr); } : &unzClose} + : d{nullptr, create ? [](void *handle) { return zipClose(handle, appInfo().c_str()); } : &unzClose} { zlib_filefunc_def def {}; #ifdef _WIN32 @@ -166,9 +167,10 @@ template stringstream ZipSerialize::extract(string_view file) cons * @param containerPath file path inside ZIP file. * @param prop Properties added for file in ZIP file. * @param compress File should be compressed in ZIP file. - * @throws IOException throws exception if there were errors during locating files in zip. + * @return Write struct for data input + * @throws Exception throws exception if there were errors during locating files in zip. */ -void ZipSerialize::addFile(const string& containerPath, istream &is, const Properties &prop, bool compress) +ZipSerialize::Write ZipSerialize::addFile(string_view containerPath, const Properties &prop, bool compress) const { if(!d) THROW("Zip file is not open"); @@ -190,26 +192,7 @@ void ZipSerialize::addFile(const string& containerPath, istream &is, const Prope if(zipResult != ZIP_OK) THROW("Failed to create new file inside ZIP container. ZLib error: %d", zipResult); - is.clear(); - is.seekg(0); - array buf{}; - while(is) - { - is.read(buf.data(), buf.size()); - if(is.gcount() <= 0) - break; - - zipResult = zipWriteInFileInZip(d.get(), buf.data(), unsigned(is.gcount())); - if(zipResult != ZIP_OK) - { - zipCloseFileInZip(d.get()); - THROW("Failed to write bytes to current file inside ZIP container. ZLib error: %d", zipResult); - } - } - - zipResult = zipCloseFileInZip(d.get()); - if(zipResult != ZIP_OK) - THROW("Failed to close current file inside ZIP container. ZLib error: %d", zipResult); + return {{d.get(), zipCloseFileInZip}}; } ZipSerialize::Properties ZipSerialize::properties(const string &file) const @@ -239,3 +222,9 @@ ZipSerialize::Properties ZipSerialize::properties(const string &file) const return prop; } + +void ZipSerialize::Write::operator()(const void *data, size_t size) const +{ + if(auto result = zipWriteInFileInZip(d.get(), data, unsigned(size)); result != ZIP_OK) + THROW("Failed to write bytes to current file inside ZIP container. ZLib error: %d", result); +} diff --git a/src/util/ZipSerialize.h b/src/util/ZipSerialize.h index 6e8c82075..4f140e5fe 100644 --- a/src/util/ZipSerialize.h +++ b/src/util/ZipSerialize.h @@ -33,6 +33,16 @@ namespace digidoc class ZipSerialize { public: + struct Write { + void operator ()(const void *data, size_t size) const; + template + constexpr void operator ()(const T &data) const + { + operator ()(data.data(), data.size()); + } + std::unique_ptr d; + }; + struct Properties { std::string comment; time_t time; @@ -44,7 +54,7 @@ namespace digidoc std::vector list() const; template T extract(std::string_view file) const; - void addFile(const std::string &containerPath, std::istream &is, const Properties &prop, bool compress = true); + Write addFile(std::string_view containerPath, const Properties &prop, bool compress = true) const; Properties properties(const std::string &file) const; private: