From fb1af302e0ab8ade6bb8fbcb7f7aa8c2a2adb695 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 4 Jul 2023 10:59:40 +0300 Subject: [PATCH] Handle ASiC-S XAdES signatures IB-7593 Signed-off-by: Raul Metsma --- src/ASiC_S.cpp | 149 +++++++++++--------------------------- src/ASiC_S.h | 9 +-- src/ASiContainer.cpp | 46 ++++++------ src/ASiContainer.h | 4 +- src/SignatureTST.cpp | 12 +-- src/SignatureTST.h | 2 +- src/SignatureXAdES_B.cpp | 4 +- src/util/ZipSerialize.cpp | 39 +++++----- 8 files changed, 96 insertions(+), 169 deletions(-) diff --git a/src/ASiC_S.cpp b/src/ASiC_S.cpp index 0c4767816..e49c4678e 100644 --- a/src/ASiC_S.cpp +++ b/src/ASiC_S.cpp @@ -20,13 +20,12 @@ #include "ASiC_S.h" #include "SignatureTST.h" -#include "crypto/Digest.h" +#include "SignatureXAdES_LTA.h" #include "util/File.h" #include "util/log.h" #include "util/ZipSerialize.h" #include -#include #include using namespace digidoc; @@ -37,37 +36,58 @@ using namespace std; * Initialize ASiCS container. */ 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) { - auto z = load(path, false, {MIMETYPE_ASIC_S}); - loadContainer(*z); -} + auto z = load(path, false, {mediaType()}); + static const string_view metaInf = "META-INF/"; -void ASiC_S::save(const string & /*path*/) -{ - THROW("Not implemented."); -} + 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."); + stringstream data; + z->extract(file, data); + addSignature(make_unique(data, this)); + } + if(file == "META-INF/signatures.xml") + { + if(!signatures().empty()) + THROW("Can not add signature to ASiC-S container which already contains a signature."); + stringstream data; + z->extract(file, data); + addSignature(make_unique(data, this, true)); + } + continue; + } -void ASiC_S::addDataFile(const string &path, const string &mediaType) -{ - if(!dataFiles().empty()) - THROW("Can not add document to ASiC-S container which already contains a document."); - - ASiContainer::addDataFile(path, mediaType); + const auto directory = File::directory(file); + if(directory.empty() || directory == "/" || directory == "./") + { + 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(dataFiles().empty()) + THROW("ASiC-S container does not contain any data objects."); + if(signatures().empty()) + THROW("ASiC-S container does not contain any signatures."); } -void ASiC_S::addDataFile(unique_ptr is, const string &fileName, const string &mediaType) +void ASiC_S::save(const string & /*path*/) { - if(!dataFiles().empty()) - THROW("Can not add document to ASiC-S container which already contains a document."); - - ASiContainer::addDataFile(move(is), fileName, mediaType); + THROW("Not implemented."); } unique_ptr ASiC_S::createInternal(const string & /*path*/) @@ -83,57 +103,11 @@ void ASiC_S::addAdESSignature(istream & /*signature*/) unique_ptr ASiC_S::openInternal(const string &path) { if (!isContainerSimpleFormat(path)) - return nullptr; + return {}; DEBUG("ASiC_S::openInternal(%s)", path.c_str()); return unique_ptr(new ASiC_S(path)); } -void ASiC_S::extractTimestamp(const ZipSerialize &z) -{ - addSignature(make_unique(dataStream("META-INF/timestamp.tst", z), this)); -} - -/** - * Load container (datafile and timestamp). - * - * @param z Zip stream. - * @param list List of files contained in the container. - * @throws IOException exception is thrown if the manifest.xml file parsing failed. - * @throws ContainerException - */ -void ASiC_S::loadContainer(const ZipSerialize &z) -{ - DEBUG("ASiC_S::loadFileAndTimestamp()"); - const string metaInf = "META-INF/"; - const vector &list = z.list(); - int files = 0; - - 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 == "./") - { - if(files > 0) - { - THROW("ASiC-S container contains more than one data objects."); - } - ASiContainer::addDataFile(dataStream(file, z), file, "application/octet-stream"); - files++; - } - } - - if(files == 0) - { - THROW("ASiC-S container does not contain any data objects."); - } - - extractTimestamp(z); -} - Signature* ASiC_S::prepareSignature(Signer * /*signer*/) { THROW("Not implemented."); @@ -144,31 +118,6 @@ Signature *ASiC_S::sign(Signer * /*signer*/) THROW("Not implemented."); } - -bool ASiC_S::isTimestampedASiC_S(const vector &list) -{ - DEBUG("isTimestampedASiC_S()"); - bool isASiCS = false; - - auto dataFiles = 0; - auto hasTimestamp = false; - - // container has only one file in root folder and has a timestamp - for(const string &file: list) - { - const auto directory = File::directory(file); - if(directory.empty() || directory == "/" || directory == "./") - dataFiles++; - if(file == "META-INF/timestamp.tst") - hasTimestamp = true; - } - - isASiCS = hasTimestamp && (dataFiles == 1); - - DEBUG("ASiCS Container: %s", isASiCS ? "yes" : "no"); - return isASiCS; -} - /** * Detect ASiC format based on file extentions, mimetype or zip contents.
* Container format is simple (ASiC-S) or extended (ASiC-E). @@ -185,26 +134,16 @@ bool ASiC_S::isContainerSimpleFormat(const string &path) return false; if(extension == ASICS_EXTENSION || extension == ASICS_EXTENSION_ABBR) return true; - DEBUG("Check if ASiC/zip containter"); try { ZipSerialize z(path, false); vector list = z.list(); - if(find(list.begin(), list.end(), "mimetype") != list.end()) - { - stringstream iss; - z.extract("mimetype", iss); - if(readMimetype(iss) == MIMETYPE_ASIC_S) - return true; - } - if(isTimestampedASiC_S(list)) - return true; + return !list.empty() && list.front() == "mimetype" && readMimetype(z) == MIMETYPE_ASIC_S; } catch(const Exception &) { // Ignore the exception: not ASiC/zip document } - return false; } diff --git a/src/ASiC_S.h b/src/ASiC_S.h index 65e94621f..95c9483ef 100644 --- a/src/ASiC_S.h +++ b/src/ASiC_S.h @@ -36,9 +36,6 @@ namespace digidoc public: void save(const std::string &path = {}) override; - void addDataFile(const std::string &path, const std::string &mediaType) override; - void addDataFile(std::unique_ptr is, const std::string &fileName, const std::string &mediaType) override; - void addAdESSignature(std::istream &sigdata) override; Signature* prepareSignature(Signer *signer) override; Signature* sign(Signer* signer) override; @@ -50,11 +47,7 @@ namespace digidoc ASiC_S(); ASiC_S(const std::string &path); DISABLE_COPY(ASiC_S); - - void extractTimestamp(const ZipSerialize &z); - void loadContainer(const ZipSerialize &z); - + static bool isContainerSimpleFormat(const std::string &path); - static bool isTimestampedASiC_S(const std::vector &list); }; } diff --git a/src/ASiContainer.cpp b/src/ASiContainer.cpp index f93feefa5..d77705984 100644 --- a/src/ASiContainer.cpp +++ b/src/ASiContainer.cpp @@ -79,29 +79,27 @@ ASiContainer::ASiContainer(const string &mimetype) unique_ptr ASiContainer::load(const string &path, bool mimetypeRequired, const set &supported) { DEBUG("ASiContainer::ASiContainer(path = '%s')", path.c_str()); - unique_ptr z = make_unique(d->path = path, false); + auto z = make_unique(d->path = path, false); vector list = z->list(); if(list.empty()) THROW("Failed to parse container"); - for(const string &file: list) - d->properties[file] = z->properties(file); - - if(mimetypeRequired && list[0] != "mimetype") + if(mimetypeRequired && list.front() != "mimetype") THROW("required mimetype not found"); // ETSI TS 102 918: mimetype has to be the first in the archive if(list.front() == "mimetype") { - stringstream data; - z->extract(list.front(), data); - d->mimetype = readMimetype(data); + d->mimetype = readMimetype(*z); DEBUG("mimetype = '%s'", d->mimetype.c_str()); if(supported.find(d->mimetype) == supported.cend()) THROW("Incorrect mimetype '%s'", d->mimetype.c_str()); } + for(const string &file: list) + d->properties[file] = z->properties(file); + return z; } @@ -154,7 +152,7 @@ unique_ptr ASiContainer::dataStream(const string &path, const ZipSeria { unique_ptr data; if(d->properties[path].size > MAX_MEM_FILE) - data = make_unique(File::encodeName(File::tempFileName()).c_str(), fstream::in|fstream::out|fstream::binary|fstream::trunc); + data = make_unique(File::encodeName(File::tempFileName()), fstream::in|fstream::out|fstream::binary|fstream::trunc); else data = make_unique(); z.extract(path, *data); @@ -180,20 +178,20 @@ void ASiContainer::addDataFile(const string &path, const string &mediaType) ZipSerialize::Properties prop { appInfo(), File::modifiedTime(path), File::fileSize(path) }; bool useTempFile = prop.size > MAX_MEM_FILE; - zproperty(File::fileName(path), move(prop)); + zproperty(File::fileName(path), std::move(prop)); unique_ptr is; if(useTempFile) { - is = make_unique(File::encodeName(path).c_str(), ifstream::binary); + is = make_unique(File::encodeName(path), ifstream::binary); } else { - stringstream *data = new stringstream; - if(ifstream file(File::encodeName(path).c_str(), ifstream::binary); file) + auto data = make_unique(); + if(ifstream file{File::encodeName(path), ifstream::binary}) *data << file.rdbuf(); - is.reset(data); + is = std::move(data); } - addDataFilePrivate(move(is), fileName, mediaType); + addDataFilePrivate(std::move(is), fileName, mediaType); } void ASiContainer::addDataFile(unique_ptr is, const string &fileName, const string &mediaType) @@ -201,7 +199,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(move(is), fileName, mediaType); + addDataFilePrivate(std::move(is), fileName, mediaType); } void ASiContainer::addDataFileChecks(const string &fileName, const string &mediaType) @@ -218,7 +216,7 @@ void ASiContainer::addDataFileChecks(const string &fileName, const string &media void ASiContainer::addDataFilePrivate(unique_ptr is, const string &fileName, const string &mediaType) { - d->documents.push_back(new DataFilePrivate(move(is), fileName, mediaType)); + d->documents.push_back(new DataFilePrivate(std::move(is), fileName, mediaType)); } /** @@ -235,7 +233,7 @@ void ASiContainer::removeDataFile(unsigned int id) THROW("Can not remove document from container which has signatures, remove all signatures before removing document."); if(id >= d->documents.size()) THROW("Incorrect document id %u, there are only %zu documents in container.", id, dataFiles().size()); - vector::const_iterator it = (d->documents.cbegin() + id); + auto it = d->documents.cbegin() + id; delete *it; d->documents.erase(it); } @@ -256,7 +254,7 @@ void ASiContainer::removeSignature(unsigned int id) { if(id >= d->signatures.size()) THROW("Incorrect signature id %u, there are only %zu signatures in container.", id, d->signatures.size()); - vector::const_iterator it = (d->signatures.cbegin() + id); + auto it = d->signatures.cbegin() + id; delete *it; d->signatures.erase(it); } @@ -280,15 +278,14 @@ string ASiContainer::zpath() const ZipSerialize::Properties ASiContainer::zproperty(const string &file) const { - map::const_iterator i = d->properties.find(file); - if(i != d->properties.cend()) + if(auto i = d->properties.find(file); i != d->properties.cend()) return i->second; return d->properties[file] = { appInfo(), time(nullptr), 0 }; } void ASiContainer::zproperty(const string &file, ZipSerialize::Properties &&prop) { - d->properties[file] = move(prop); + d->properties[file] = std::move(prop); } /** @@ -298,9 +295,12 @@ void ASiContainer::zproperty(const string &file, ZipSerialize::Properties &&prop * @throws IOException exception is thrown if there was error reading mimetype file from disk. * @throws ContainerException exception is thrown if the parsed mimetype is incorrect. */ -string ASiContainer::readMimetype(istream &is) +string ASiContainer::readMimetype(const ZipSerialize &z) { DEBUG("ASiContainer::readMimetype()"); + stringstream is; + z.extract("mimetype", is); + array bom{}; is.read((char*)bom.data(), bom.size()); // Contains UTF-16 BOM diff --git a/src/ASiContainer.h b/src/ASiContainer.h index 6be4cb088..56c6de0bf 100644 --- a/src/ASiContainer.h +++ b/src/ASiContainer.h @@ -72,8 +72,8 @@ namespace digidoc ZipSerialize::Properties zproperty(const std::string &file) const; void zproperty(const std::string &file, ZipSerialize::Properties &&prop); - static std::string readMimetype(std::istream &path); - + static std::string readMimetype(const ZipSerialize &z); + private: DISABLE_COPY(ASiContainer); diff --git a/src/SignatureTST.cpp b/src/SignatureTST.cpp index 8763c8480..55f48cc79 100644 --- a/src/SignatureTST.cpp +++ b/src/SignatureTST.cpp @@ -29,16 +29,16 @@ using namespace digidoc; using namespace std; -SignatureTST::SignatureTST(std::unique_ptr is, ASiC_S *asicSDoc): asicSDoc(asicSDoc) +SignatureTST::SignatureTST(istream &is, ASiC_S *asicSDoc): asicSDoc(asicSDoc) { - is->seekg(0, istream::end); - istream::pos_type pos = is->tellg(); + is.seekg(0, istream::end); + istream::pos_type pos = is.tellg(); const auto size = pos < 0 ? 0 : (unsigned long)pos; - is->clear(); - is->seekg(0, istream::beg); + is.clear(); + is.seekg(0, istream::beg); vector buf(size, 0); - is->read((char*)buf.data(), streamsize(buf.size())); + is.read((char*)buf.data(), streamsize(buf.size())); timestampToken = make_unique(buf.data(), buf.size()); } diff --git a/src/SignatureTST.h b/src/SignatureTST.h index 2cea49f12..11053bd68 100644 --- a/src/SignatureTST.h +++ b/src/SignatureTST.h @@ -31,7 +31,7 @@ class TS; class SignatureTST final: public Signature { public: - SignatureTST(std::unique_ptr sigdata, ASiC_S *asicSDoc); + SignatureTST(std::istream &sigdata, ASiC_S *asicSDoc); ~SignatureTST(); std::string trustedSigningTime() const final; diff --git a/src/SignatureXAdES_B.cpp b/src/SignatureXAdES_B.cpp index b439165a7..fdf5b0fb8 100644 --- a/src/SignatureXAdES_B.cpp +++ b/src/SignatureXAdES_B.cpp @@ -539,7 +539,7 @@ void SignatureXAdES_B::validate(const string &policy) const else { string uri = File::fromUriPath(ref.uRI().get()); - if(uri[0] == '/') + if(uri.front() == '/') uri.erase(0); signatureref.insert({ uri, mimeinfo["#"+ref.id().get()] }); } @@ -554,7 +554,7 @@ void SignatureXAdES_B::validate(const string &policy) const { if(auto i = signatureref.find(file->fileName()); i != signatureref.end()) { - if(i->second != file->mediaType()) + if(bdoc->mediaType() != ASiContainer::MIMETYPE_ASIC_S && i->second != file->mediaType()) EXCEPTION_ADD(exception, "Manifest datafile '%s' mime '%s' does not match signature mime '%s'", file->fileName().c_str(), file->mediaType().c_str(), i->second.c_str()); static const regex reg(R"(([\w])*/([\w\-\+\.])*)"); diff --git a/src/util/ZipSerialize.cpp b/src/util/ZipSerialize.cpp index 87bf9dbe3..cae1ebe95 100644 --- a/src/util/ZipSerialize.cpp +++ b/src/util/ZipSerialize.cpp @@ -59,7 +59,7 @@ ZipSerialize::ZipSerialize(string path, bool create) #else fill_fopen_filefunc(&d->def); #endif - d->path = move(path); + d->path = std::move(path); if(create) { DEBUG("ZipSerialize::create(%s)", d->path.c_str()); @@ -105,17 +105,15 @@ vector ZipSerialize::list() const if(unzResult != UNZ_OK) THROW("Failed to go to the next file inside ZIP container. ZLib error: %d", unzResult); - unz_file_info fileInfo; + unz_file_info fileInfo{}; unzResult = unzGetCurrentFileInfo(d->open, &fileInfo, nullptr, 0, nullptr, 0, nullptr, 0); if(unzResult != UNZ_OK) THROW("Failed to get filename of the current file inside ZIP container. ZLib error: %d", unzResult); - string fileName(fileInfo.size_filename, 0); + auto &fileName = list.emplace_back(fileInfo.size_filename, 0); unzResult = unzGetCurrentFileInfo(d->open, &fileInfo, fileName.data(), uLong(fileName.size()), nullptr, 0, nullptr, 0); if(unzResult != UNZ_OK) THROW("Failed to get filename of the current file inside ZIP container. ZLib error: %d", unzResult); - - list.push_back(move(fileName)); } return list; @@ -132,7 +130,7 @@ vector ZipSerialize::list() const void ZipSerialize::extract(const string &file, ostream &os) const { DEBUG("ZipSerializePrivate::extract(%s)", file.c_str()); - if(file[file.size()-1] == '/') + if(file.empty() || file.back() == '/') return; int unzResult = unzLocateFile(d->open, file.c_str(), 1); @@ -143,13 +141,11 @@ void ZipSerialize::extract(const string &file, ostream &os) const if(unzResult != UNZ_OK) THROW("Failed to open file inside ZIP container. ZLib error: %d", unzResult); - int currentStreamSize = 0; array buf{}; - while((unzResult = unzReadCurrentFile(d->open, buf.data(), buf.size())) > UNZ_EOF) + for(int currentStreamSize = 0; + (unzResult = unzReadCurrentFile(d->open, buf.data(), buf.size())) > UNZ_EOF; currentStreamSize += unzResult) { - os.write(buf.data(), unzResult); - currentStreamSize += unzResult; - if(os.fail()) + if(!os.write(buf.data(), unzResult)) { unzCloseCurrentFile(d->open); THROW("Failed to write file '%s' data to stream. Stream size: %d", file.c_str(), currentStreamSize); @@ -181,8 +177,8 @@ void ZipSerialize::addFile(const string& containerPath, istream &is, const Prope THROW("Zip file is not open"); DEBUG("ZipSerialize::addFile(%s)", containerPath.c_str()); - struct tm time = util::date::gmtime(prop.time); - zip_fileinfo info = { + tm time = util::date::gmtime(prop.time); + zip_fileinfo info { { time.tm_sec, time.tm_min, time.tm_hour, time.tm_mday, time.tm_mon, time.tm_year }, 0, 0, 0 }; @@ -190,7 +186,7 @@ void ZipSerialize::addFile(const string& containerPath, istream &is, const Prope // Create new file inside ZIP container. int method = flags & DontCompress ? Z_NULL : Z_DEFLATED; int level = flags & DontCompress ? Z_NO_COMPRESSION : Z_DEFAULT_COMPRESSION; - uLong UTF8_encoding = 1 << 11; // general purpose bit 11 for unicode + static constexpr uLong UTF8_encoding = 1 << 11; // general purpose bit 11 for unicode int zipResult = zipOpenNewFileInZip4(d->create, containerPath.c_str(), &info, nullptr, 0, nullptr, 0, prop.comment.c_str(), method, level, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, nullptr, 0, 0, UTF8_encoding); @@ -200,7 +196,7 @@ void ZipSerialize::addFile(const string& containerPath, istream &is, const Prope is.clear(); is.seekg(0); array buf{}; - while( is ) + while(is) { is.read(buf.data(), buf.size()); if(is.gcount() <= 0) @@ -230,18 +226,17 @@ ZipSerialize::Properties ZipSerialize::properties(const string &file) const if(unzResult != UNZ_OK) THROW("Failed to get filename of the current file inside ZIP container. ZLib error: %d", unzResult); - struct tm time = { int(info.tmu_date.tm_sec), int(info.tmu_date.tm_min), int(info.tmu_date.tm_hour), - int(info.tmu_date.tm_mday), int(info.tmu_date.tm_mon), int(info.tmu_date.tm_year), 0, 0, 0 + tm time { info.tmu_date.tm_sec, info.tmu_date.tm_min, info.tmu_date.tm_hour, + info.tmu_date.tm_mday, info.tmu_date.tm_mon, info.tmu_date.tm_year, 0, 0, 0, #ifndef _WIN32 - , 0, nullptr + 0, nullptr #endif }; - Properties prop { {}, util::date::mkgmtime(time), info.uncompressed_size }; - if(info.size_file_comment == 0) + Properties prop { string(size_t(info.size_file_comment), 0), util::date::mkgmtime(time), info.uncompressed_size }; + if(prop.comment.empty()) return prop; - prop.comment.resize(info.size_file_comment); - unzResult = unzGetCurrentFileInfo(d->open, &info, nullptr, 0, nullptr, 0, &prop.comment[0], uLong(prop.comment.size())); + unzResult = unzGetCurrentFileInfo(d->open, &info, nullptr, 0, nullptr, 0, prop.comment.data(), uLong(prop.comment.size())); if(unzResult != UNZ_OK) THROW("Failed to get filename of the current file inside ZIP container. ZLib error: %d", unzResult);