diff --git a/examples/DigiDocCSharp/Program.cs b/examples/DigiDocCSharp/Program.cs index 40387bb92..617cbf2d5 100644 --- a/examples/DigiDocCSharp/Program.cs +++ b/examples/DigiDocCSharp/Program.cs @@ -209,6 +209,11 @@ private static void Verify(string file) Console.WriteLine("Time: " + s.trustedSigningTime()); Console.WriteLine("Cert: " + s.signingCertificate().Subject); Console.WriteLine("TimeStamp: " + s.TimeStampCertificate().Subject); + foreach (TSAInfo tsaInfo in s.ArchiveTimeStamps()) + { + Console.WriteLine("Archive Time: " + tsaInfo.time); + Console.WriteLine("Archive Cert: " + tsaInfo.cert.Subject); + } s.validate(); Console.WriteLine("Signature is valid"); diff --git a/examples/java/src/main/java/ee/ria/libdigidocpp/libdigidocpp.java b/examples/java/src/main/java/ee/ria/libdigidocpp/libdigidocpp.java index db1213b93..6510f0674 100644 --- a/examples/java/src/main/java/ee/ria/libdigidocpp/libdigidocpp.java +++ b/examples/java/src/main/java/ee/ria/libdigidocpp/libdigidocpp.java @@ -152,6 +152,10 @@ static void verify(String file) { System.out.println("Time: " + signature.trustedSigningTime()); System.out.println("Cert: " + signature.signingCertificate().getSubjectDN().toString()); System.out.println("TimeStamp Cert: " + signature.TimeStampCertificate().getSubjectDN().toString()); + for(TSAInfo tsaInfo : signature.ArchiveTimeStamps()) { + System.out.println("Archive Time: " + tsaInfo.getTime()); + System.out.println("Archive Cert: " + tsaInfo.getCert().getSubjectDN().toString()); + } try { diff --git a/libdigidocpp.i b/libdigidocpp.i index 90a4606cb..db4774d19 100644 --- a/libdigidocpp.i +++ b/libdigidocpp.i @@ -102,15 +102,17 @@ static std::vector* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je %{ $1 = SWIG_JavaArrayToVectorUnsignedChar(jenv, $input); %} %typemap(out, fragment="SWIG_VectorUnsignedCharToJavaArray") std::vector, digidoc::X509Cert %{ $result = SWIG_VectorUnsignedCharToJavaArray(jenv, $1); %} -%typemap(jtype) std::vector, digidoc::X509Cert "byte[]" +%typemap(out, fragment="SWIG_VectorUnsignedCharToJavaArray") digidoc::X509Cert * +%{ $result = SWIG_VectorUnsignedCharToJavaArray(jenv, *$1); %} +%typemap(jtype) std::vector, digidoc::X509Cert, digidoc::X509Cert * "byte[]" %typemap(jstype) std::vector "byte[]" -%typemap(jstype) digidoc::X509Cert "java.security.cert.X509Certificate" -%typemap(jni) std::vector, digidoc::X509Cert "jbyteArray" +%typemap(jstype) digidoc::X509Cert, digidoc::X509Cert* "java.security.cert.X509Certificate" +%typemap(jni) std::vector, digidoc::X509Cert, digidoc::X509Cert * "jbyteArray" %typemap(javain) std::vector, digidoc::X509Cert "$javainput" %typemap(javaout) std::vector { return $jnicall; } -%typemap(javaout, throws="java.security.cert.CertificateException, java.io.IOException") digidoc::X509Cert { +%typemap(javaout, throws="java.security.cert.CertificateException, java.io.IOException") digidoc::X509Cert, digidoc::X509Cert * { byte[] der = $jnicall; java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X509"); try (java.io.ByteArrayInputStream is = new java.io.ByteArrayInputStream(der)) { @@ -120,7 +122,7 @@ static std::vector* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je #elif defined(SWIGCSHARP) %typemap(cstype) std::vector "byte[]" -%typemap(cstype) digidoc::X509Cert "System.Security.Cryptography.X509Certificates.X509Certificate2" +%typemap(cstype) digidoc::X509Cert, digidoc::X509Cert* "System.Security.Cryptography.X509Certificates.X509Certificate2" %typemap(csin, pre= " global::System.IntPtr cPtr$csinput = digidocPINVOKE.ByteVector_to($csinput, $csinput.Length); var handleRef$csinput = new global::System.Runtime.InteropServices.HandleRef($csinput, cPtr$csinput);" ) std::vector "handleRef$csinput" @@ -132,6 +134,14 @@ static std::vector* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je global::System.IntPtr cPtr = $imcall;$excode return new System.Security.Cryptography.X509Certificates.X509Certificate2($modulePINVOKE.To_ByteArray(cPtr)); } +%typemap(csvarout, excode=SWIGEXCODE2) digidoc::X509Cert * %{ + get { + global::System.IntPtr cPtr = $imcall;$excode + byte[] der = new byte[$modulePINVOKE.ByteVector_size(cPtr)]; + global::System.Runtime.InteropServices.Marshal.Copy($modulePINVOKE.ByteVector_data(cPtr), der, 0, der.Length); + $modulePINVOKE.ByteVector_free(cPtr); + return new System.Security.Cryptography.X509Certificates.X509Certificate2(der); + } %} %typemap(out) std::vector %{ $result = new std::vector(std::move($1)); %} %typemap(out) digidoc::X509Cert %{ $result = new std::vector($1.operator std::vector()); %} @@ -154,6 +164,10 @@ static std::vector* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je std::vector temp = $1; $result = PyBytes_FromStringAndSize((const char*)temp.data(), temp.size()); } +%typemap(out) digidoc::X509Cert * { + std::vector temp = *$1; + $result = PyBytes_FromStringAndSize((const char*)temp.data(), temp.size()); +} #endif %typemap(freearg) std::vector %{ delete $1; %} @@ -201,9 +215,14 @@ static std::vector* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je // std::unique_ptr is since swig 4.1 %ignore digidoc::Container::createPtr; %ignore digidoc::Container::openPtr; +%ignore digidoc::Container::extendContainerValidity; %newobject digidoc::Container::open; %newobject digidoc::Container::create; +%newobject digidoc::Container::extendContainerValidity; + +%immutable digidoc::TSAInfo::cert; +%immutable digidoc::TSAInfo::time; %feature("director") digidoc::ContainerOpenCB; @@ -262,8 +281,16 @@ def transfer(self): %template(StringMap) std::map; %template(DataFiles) std::vector; %template(Signatures) std::vector; +%template(TSAInfos) std::vector; + +%rename("%s") digidoc::Container::extendContainerValidity; %extend digidoc::Container { + static Container* extendContainerValidity(Container &doc, Signer *signer) + { + return digidoc::Container::extendContainerValidity(doc, signer).release(); + } + static digidoc::Container* open(const std::string &path, digidoc::ContainerOpenCB *cb) { return digidoc::Container::openPtr(path, cb).release(); diff --git a/src/ASiC_E.cpp b/src/ASiC_E.cpp index 5b2b584b3..a73fb6e1d 100644 --- a/src/ASiC_E.cpp +++ b/src/ASiC_E.cpp @@ -51,21 +51,89 @@ class ASiC_E::Private /** * Initialize BDOC container. */ -ASiC_E::ASiC_E() - : ASiContainer(MIMETYPE_ASIC_E) +ASiC_E::ASiC_E(const string &path, bool create) + : ASiContainer(path, MIMETYPE_ASIC_E) , d(make_unique()) { -} + if(create) + return; + auto z = load(true, {MIMETYPE_ASIC_E, MIMETYPE_ADOC}); -/** - * Opens ASiC container from a file - */ -ASiC_E::ASiC_E(const string &path) - : ASiContainer(MIMETYPE_ASIC_E) - , d(make_unique()) -{ - auto zip = load(path, true, {MIMETYPE_ASIC_E, MIMETYPE_ADOC}); - parseManifestAndLoadFiles(zip); + try + { + auto manifestdata = z.extract("META-INF/manifest.xml"); + auto doc = XMLDocument::openStream(manifestdata, {"manifest", MANIFEST_NS}); + doc.validateSchema(File::path(Conf::instance()->xsdPath(), "OpenDocument_manifest_v1_2.xsd")); + + set manifestFiles; + bool mimeFound = false; + for(auto file = doc/"file-entry"; file; file++) + { + auto full_path = file[{"full-path", MANIFEST_NS}]; + auto media_type = file[{"media-type", MANIFEST_NS}]; + DEBUG("full_path = '%s', media_type = '%s'", full_path.data(), media_type.data()); + + if(manifestFiles.find(full_path) != manifestFiles.end()) + THROW("Manifest multiple entries defined for file '%s'.", full_path.data()); + + // ODF does not specify that mimetype should be first in manifest + if(full_path == "/") + { + if(mediaType() != media_type) + THROW("Manifest has incorrect container media type defined '%s', expecting '%s'.", media_type.data(), mediaType().c_str()); + mimeFound = true; + continue; + } + if(full_path.back() == '/') // Skip Directory entries + continue; + + manifestFiles.insert(full_path); + if(mediaType() == MIMETYPE_ADOC && + (full_path.compare(0, 9, "META-INF/") == 0 || + full_path.compare(0, 9, "metadata/") == 0)) + d->metadata.push_back(new DataFilePrivate(dataStream(full_path, z), string(full_path), string(media_type))); + else + addDataFilePrivate(dataStream(full_path, z), string(full_path), string(media_type)); + } + if(!mimeFound) + THROW("Manifest is missing mediatype file entry."); + + for(const string &file: z.list()) + { + /** + * http://www.etsi.org/deliver/etsi_ts/102900_102999/102918/01.03.01_60/ts_102918v010301p.pdf + * 6.2.2 Contents of Container + * 3) The root element of each "*signatures*.xml" content shall be either: + */ + if(file.compare(0, 9, "META-INF/") == 0 && + file.find("signatures") != string::npos) + { + try + { + auto data = z.extract(file); + loadSignatures(data, file); + } + catch(const Exception &e) + { + THROW_CAUSE(e, "Failed to parse signature '%s'.", file.c_str()); + } + continue; + } + + if(file == "mimetype" || file.compare(0, 8,"META-INF") == 0) + continue; + if(manifestFiles.count(file) == 0) + THROW("File '%s' found in container is not described in manifest.", file.c_str()); + } + } + catch(const Exception &e) + { + THROW_CAUSE(e, "Failed to parse manifest"); + } + catch(...) + { + THROW("Failed to parse manifest XML: Unknown exception"); + } } ASiC_E::~ASiC_E() @@ -110,9 +178,7 @@ void ASiC_E::save(const ZipSerialize &s) unique_ptr ASiC_E::createInternal(const string &path) { DEBUG("ASiC_E::createInternal(%s)", path.c_str()); - unique_ptr doc = unique_ptr(new ASiC_E); - doc->zpath(path); - return doc; + return unique_ptr(new ASiC_E(path, true)); } /** @@ -146,7 +212,7 @@ void ASiC_E::canSave() unique_ptr ASiC_E::openInternal(const string &path) { DEBUG("ASiC_E::openInternal(%s)", path.c_str()); - return unique_ptr(new ASiC_E(path)); + return unique_ptr(new ASiC_E(path, false)); } void ASiC_E::loadSignatures(istream &data, const string &file) @@ -157,94 +223,6 @@ void ASiC_E::loadSignatures(istream &data, const string &file) addSignature(make_unique(signatures, s, this)); } -/** - * Parses manifest file and checks that files described in manifest exist, also - * checks that no extra file do exist that are not described in manifest.xml. - * - * @param path directory on disk of the BDOC container. - * @throws Exception exception is thrown if the manifest.xml file parsing failed. - */ -void ASiC_E::parseManifestAndLoadFiles(const ZipSerialize &z) -{ - DEBUG("ASiC_E::readManifest()"); - - try - { - auto manifestdata = z.extract("META-INF/manifest.xml"); - auto doc = XMLDocument::openStream(manifestdata, {"manifest", MANIFEST_NS}); - doc.validateSchema(File::path(Conf::instance()->xsdPath(), "OpenDocument_manifest_v1_2.xsd")); - - set manifestFiles; - bool mimeFound = false; - for(auto file = doc/"file-entry"; file; file++) - { - auto full_path = file[{"full-path", MANIFEST_NS}]; - auto media_type = file[{"media-type", MANIFEST_NS}]; - DEBUG("full_path = '%s', media_type = '%s'", full_path.data(), media_type.data()); - - if(manifestFiles.find(full_path) != manifestFiles.end()) - THROW("Manifest multiple entries defined for file '%s'.", full_path.data()); - - // ODF does not specify that mimetype should be first in manifest - if(full_path == "/") - { - if(mediaType() != media_type) - THROW("Manifest has incorrect container media type defined '%s', expecting '%s'.", media_type.data(), mediaType().c_str()); - mimeFound = true; - continue; - } - if(full_path.back() == '/') // Skip Directory entries - continue; - - manifestFiles.insert(full_path); - if(mediaType() == MIMETYPE_ADOC && - (full_path.compare(0, 9, "META-INF/") == 0 || - full_path.compare(0, 9, "metadata/") == 0)) - d->metadata.push_back(new DataFilePrivate(dataStream(full_path, z), string(full_path), string(media_type))); - else - addDataFilePrivate(dataStream(full_path, z), string(full_path), string(media_type)); - } - if(!mimeFound) - THROW("Manifest is missing mediatype file entry."); - - for(const string &file: z.list()) - { - /** - * http://www.etsi.org/deliver/etsi_ts/102900_102999/102918/01.03.01_60/ts_102918v010301p.pdf - * 6.2.2 Contents of Container - * 3) The root element of each "*signatures*.xml" content shall be either: - */ - if(file.compare(0, 9, "META-INF/") == 0 && - file.find("signatures") != string::npos) - { - try - { - auto data = z.extract(file); - loadSignatures(data, file); - } - catch(const Exception &e) - { - THROW_CAUSE(e, "Failed to parse signature '%s'.", file.c_str()); - } - continue; - } - - if(file == "mimetype" || file.compare(0, 8,"META-INF") == 0) - continue; - if(manifestFiles.count(file) == 0) - THROW("File '%s' found in container is not described in manifest.", file.c_str()); - } - } - catch(const Exception &e) - { - THROW_CAUSE(e, "Failed to parse manifest"); - } - catch(...) - { - THROW("Failed to parse manifest XML: Unknown exception"); - } -} - Signature* ASiC_E::prepareSignature(Signer *signer) { if(mediaType() != MIMETYPE_ASIC_E) diff --git a/src/ASiC_E.h b/src/ASiC_E.h index 88b0d93a1..0e11a8ff8 100644 --- a/src/ASiC_E.h +++ b/src/ASiC_E.h @@ -50,12 +50,10 @@ namespace digidoc static std::unique_ptr openInternal(const std::string &path); private: - ASiC_E(); - ASiC_E(const std::string &path); + ASiC_E(const std::string &path, bool create); DISABLE_COPY(ASiC_E); 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; diff --git a/src/ASiC_S.cpp b/src/ASiC_S.cpp index 28f837a16..7522eb813 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,43 +32,28 @@ 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. */ -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, bool create) + : ASiContainer(path, MIMETYPE_ASIC_S) { - auto z = load(path, false, {mediaType()}); + if(create) + return; + auto z = load(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 +64,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 +74,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."); @@ -131,9 +99,7 @@ unique_ptr ASiC_S::createInternal(const string &path) if(!util::File::fileExtension(path, {"asics", "scs"})) return {}; DEBUG("ASiC_S::createInternal(%s)", path.c_str()); - auto doc = unique_ptr(new ASiC_S()); - doc->zpath(path); - return doc; + return unique_ptr(new ASiC_S(path, true)); } void ASiC_S::addAdESSignature(istream & /*signature*/) @@ -147,20 +113,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, false)); + } + catch(const Exception &) + { + // Ignore the exception: not ASiC/zip document + } + return {}; } Signature* ASiC_S::prepareSignature(Signer * /*signer*/) @@ -170,10 +136,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 +151,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..6353f2086 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,23 +37,17 @@ 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); private: - ASiC_S(); - ASiC_S(const std::string &path); + ASiC_S(const std::string &path, bool create); DISABLE_COPY(ASiC_S); void addDataFileChecks(const std::string &path, const std::string &mediaType) override; 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.cpp b/src/ASiContainer.cpp index a4c1b0ce9..862ed07f9 100644 --- a/src/ASiContainer.cpp +++ b/src/ASiContainer.cpp @@ -41,7 +41,7 @@ constexpr unsigned long MAX_MEM_FILE = 500UL*1024UL*1024UL; class ASiContainer::Private { public: - string mimetype, path; + string path, mimetype; vector documents; vector signatures; map> properties; @@ -50,9 +50,10 @@ class ASiContainer::Private /** * Initialize Container. */ -ASiContainer::ASiContainer(string_view mimetype) +ASiContainer::ASiContainer(string_view path, string_view mimetype) : d(make_unique()) { + d->path = string(path); d->mimetype = string(mimetype); } @@ -80,10 +81,10 @@ XMLDocument ASiContainer::createManifest() const * @param supported supported mimetypes. * @return returns zip serializer for the container. */ -ZipSerialize ASiContainer::load(const string &path, bool mimetypeRequired, const set &supported) +ZipSerialize ASiContainer::load(bool mimetypeRequired, const set &supported) { - DEBUG("ASiContainer::ASiContainer(path = '%s')", path.c_str()); - ZipSerialize z(d->path = path, false); + DEBUG("ASiContainer::ASiContainer(path = '%s')", d->path.c_str()); + ZipSerialize z(d->path, false); vector list = z.list(); // ETSI TS 102 918: mimetype has to be the first in the archive @@ -265,8 +266,8 @@ void ASiContainer::save(const string &path) THROW("Can not save, container is empty."); canSave(); if(!path.empty()) - zpath(path); - ZipSerialize s(zpath(), true); + d->path = path; + ZipSerialize s(d->path, true); s.addFile("mimetype", zproperty("mimetype"), false)(mediaType()); array buf{}; @@ -287,12 +288,7 @@ void ASiContainer::save(const string &path) save(s); } -void ASiContainer::zpath(const string &file) -{ - d->path = file; -} - -string ASiContainer::zpath() const +const string& ASiContainer::path() const { return d->path; } diff --git a/src/ASiContainer.h b/src/ASiContainer.h index cdc0b952a..bb2fc5265 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; @@ -58,9 +59,10 @@ namespace digidoc std::vector signatures() const override; static std::string readMimetype(const ZipSerialize &z); + const std::string& path() const override; protected: - ASiContainer(std::string_view mimetype); + ASiContainer(std::string_view path, 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); @@ -68,12 +70,10 @@ namespace digidoc 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); + ZipSerialize load(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(std::string_view file) const; private: diff --git a/src/Container.cpp b/src/Container.cpp index 515c25d89..e75574767 100644 --- a/src/Container.cpp +++ b/src/Container.cpp @@ -25,7 +25,9 @@ #include "PDF.h" #include "SiVaContainer.h" #include "XmlConf.h" +#include "crypto/Signer.h" #include "crypto/X509CertStore.h" +#include "util/algorithm.h" #include "util/File.h" #include "util/log.h" @@ -36,7 +38,7 @@ #include #include -#include +#include #include #include @@ -304,6 +306,102 @@ unique_ptr Container::createPtr(const std::string &path) return ASiC_E::createInternal(path); } +/** + * Extends Container Validity + * + * @returns Container if new container is created new reference is returned + */ +std::unique_ptr Container::extendContainerValidity(Container &doc, Signer *signer) try +{ + if(doc.signatures().empty()) + THROW("Container does not contain signatures"); + + if(doc.mediaType() == ASiContainer::MIMETYPE_ASIC_S || + doc.mediaType() == ASiContainer::MIMETYPE_ASIC_E) + { + bool extendInPlace = true; + for(Signature *s: doc.signatures()) + { + if(s->profile().find(ASiC_S::ASIC_TST_PROFILE) != string::npos) + break; + if(s->profile().find(ASiC_E::ASIC_TS_PROFILE) == string::npos) + { + extendInPlace = false; + break; + } + + auto signingCert = s->signingCertificate(); + if(auto list = s->ArchiveTimeStamps(); !list.empty() && !list.back().cert.isValid()) + { + extendInPlace = false; + break; + } + else if(list.empty() && (!s->TimeStampCertificate().isValid() || !signingCert.isValid())) + { + extendInPlace = false; + break; + } + else if(!s->OCSPCertificate().isValid()) + { + extendInPlace = false; + break; + } + + try { + s->validate(); + } catch(const Exception &e) { + std::function isInvalid; + isInvalid = [&](const Exception &e) { + for(const Exception &child: e.causes()) + { + switch(child.code()) + { + case Exception::MimeTypeWarning: + return false; + case Exception::CertificateIssuerMissing: + case Exception::CertificateUnknown: + case Exception::OCSPResponderMissing: + case Exception::OCSPCertMissing: + //break; TODO: should we extend? + default: + break; + } + return isInvalid(child); + } + return true; + }; + if(isInvalid(e)) + { + extendInPlace = false; + break; + } + } + } + + if(extendInPlace) + { + for(Signature *s: doc.signatures()) + { + if(contains(s->signingCertificate().qcStatements(), digidoc::X509Cert::QCT_ESEAL)) + continue; + if(s->profile().find(ASiC_E::ASIC_TS_PROFILE) != string::npos) + signer->setProfile(string(ASiC_E::ASIC_TSA_PROFILE)); + else + signer->setProfile(string(ASiC_S::ASIC_TST_PROFILE)); + s->extendSignatureProfile(signer); + } + return {}; + } + } + + signer->setProfile(string(ASiC_S::ASIC_TST_PROFILE)); + auto asics = ASiC_S::createInternal(doc.path() + ".asics"); + asics->addDataFile(doc.path(), doc.mediaType()); + asics->sign(signer); + return asics; +} catch(const Exception &e) { + THROW_CAUSE(e, "Failed to extend signature"); +} /** * @fn digidoc::Container::dataFiles @@ -324,7 +422,7 @@ unsigned int Container::newSignatureId() const { vector list = signatures(); for(unsigned int id = 0; ; ++id) - if(!any_of(list.cbegin(), list.cend(), [id](Signature *s){ return s->id() == Log::format("S%u", id); })) + if(!any_of(list, [id](Signature *s){ return s->id() == Log::format("S%u", id); })) return id; } diff --git a/src/Container.h b/src/Container.h index 92655e978..23fbeb19c 100644 --- a/src/Container.h +++ b/src/Container.h @@ -75,6 +75,10 @@ class DIGIDOCPP_EXPORT Container template static void addContainerImplementation(); + static std::unique_ptr extendContainerValidity(Container &doc, Signer *signer); + + virtual const std::string& path() const = 0; + protected: Container(); unsigned int newSignatureId() const; diff --git a/src/SiVaContainer.cpp b/src/SiVaContainer.cpp index 234c17ae9..bb682fbcc 100644 --- a/src/SiVaContainer.cpp +++ b/src/SiVaContainer.cpp @@ -379,6 +379,11 @@ unique_ptr SiVaContainer::parseDDoc(bool useHashCode) } } +const string& SiVaContainer::path() const +{ + return d->path; +} + Signature* SiVaContainer::prepareSignature(Signer * /*signer*/) { THROW("Not implemented."); diff --git a/src/SiVaContainer.h b/src/SiVaContainer.h index 9624771ff..3d3c2b5fe 100644 --- a/src/SiVaContainer.h +++ b/src/SiVaContainer.h @@ -98,6 +98,8 @@ class SiVaContainer final: public Container void removeSignature(unsigned int id) final; Signature* sign(Signer* signer) final; + const std::string& path() const final; + static std::unique_ptr createInternal(const std::string &path); static std::unique_ptr openInternal(const std::string &path, ContainerOpenCB *cb); diff --git a/src/Signature.cpp b/src/Signature.cpp index 9d7973a02..997fd5279 100644 --- a/src/Signature.cpp +++ b/src/Signature.cpp @@ -230,6 +230,11 @@ X509Cert Signature::ArchiveTimeStampCertificate() const { return X509Cert(); } */ string Signature::ArchiveTimeStampTime() const { return {}; } +/** + * Returns signature Archive TimeStampTokens. + */ +vector Signature::ArchiveTimeStamps() const { return {}; } + struct Signature::Validator::Private { Status result = Valid; diff --git a/src/Signature.h b/src/Signature.h index cd086aff2..d3923685f 100644 --- a/src/Signature.h +++ b/src/Signature.h @@ -21,13 +21,18 @@ #include "Exception.h" -#include -#include +#include "crypto/X509Cert.h" namespace digidoc { class Signer; class X509Cert; + + struct TSAInfo { + X509Cert cert; + std::string time; + }; + class DIGIDOCPP_EXPORT Signature { public: @@ -86,18 +91,18 @@ namespace digidoc virtual std::string countryName() const; virtual std::vector signerRoles() const; - //TM profile properties + // TM profile properties virtual std::string OCSPProducedAt() const; virtual X509Cert OCSPCertificate() const; DIGIDOCPP_DEPRECATED virtual std::vector OCSPNonce() const; - //TS profile properties + // TS profile properties virtual X509Cert TimeStampCertificate() const; virtual std::string TimeStampTime() const; - //TSA profile properties - virtual X509Cert ArchiveTimeStampCertificate() const; - virtual std::string ArchiveTimeStampTime() const; + // TSA profile properties + DIGIDOCPP_DEPRECATED virtual X509Cert ArchiveTimeStampCertificate() const; + DIGIDOCPP_DEPRECATED virtual std::string ArchiveTimeStampTime() const; // Xades properties virtual std::string streetAddress() const; @@ -114,6 +119,9 @@ namespace digidoc // DSig properties virtual void extendSignatureProfile(Signer *signer); + //TSA profile properties + virtual std::vector ArchiveTimeStamps() const; + protected: Signature(); diff --git a/src/SignatureTST.cpp b/src/SignatureTST.cpp index da1a65dcb..de8aeed74 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,10 +85,80 @@ 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; +std::vector SignatureTST::ArchiveTimeStamps() const +{ + std::vector result; + for(auto i = metadata.cbegin() + 1; i != metadata.cend(); ++i) + { + if(i->mime != "application/vnd.etsi.timestamp-token") + continue; + TS ts((const unsigned char*)i->data.data(), i->data.size()); + result.push_back({ts.cert(), util::date::to_string(ts.time())}); + } + return result; +} + +void SignatureTST::extendSignatureProfile(Signer *signer) +{ + + string tstName = "META-INF/timestamp001.tst"; + for(size_t i = 1; + any_of(metadata, [&tstName](const auto &f) { return f.name == tstName; }); + tstName = Log::format("META-INF/timestamp%03zu.tst", ++i)); + + auto doc = XMLDocument::create("ASiCManifest", ASiContainer::ASIC_NS, "asic"); + auto ref = doc + "SigReference"; + ref.setProperty("MimeType", "application/vnd.etsi.timestamp-token"); + ref.setProperty("URI", tstName); + + auto addRef = [&doc](const string &name, string_view mime, bool root, const Digest &digest) { + auto ref = doc + "DataObjectReference"; + ref.setProperty("MimeType", mime); + ref.setProperty("URI", util::File::toUriPath(name)); + if(root) + ref.setProperty("Rootfile", "true"); + auto method = ref + DigestMethod; + method.setNS(method.addNS(DSIG_NS, "ds")); + method.setProperty("Algorithm", digest.uri()); + auto value = ref + DigestValue; + value.setNS(value.addNS(DSIG_NS, "ds")); + value = digest.result(); + }; + + DataFile *file = asicSDoc->dataFiles().front(); + Digest digest; + static_cast(file)->digest(digest); + addRef(file->fileName(), file->mediaType(), false, digest); + for(auto &data: metadata) + { + if(data.name == "META-INF/ASiCArchiveManifest.xml") + { + string mfsName = "META-INF/ASiCArchiveManifest001.xml"; + for(size_t i = 0; + any_of(metadata, [&mfsName](const auto &f) { return f.name == mfsName; }); + mfsName = Log::format("META-INF/ASiCArchiveManifest%03zu.xml", ++i)); + data.name = mfsName; + data.root = true; + } + addRef(data.name, data.mime, data.root, data.digest()); + } + + string data; + doc.save([&data](const char *buf, size_t size) { + data.append(buf, size); + return size; + }, true); + metadata.push_back({"META-INF/ASiCArchiveManifest.xml", "text/xml", std::move(data)}); + vector der = TS(metadata.back().digest(), signer->userAgent()); + metadata.push_back({tstName, "application/vnd.etsi.timestamp-token", {der.cbegin(), der.cend()}}); +} + X509Cert SignatureTST::TimeStampCertificate() const { return timestampToken->cert(); @@ -106,28 +206,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 +261,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 +280,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..d6da41b0a 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(); @@ -50,18 +50,22 @@ class SignatureTST final: public Signature void validate() const final; std::vector dataToSign() const final; void setSignatureValue(const std::vector &signatureValue) final; + void extendSignatureProfile(Signer *signer) final; // Xades properties std::string profile() const final; - std::vector save() const; + //TSA profile properties + std::vector ArchiveTimeStamps() const final; + + 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..f19db6f64 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"); @@ -335,6 +333,9 @@ SignatureXAdES_B::SignatureXAdES_B(const shared_ptr &signatures, XML "AttrAuthoritiesCertValues", "AttributeRevocationValues", "ArchiveTimeStamp"}) if(usp/elem) THROW("%s is not supported", elem); + for(const char *elem: {"CompleteCertificateRefsV2", "AttributeCertificateRefsV2", "SigAndRefsTimeStampV2", "RefsOnlyTimeStampV2"}) + if(usp/XMLName{elem, XADESv141_NS}) + THROW("%s is not supported", elem); for(const char *elem: {"CompleteCertificateRefs", "CompleteRevocationRefs", "SigAndRefsTimeStamp", "TimeStampValidationData"}) if(usp/elem) WARN("%s are not supported", elem); 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/SignatureXAdES_LTA.cpp b/src/SignatureXAdES_LTA.cpp index bfa1128b7..7b1e38b06 100644 --- a/src/SignatureXAdES_LTA.cpp +++ b/src/SignatureXAdES_LTA.cpp @@ -39,7 +39,7 @@ namespace digidoc constexpr XMLName ArchiveTimeStamp {"ArchiveTimeStamp", XADESv141_NS}; } -void SignatureXAdES_LTA::calcArchiveDigest(const Digest &digest, string_view canonicalizationMethod) const +void SignatureXAdES_LTA::calcArchiveDigest(const Digest &digest, string_view canonicalizationMethod, XMLNode ts) const { for(auto ref = signature/"SignedInfo"/"Reference"; ref; ref++) { @@ -64,7 +64,7 @@ void SignatureXAdES_LTA::calcArchiveDigest(const Digest &digest, string_view can if(file == files.cend()) THROW("Filed to find reference URI in container"); - static_cast(*file)->digest(digest); + dynamic_cast(*file)->digest(digest); } for(const auto *name: {"SignedInfo", "SignatureValue", "KeyInfo"}) @@ -75,65 +75,60 @@ void SignatureXAdES_LTA::calcArchiveDigest(const Digest &digest, string_view can DEBUG("Element %s not found", name); } - auto usp = unsignedSignatureProperties(); - for(const auto *name: { - "SignatureTimeStamp", - "CounterSignature", - "CompleteCertificateRefs", - "CompleteRevocationRefs", - "AttributeCertificateRefs", - "AttributeRevocationRefs", - "CertificateValues", - "RevocationValues", - "SigAndRefsTimeStamp", - "RefsOnlyTimeStamp" }) + for(auto elem: unsignedSignatureProperties()) { - if(auto elem = usp/name) - signatures->c14n(digest, canonicalizationMethod, elem); - else - DEBUG("Element %s not found", name); - } - - if(auto elem = usp/XMLName{"TimeStampValidationData", XADESv141_NS}) + if(elem == ts) + break; signatures->c14n(digest, canonicalizationMethod, elem); - else - DEBUG("Element TimeStampValidationData not found"); + } //ds:Object } void SignatureXAdES_LTA::extendSignatureProfile(Signer *signer) { - SignatureXAdES_LT::extendSignatureProfile(signer); + if(SignatureXAdES_LTA::profile().find(ASiC_E::ASIC_TS_PROFILE) == string::npos) + SignatureXAdES_LT::extendSignatureProfile(signer); if(signer->profile() != ASiC_E::ASIC_TSA_PROFILE) return; + + int i = 0; + for(auto ts = unsignedSignatureProperties()/ArchiveTimeStamp; ts; ts++, ++i); + Digest calc; auto method = canonicalizationMethod(); - calcArchiveDigest(calc, method); + calcArchiveDigest(calc, method, {}); TS tsa(calc, signer->userAgent()); auto ts = unsignedSignatureProperties() + ArchiveTimeStamp; ts.setNS(ts.addNS(XADESv141_NS, "xades141")); - ts.setProperty("Id", id() + "-A0"); + ts.setProperty("Id", id() + "-A" + to_string(i)); (ts + CanonicalizationMethod).setProperty("Algorithm", method); ts + EncapsulatedTimeStamp = tsa; } -TS SignatureXAdES_LTA::tsaFromBase64() const +X509Cert SignatureXAdES_LTA::ArchiveTimeStampCertificate() const { - try { - return {unsignedSignatureProperties()/ArchiveTimeStamp/EncapsulatedTimeStamp}; - } catch(const Exception &) {} - return {}; + if(auto list = ArchiveTimeStamps(); !list.empty()) + return list.back().cert; + return X509Cert(); } -X509Cert SignatureXAdES_LTA::ArchiveTimeStampCertificate() const +string SignatureXAdES_LTA::ArchiveTimeStampTime() const { - return tsaFromBase64().cert(); + if(auto list = ArchiveTimeStamps(); !list.empty()) + return list.back().time; + return {}; } -string SignatureXAdES_LTA::ArchiveTimeStampTime() const +vector SignatureXAdES_LTA::ArchiveTimeStamps() const { - return date::to_string(tsaFromBase64().time()); + vector result; + for(auto ts = unsignedSignatureProperties()/ArchiveTimeStamp; ts; ts++) + { + TS t(ts/EncapsulatedTimeStamp); + result.push_back({t.cert(), util::date::to_string(t.time())}); + } + return result; } void SignatureXAdES_LTA::validate(const string &policy) const @@ -157,9 +152,12 @@ void SignatureXAdES_LTA::validate(const string &policy) const auto ts = unsignedSignatureProperties()/ArchiveTimeStamp; if(!ts) THROW("Missing ArchiveTimeStamp element"); - verifyTS(ts, exception, [this](const Digest &digest, string_view canonicalizationMethod) { - calcArchiveDigest(digest, canonicalizationMethod); - }); + for(; ts; ts++) + { + verifyTS(ts, exception, [this, ts](const Digest &digest, string_view canonicalizationMethod) { + calcArchiveDigest(digest, canonicalizationMethod, ts); + }); + } } catch(const Exception &e) { exception.addCause(e); } catch(...) { diff --git a/src/SignatureXAdES_LTA.h b/src/SignatureXAdES_LTA.h index 1c8848cc3..93e827a9a 100644 --- a/src/SignatureXAdES_LTA.h +++ b/src/SignatureXAdES_LTA.h @@ -31,14 +31,14 @@ class SignatureXAdES_LTA final: public SignatureXAdES_LT X509Cert ArchiveTimeStampCertificate() const final; std::string ArchiveTimeStampTime() const final; + std::vector ArchiveTimeStamps() const final; void validate(const std::string &policy) const final; void extendSignatureProfile(Signer *signer) final; private: DISABLE_COPY(SignatureXAdES_LTA); - void calcArchiveDigest(const Digest &digest, std::string_view canonicalizationMethod) const; - TS tsaFromBase64() const; + void calcArchiveDigest(const Digest &digest, std::string_view canonicalizationMethod, XMLNode node) const; }; } diff --git a/src/XMLDocument.h b/src/XMLDocument.h index 33dda5a26..1c3f95ab7 100644 --- a/src/XMLDocument.h +++ b/src/XMLDocument.h @@ -141,6 +141,11 @@ struct XMLElem return bool(d); } + constexpr bool operator==(XMLElem other) const noexcept + { + return d == other.d; + } + constexpr auto& operator++() noexcept { d = d ? find(d->next, d->type) : nullptr; @@ -280,6 +285,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 +400,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 +434,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 +488,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 { diff --git a/src/digidoc-tool.1.cmake b/src/digidoc-tool.1.cmake index edd974c84..6eca6a05b 100644 --- a/src/digidoc-tool.1.cmake +++ b/src/digidoc-tool.1.cmake @@ -69,6 +69,13 @@ Command sign: --dontValidate - Don't validate container on signature creation --userAgent - Additional info info that is sent to TSA or OCSP service +Command extend: + Example: " << executable << " extend --signature=0 demo-container.asice + Available options: + --profile= - signature profile, TS, TSA, time-stamp, time-stamp-archive + --signature= - signature to extend + --dontValidate - Don't validate container on signature creation + All commands: --nocolor - Disable terminal colors --loglevel=[0,1,2,3,4] - Log level 0 - none, 1 - error, 2 - warning, 3 - info, 4 - debug diff --git a/src/digidoc-tool.cpp b/src/digidoc-tool.cpp index de4c08ac5..ac657e92a 100644 --- a/src/digidoc-tool.cpp +++ b/src/digidoc-tool.cpp @@ -272,6 +272,18 @@ struct value: public string_view { } }; +class NullSigner final: public Signer +{ +public: + explicit NullSigner(X509Cert cert): _cert(std::move(cert)) {} + X509Cert cert() const final { return _cert; } + vector sign(const string & /*method*/, const vector & /*digest*/) const final + { + THROW("Not implemented"); + } + X509Cert _cert; +}; + class ToolConfig final: public XmlConfCurrent { public: @@ -390,6 +402,12 @@ static int printUsage(const char *executable) << " --tsurl - option to change TS URL (default " << CONF(TSUrl) << ")" << endl << " --dontValidate - Don't validate container on signature creation" << endl << endl << " --userAgent - Additional info info that is sent to TSA or OCSP service" << endl << endl + << " Command extend:" << endl + << " Example: " << executable << " extend --signature=0 demo-container.asice" << endl + << " Available options:" << endl + << " --profile= - signature profile, TS, TSA, time-stamp, time-stamp-archive" << endl + << " --signature= - signature to extend" << endl + << " --dontValidate - Don't validate container on signature creation" << endl << endl << " All commands:" << endl << " --nocolor - Disable terminal colors" << endl << " --loglevel=[0,1,2,3,4] - Log level 0 - none, 1 - error, 2 - warning, 3 - info, 4 - debug" << endl @@ -476,20 +494,7 @@ unique_ptr ToolConfig::getSigner(bool getwebsigner) const { unique_ptr signer; if(getwebsigner) - { - class WebSigner final: public Signer - { - public: - explicit WebSigner(X509Cert cert): _cert(std::move(cert)) {} - X509Cert cert() const final { return _cert; } - vector sign(const string & /*method*/, const vector & /*digest*/) const final - { - THROW("Not implemented"); - } - X509Cert _cert; - }; - signer = make_unique(X509Cert(cert, X509Cert::Pem)); - } + signer = make_unique(X509Cert(cert, X509Cert::Pem)); #ifdef _WIN32 else if(cng) { @@ -688,15 +693,88 @@ static int open(int argc, char* argv[]) << " OCSP Responder: " << s->OCSPCertificate() << endl << " Message imprint (" << msgImprint.size() << "): " << msgImprint << endl << " TS: " << s->TimeStampCertificate() << endl - << " TS time: " << s->TimeStampTime() << endl - << " TSA: " << s->ArchiveTimeStampCertificate() << endl - << " TSA time: " << s->ArchiveTimeStampTime() << endl; + << " TS time: " << s->TimeStampTime() << endl; + for(const auto &tsaInfo: s->ArchiveTimeStamps()) + { + cout + << " TSA: " << tsaInfo.cert << '\n' + << " TSA time: " << tsaInfo.time << '\n'; + } } if(returnCode == EXIT_SUCCESS && !extractPath.empty()) return extractFiles(); return returnCode; } +/** + * Extend signatures in container. + * + * @param argc number of command line arguments. + * @param argv command line arguments. + * @return EXIT_FAILURE (1) - failure, EXIT_SUCCESS (0) - success + */ +static int extend(int argc, char *argv[]) +{ + vector extendId; + bool dontValidate = false; + value path; + NullSigner signer{X509Cert()}; + for(int i = 2; i < argc; i++) + { + string_view arg(argv[i]); + if(value v{arg, "--profile="}) + signer.setProfile(string(v)); + else if(value v{arg, "--signature="}) + extendId.push_back(unsigned(atoi(v.data()))); + else if(arg == "--dontValidate") + dontValidate = true; + else + path = arg; + } + + if(path.empty()) + return printUsage(argv[0]); + + unique_ptr doc; + try { + doc = Container::openPtr(path); + } catch(const Exception &e) { + cout << "Failed to parse container" << endl; + cout << " Exception:" << endl << e; + return EXIT_FAILURE; + } + + auto signatures = doc->signatures(); + if(signatures.empty()) + { + cout << " Container does not contain signatures\n"; + return EXIT_SUCCESS; + } + + for(unsigned int i : extendId) + { + if(i >= signatures.size()) + THROW("Incorrect signature id %u, there are only %zu signatures in container.", i, signatures.size()); + cout << " Extending signature " << i << " to " << signer.profile() << endl; + signatures[i]->extendSignatureProfile(&signer); + if(!dontValidate) + validateSignature(signatures[i]); + } + + if(extendId.empty()) + { + cout << " Extending " << signatures.size() << " signature(s)\n"; + if(auto wrapped = Container::extendContainerValidity(*doc, &signer)) + { + doc = std::move(wrapped); + cout << " Wrapped to new container\n"; + } + } + + doc->save(); + return EXIT_SUCCESS; +} + /** * Remove items from container. * @@ -1050,6 +1128,8 @@ int main(int argc, char *argv[]) try return remove(argc, argv); if(command == "sign") return sign(*conf, argv[0]); + if(command == "extend") + return extend(argc, argv); if(command == "websign") return websign(*conf, argv[0]); if(command == "tsl") diff --git a/test/libdigidocpp_boost.cpp b/test/libdigidocpp_boost.cpp index 2adeccd16..fcb1f3967 100644 --- a/test/libdigidocpp_boost.cpp +++ b/test/libdigidocpp_boost.cpp @@ -354,8 +354,11 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(signature, Doc, DocTypes) // TSA signature signer2.setProfile("time-stamp-archive"); BOOST_CHECK_NO_THROW(s3 = d->sign(&signer2)); - //BOOST_CHECK_EQUAL(s3->TSCertificate(), signer2.cert()); - //BOOST_CHECK_NO_THROW(s3->validate()); + BOOST_CHECK_EQUAL(s3->signingCertificate(), signer2.cert()); + BOOST_CHECK_NO_THROW(s3->validate()); + // Extend TSA + BOOST_CHECK_NO_THROW(s3->extendSignatureProfile(&signer2)); + BOOST_CHECK_NO_THROW(s3->validate()); BOOST_CHECK_NO_THROW(d->save(Doc::EXT + "-TSA.tmp")); BOOST_CHECK_NO_THROW(d->removeSignature(1U)); BOOST_CHECK_EQUAL(d->signatures().size(), 1U);