From 12cbbf01e4f91f675cd7e7f5a17e7f31ce6fb4e7 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 16 Dec 2024 13:53:39 +0200 Subject: [PATCH] Validate ASiC-S manifest containers IB-8180, IB-8330 Signed-off-by: Raul Metsma --- src/ASiC_S.cpp | 106 ++++++++++----------------------------- src/ASiC_S.h | 9 +--- src/ASiContainer.h | 1 + src/SignatureTST.cpp | 101 ++++++++++++++++++++++++++++--------- src/SignatureTST.h | 12 ++--- src/SignatureXAdES_B.cpp | 4 +- src/SignatureXAdES_B.h | 3 -- src/XMLDocument.h | 61 +++++++++++++++------- src/crypto/TSL.cpp | 2 - 9 files changed, 155 insertions(+), 144 deletions(-) diff --git a/src/ASiC_S.cpp b/src/ASiC_S.cpp index 28f837a16..0e71cc157 100644 --- a/src/ASiC_S.cpp +++ b/src/ASiC_S.cpp @@ -19,7 +19,6 @@ #include "ASiC_S.h" -#include "Conf.h" #include "SignatureTST.h" #include "SignatureXAdES_LTA.h" #include "crypto/Signer.h" @@ -33,18 +32,6 @@ using namespace digidoc; using namespace digidoc::util; using namespace std; -struct ASiC_S::Data { - std::string name, mime, data; - - Digest digest(Digest digest = {}) const - { - digest.update((const unsigned char*)data.data(), data.size()); - return digest; - } -}; - - - /** * Initialize ASiCS container. */ @@ -59,17 +46,19 @@ ASiC_S::ASiC_S(const string &path) : ASiContainer(MIMETYPE_ASIC_S) { auto z = load(path, false, {mediaType()}); + bool foundTimestamp = false; for(const string &file: z.list()) { if(file == "mimetype") continue; if(file == "META-INF/timestamp.tst") + foundTimestamp = true; + if(file == "META-INF/ASiCArchiveManifest.xml") { if(!signatures().empty()) THROW("Can not add signature to ASiC-S container which already contains a signature."); - string tst = z.extract(file).str(); - addSignature(make_unique(tst, this)); - metadata.push_back({file, "application/vnd.etsi.timestamp-token", std::move(tst)}); + addSignature(make_unique(true, z, this)); + foundTimestamp = false; // Manifest contains timestamp } else if(file == "META-INF/signatures.xml") { @@ -80,28 +69,6 @@ ASiC_S::ASiC_S(const string &path) for(auto s = signatures->signature(); s; s++) addSignature(make_unique(signatures, s, this)); } - else if(file == "META-INF/ASiCArchiveManifest.xml") - { - function add = [this, &add, &z](const string &file, string_view mime) { - auto xml = z.extract(file); - XMLDocument doc = XMLDocument::openStream(xml, {"ASiCManifest", ASIC_NS}); - doc.validateSchema(util::File::path(Conf::instance()->xsdPath(), "en_31916201v010101.xsd")); - - for(auto ref = doc/"DataObjectReference"; ref; ref++) - { - if(ref["Rootfile"] == "true") - add(util::File::fromUriPath(ref["URI"]), ref["MimeType"]); - } - - auto ref = doc/"SigReference"; - string uri = util::File::fromUriPath(ref["URI"]); - string tst = z.extract(uri).str(); - addSignature(make_unique(file, ::move(doc), tst, this)); - metadata.push_back({file, string(mime), xml.str()}); - metadata.push_back({uri, string(ref["MimeType"]), std::move(tst)}); - }; - add(file, "text/xml"); - } else if(starts_with(file, "META-INF/")) continue; else if(const auto directory = File::directory(file); @@ -112,6 +79,12 @@ ASiC_S::ASiC_S(const string &path) else addDataFile(dataStream(file, z), file, "application/octet-stream"); } + if(foundTimestamp) + { + if(!signatures().empty()) + THROW("Can not add signature to ASiC-S container which already contains a signature."); + addSignature(make_unique(false, z, this)); + } if(dataFiles().empty()) THROW("ASiC-S container does not contain any data objects."); @@ -147,20 +120,20 @@ void ASiC_S::canSave() THROW("ASiC-S container supports only saving TimeStampToken signatures."); } -Digest ASiC_S::fileDigest(const string &file, string_view method) const -{ - if(auto i = find_if(metadata.cbegin(), metadata.cend(), [&file](const auto &d) { return d.name == file; }); - i != metadata.cend()) - return i->digest(method); - THROW("File not found %s.", file.c_str()); -} - unique_ptr ASiC_S::openInternal(const string &path, ContainerOpenCB * /*cb*/) { - if (!isContainerSimpleFormat(path)) - return {}; DEBUG("ASiC_S::openInternal(%s)", path.c_str()); - return unique_ptr(new ASiC_S(path)); + try + { + if(util::File::fileExtension(path, {"asice", "sce", "bdoc"})) + return {}; + return unique_ptr(new ASiC_S(path)); + } + catch(const Exception &) + { + // Ignore the exception: not ASiC/zip document + } + return {}; } Signature* ASiC_S::prepareSignature(Signer * /*signer*/) @@ -170,10 +143,11 @@ Signature* ASiC_S::prepareSignature(Signer * /*signer*/) 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)) + if(const auto &prop = zproperty("META-INF/manifest.xml"); + prop.size && !createManifest().save(s.addFile("META-INF/manifest.xml", prop), true)) THROW("Failed to create manifest XML"); - for(const auto &[name, mime, data]: metadata) - s.addFile(name, zproperty(name))(data); + for(Signature *sig: signatures()) + static_cast(sig)->save(s); } Signature *ASiC_S::sign(Signer *signer) @@ -184,31 +158,3 @@ Signature *ASiC_S::sign(Signer *signer) THROW("ASiC-S container supports only one TimeStampToken signature."); return addSignature(make_unique(this, signer)); } - -/** - * Detect ASiC format based on file extentions, mimetype or zip contents.
- * Container format is simple (ASiC-S) or extended (ASiC-E). - * - * @param path Path of the container. - * @throws Exception - */ -bool ASiC_S::isContainerSimpleFormat(const string &path) -{ - DEBUG("isContainerSimpleFormat(path = '%s')", path.c_str()); - if(util::File::fileExtension(path, {"asice", "sce", "bdoc"})) - return false; - if(util::File::fileExtension(path, {"asics", "scs"})) - return true; - DEBUG("Check if ASiC/zip containter"); - try - { - ZipSerialize z(path, false); - vector list = z.list(); - return 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 6bbca3758..5397a6802 100644 --- a/src/ASiC_S.h +++ b/src/ASiC_S.h @@ -23,8 +23,6 @@ namespace digidoc { - class Digest; - /** * Implements the ASiC-S specification of the timestamped digital document container. * Container contains a single datafile object and one time assertion file. @@ -39,8 +37,6 @@ namespace digidoc Signature* prepareSignature(Signer *signer) override; Signature* sign(Signer* signer) override; - Digest fileDigest(const std::string &file, std::string_view method = {}) const; - static std::unique_ptr createInternal(const std::string &path); static std::unique_ptr openInternal(const std::string &path, ContainerOpenCB *cb); @@ -53,9 +49,6 @@ namespace digidoc void canSave() final; void save(const ZipSerialize &s) final; - static bool isContainerSimpleFormat(const std::string &path); - - struct Data; - std::vector metadata; + friend class SignatureTST; }; } diff --git a/src/ASiContainer.h b/src/ASiContainer.h index cdc0b952a..cd2772287 100644 --- a/src/ASiContainer.h +++ b/src/ASiContainer.h @@ -45,6 +45,7 @@ namespace digidoc //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"; + static constexpr std::string_view ASIC_NS = "http://uri.etsi.org/02918/v1.2.1#"; ~ASiContainer() override; std::string mediaType() const override; diff --git a/src/SignatureTST.cpp b/src/SignatureTST.cpp index da1a65dcb..63127e7c1 100644 --- a/src/SignatureTST.cpp +++ b/src/SignatureTST.cpp @@ -20,32 +20,62 @@ #include "SignatureTST.h" #include "ASiC_S.h" +#include "Conf.h" #include "DataFile_p.h" +#include "XMLDocument.h" #include "crypto/Digest.h" #include "crypto/Signer.h" #include "crypto/TS.h" #include "crypto/X509Cert.h" +#include "util/algorithm.h" #include "util/DateTime.h" #include "util/File.h" #include "util/log.h" +#include +#include + using namespace digidoc; using namespace std; -constexpr std::string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"}; -constexpr XMLName DigestMethod {"DigestMethod", DSIG_NS}; -constexpr XMLName DigestValue {"DigestValue", DSIG_NS}; +struct SignatureTST::Data { + std::string name, mime, data; + bool root = false; -SignatureTST::SignatureTST(const string &data, ASiC_S *asicSDoc) - : asicSDoc(asicSDoc) - , timestampToken(make_unique((const unsigned char*)data.data(), data.size())) -{} + Digest digest(Digest digest = {}) const + { + digest.update((const unsigned char*)data.data(), data.size()); + return digest; + } +}; -SignatureTST::SignatureTST(string current, XMLDocument &&xml, const string &data, ASiC_S *asicSDoc) - : SignatureTST(data, asicSDoc) +SignatureTST::SignatureTST(bool manifest, const ZipSerialize &z, ASiC_S *asicSDoc) + : asicSDoc(asicSDoc) { - file = std::move(current); - doc = std::move(xml); + auto data = z.extract("META-INF/timestamp.tst").str(); + timestampToken = make_unique((const unsigned char*)data.data(), data.size()); + metadata.push_back({"META-INF/timestamp.tst", "application/vnd.etsi.timestamp-token", std::move(data)}); + if(!manifest) + return; + XMLSchema schema(util::File::path(Conf::instance()->xsdPath(), "en_31916201v010101.xsd")); + function add = [this, &schema, &add, &z](const string &file, string_view mime) { + auto xml = z.extract(file); + XMLDocument doc = XMLDocument::openStream(xml, {"ASiCManifest", ASiContainer::ASIC_NS}); + schema.validate(doc); + + for(auto ref = doc/"DataObjectReference"; ref; ref++) + { + if(ref["Rootfile"] == "true") + add(util::File::fromUriPath(ref["URI"]), ref["MimeType"]); + } + + auto ref = doc/"SigReference"; + string uri = util::File::fromUriPath(ref["URI"]); + string tst = z.extract(uri).str(); + metadata.push_back({file, string(mime), xml.str()}); + metadata.push_back({uri, string(ref["MimeType"]), std::move(tst)}); + }; + add("META-INF/ASiCArchiveManifest.xml", "text/xml"); } SignatureTST::SignatureTST(ASiC_S *asicSDoc, Signer *signer) @@ -55,6 +85,8 @@ SignatureTST::SignatureTST(ASiC_S *asicSDoc, Signer *signer) Digest digest; dataFile->digest(digest); timestampToken = make_unique(digest, signer->userAgent()); + vector der = *timestampToken; + metadata.push_back({"META-INF/timestamp.tst", "application/vnd.etsi.timestamp-token", {der.cbegin(), der.cend()}}); } SignatureTST::~SignatureTST() = default; @@ -106,28 +138,48 @@ void SignatureTST::validate() const } try { - timestampToken->verify(dataToSign()); - if(auto digestMethod = signatureMethod(); - !Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) && + auto digestMethod = signatureMethod(); + DataFile *file = asicSDoc->dataFiles().front(); + timestampToken->verify(file->calcDigest(digestMethod)); + if(!Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) && Digest::isWeakDigest(digestMethod)) { Exception e(EXCEPTION_PARAMS("TimeStamp '%s' digest weak", digestMethod.c_str())); e.setCode(Exception::ReferenceDigestWeak); exception.addCause(e); } - if(doc) + vector list {file->fileName()}; + for(const auto &manifest: metadata) { - DataFile *file = asicSDoc->dataFiles().front(); + if(manifest.mime != "text/xml") + continue; + istringstream is(manifest.data); + XMLDocument doc = XMLDocument::openStream(is, {"ASiCManifest", ASiContainer::ASIC_NS}); + vector add; for(auto ref = doc/"DataObjectReference"; ref; ref++) { string_view method = (ref/DigestMethod)["Algorithm"]; - auto uri = util::File::fromUriPath(ref["URI"]); - vector digest = file->fileName() == uri ? - dynamic_cast(file)->calcDigest(string(method)) : - asicSDoc->fileDigest(uri, method).result(); + const auto &uri = add.emplace_back(util::File::fromUriPath(ref["URI"])); + vector digest; + if(file->fileName() == uri) + digest = file->calcDigest(string(method)); + else + { + auto i = find_if(metadata.cbegin(), metadata.cend(), [&uri](const auto &d) { return d.name == uri; }); + if(i == metadata.cend()) + THROW("File not found '%s'.", uri.c_str()); + digest = i->digest(method).result(); + } if(vector digestValue = ref/DigestValue; digest != digestValue) - THROW("Reference %s digest does not match", uri.c_str()); + THROW("Reference '%s' digest does not match", uri.c_str()); + } + // Check if all files in previous scope are present + for(const string &uri: list) + { + if(!contains(add, uri)) + THROW("Reference '%s' not found in manifest", uri.c_str()); } + list = std::move(add); } } catch (const Exception& e) @@ -141,8 +193,6 @@ void SignatureTST::validate() const std::vector SignatureTST::dataToSign() const { - if(!file.empty()) - return asicSDoc->fileDigest(file, signatureMethod()).result(); return asicSDoc->dataFiles().front()->calcDigest(signatureMethod()); } @@ -162,7 +212,8 @@ string SignatureTST::profile() const return string(ASiC_S::ASIC_TST_PROFILE); } -std::vector SignatureTST::save() const +void SignatureTST::save(const ZipSerialize &z) const { - return *timestampToken; + for(const auto &[name, mime, data, root]: metadata) + z.addFile(name, asicSDoc->zproperty(name))(data); } diff --git a/src/SignatureTST.h b/src/SignatureTST.h index 9797270df..d442e5391 100644 --- a/src/SignatureTST.h +++ b/src/SignatureTST.h @@ -21,18 +21,18 @@ #include "Signature.h" -#include "XMLDocument.h" +#include namespace digidoc { class ASiC_S; class TS; +class ZipSerialize; class SignatureTST final: public Signature { public: - SignatureTST(const std::string &data, ASiC_S *asicSDoc); - SignatureTST(std::string current, XMLDocument &&xml, const std::string &data, ASiC_S *asicSDoc); + SignatureTST(bool manifest, const ZipSerialize &z, ASiC_S *asicSDoc); SignatureTST(ASiC_S *asicSDoc, Signer *signer); ~SignatureTST(); @@ -54,14 +54,14 @@ class SignatureTST final: public Signature // Xades properties std::string profile() const final; - std::vector save() const; + void save(const ZipSerialize &s) const; private: DISABLE_COPY(SignatureTST); ASiC_S *asicSDoc {}; - std::string file; - XMLDocument doc; std::unique_ptr timestampToken; + struct Data; + std::vector metadata; }; } diff --git a/src/SignatureXAdES_B.cpp b/src/SignatureXAdES_B.cpp index 3a05c7932..f84d870af 100644 --- a/src/SignatureXAdES_B.cpp +++ b/src/SignatureXAdES_B.cpp @@ -86,8 +86,6 @@ const map SignatureXAdES_B::policylist{ namespace digidoc { -constexpr XMLName DigestMethod {"DigestMethod", DSIG_NS}; -constexpr XMLName DigestValue {"DigestValue", DSIG_NS}; constexpr XMLName X509IssuerName {"X509IssuerName", DSIG_NS}; constexpr XMLName X509SerialNumber {"X509SerialNumber", DSIG_NS}; @@ -193,7 +191,7 @@ int initXmlSecCallback() } Signatures::Signatures() - : XMLDocument(create("XAdESSignatures", ASIC_NS, "asic")) + : XMLDocument(create("XAdESSignatures", ASiContainer::ASIC_NS, "asic")) { addNS(DSIG_NS, "ds"); addNS(XADES_NS, "xades"); diff --git a/src/SignatureXAdES_B.h b/src/SignatureXAdES_B.h index 67605d1a0..2e46a2d19 100644 --- a/src/SignatureXAdES_B.h +++ b/src/SignatureXAdES_B.h @@ -27,10 +27,7 @@ namespace digidoc { - constexpr std::string_view ASIC_NS {"http://uri.etsi.org/02918/v1.2.1#"}; constexpr std::string_view OPENDOCUMENT_NS {"urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0"}; - constexpr std::string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"}; - constexpr std::string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"}; constexpr std::string_view XADESv141_NS {"http://uri.etsi.org/01903/v1.4.1#"}; constexpr std::string_view REF_TYPE {"http://uri.etsi.org/01903#SignedProperties"}; diff --git a/src/XMLDocument.h b/src/XMLDocument.h index 33dda5a26..b504f876e 100644 --- a/src/XMLDocument.h +++ b/src/XMLDocument.h @@ -280,6 +280,7 @@ struct XMLNode: public XMLElem } }; +struct XMLSchema; struct XMLDocument: public unique_free_t, public XMLNode { static constexpr std::string_view C14D_ID_1_0 {"http://www.w3.org/TR/2001/REC-xml-c14n-20010315"}; @@ -394,23 +395,7 @@ struct XMLDocument: public unique_free_t, public XMLNode return xmlSaveFormatFileTo(buf, get(), "UTF-8", format) > 0; } - void validateSchema(const std::string &schemaPath) const - { - auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(schemaPath.c_str()), xmlSchemaFreeParserCtxt); - if(!parser) - THROW("Failed to create schema parser context %s", schemaPath.c_str()); - xmlSchemaSetParserErrors(parser.get(), schemaValidationError, schemaValidationWarning, nullptr); - auto schema = make_unique_ptr(xmlSchemaParse(parser.get()), xmlSchemaFree); - if(!schema) - THROW("Failed to parse schema %s", schemaPath.c_str()); - auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(schema.get()), xmlSchemaFreeValidCtxt); - if(!validate) - THROW("Failed to create schema validation context %s", schemaPath.c_str()); - Exception e(EXCEPTION_PARAMS("Failed to XML with schema")); - xmlSchemaSetValidErrors(validate.get(), schemaValidationError, schemaValidationWarning, &e); - if(xmlSchemaValidateDoc(validate.get(), get()) != 0) - throw e; - } + inline void validateSchema(const XMLSchema &schema) const; static bool verifySignature(XMLNode signature, [[maybe_unused]] Exception *e = {}) noexcept { @@ -444,6 +429,36 @@ struct XMLDocument: public unique_free_t, public XMLNode return false; return ctx->status == xmlSecDSigStatusSucceeded; } +}; + +struct XMLSchema +{ + auto parser(const std::string &path) + { + auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(path.c_str()), xmlSchemaFreeParserCtxt); + if(!parser) + THROW("Failed to create schema parser context %s", path.c_str()); + xmlSchemaSetParserErrors(parser.get(), schemaValidationError, schemaValidationWarning, nullptr); + return parser; + } + + XMLSchema(const std::string &path) + : d(make_unique_ptr(xmlSchemaParse(parser(path).get()), xmlSchemaFree)) + { + if(!d) + THROW("Failed to parse schema %s", path.c_str()); + } + + void validate(const XMLDocument &doc) const + { + auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(d.get()), xmlSchemaFreeValidCtxt); + if(!validate) + THROW("Failed to create schema validation context"); + Exception e(EXCEPTION_PARAMS("Failed to XML with schema")); + xmlSchemaSetValidErrors(validate.get(), schemaValidationError, schemaValidationWarning, &e); + if(xmlSchemaValidateDoc(validate.get(), doc.get()) != 0) + throw e; + } static void schemaValidationError(void *ctx, const char *msg, ...) noexcept { @@ -468,6 +483,18 @@ struct XMLDocument: public unique_free_t, public XMLNode va_end(args); WARN("Schema validation warning: %s", m.c_str()); } + + unique_free_t d; }; +inline void XMLDocument::validateSchema(const XMLSchema &schema) const +{ + schema.validate(*this); +} + +constexpr std::string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"}; +constexpr std::string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"}; +constexpr XMLName DigestMethod {"DigestMethod", DSIG_NS}; +constexpr XMLName DigestValue {"DigestValue", DSIG_NS}; + } diff --git a/src/crypto/TSL.cpp b/src/crypto/TSL.cpp index cdcc8aa84..5bc99e34f 100644 --- a/src/crypto/TSL.cpp +++ b/src/crypto/TSL.cpp @@ -40,8 +40,6 @@ namespace digidoc { constexpr string_view TSL_NS {"http://uri.etsi.org/02231/v2#"}; constexpr string_view ADD_NS {"http://uri.etsi.org/02231/v2/additionaltypes#"}; constexpr string_view ECC_NS {"http://uri.etsi.org/TrstSvc/SvcInfoExt/eSigDir-1999-93-EC-TrustedList/#"}; -constexpr string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"}; -constexpr string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"}; constexpr string_view XML_NS {"http://www.w3.org/XML/1998/namespace"}; constexpr array SCHEMES_URI {