From 2dcc2c7b85058060fcecf01e5339a31a34b38d2b Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 30 Sep 2024 15:34:14 +0300 Subject: [PATCH 1/4] ASiC-S TimeStamp creation support IB-8181 Signed-off-by: Raul Metsma --- src/ASiC_S.cpp | 31 ++++++++++++++++++++++--------- src/ASiC_S.h | 1 + src/SignatureTST.cpp | 10 ++++++++++ src/SignatureTST.h | 1 + src/crypto/Signer.cpp | 8 +++++--- src/digidoc-tool.1.cmake | 2 +- src/digidoc-tool.cpp | 2 +- 7 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/ASiC_S.cpp b/src/ASiC_S.cpp index ac64f9be3..95ddfa93e 100644 --- a/src/ASiC_S.cpp +++ b/src/ASiC_S.cpp @@ -21,10 +21,11 @@ #include "SignatureTST.h" #include "SignatureXAdES_LTA.h" +#include "crypto/Signer.h" +#include "util/algorithm.h" #include "util/File.h" #include "util/log.h" -#include #include using namespace digidoc; @@ -45,10 +46,6 @@ ASiC_S::ASiC_S(const string &path) : ASiContainer(MIMETYPE_ASIC_S) { auto z = load(path, false, {mediaType()}); - auto starts_with = [](string_view str, string_view needle) constexpr { - return str.size() >= needle.size() && str.compare(0, needle.size(), needle) == 0; - }; - for(const string &file: z.list()) { if(file == "mimetype") @@ -87,9 +84,21 @@ ASiC_S::ASiC_S(const string &path) THROW("ASiC-S container does not contain any signatures."); } -unique_ptr ASiC_S::createInternal(const string & /*path*/) +void ASiC_S::addDataFileChecks(const string &fileName, const string &mediaType) +{ + ASiContainer::addDataFileChecks(fileName, mediaType); + if(!dataFiles().empty()) + THROW("Can not add document to ASiC-S container which already contains a document."); +} + +unique_ptr ASiC_S::createInternal(const string &path) { - return {}; + 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; } void ASiC_S::addAdESSignature(istream & /*signature*/) @@ -124,9 +133,13 @@ void ASiC_S::save(const ZipSerialize &s) s.addFile("META-INF/timestamp.tst", zproperty("META-INF/timestamp.tst"))(static_cast(list.front())->save()); } -Signature *ASiC_S::sign(Signer * /*signer*/) +Signature *ASiC_S::sign(Signer *signer) { - THROW("Not implemented."); + if(signer->profile() != ASIC_TST_PROFILE) + THROW("ASiC-S container supports only TimeStampToken signing."); + if(!signatures().empty()) + THROW("ASiC-S container supports only one TimeStampToken signature."); + return addSignature(make_unique(this, signer)); } /** diff --git a/src/ASiC_S.h b/src/ASiC_S.h index f6eba5a20..af32d5673 100644 --- a/src/ASiC_S.h +++ b/src/ASiC_S.h @@ -45,6 +45,7 @@ namespace digidoc ASiC_S(const std::string &path); DISABLE_COPY(ASiC_S); + void addDataFileChecks(const std::string &path, const std::string &mediaType) override; void canSave() final; void save(const ZipSerialize &s) final; diff --git a/src/SignatureTST.cpp b/src/SignatureTST.cpp index 75299a426..105ce8259 100644 --- a/src/SignatureTST.cpp +++ b/src/SignatureTST.cpp @@ -22,6 +22,7 @@ #include "ASiC_S.h" #include "DataFile_p.h" #include "crypto/Digest.h" +#include "crypto/Signer.h" #include "crypto/TS.h" #include "crypto/X509Cert.h" #include "util/DateTime.h" @@ -35,6 +36,15 @@ SignatureTST::SignatureTST(const string &data, ASiC_S *asicSDoc) , timestampToken(make_unique((const unsigned char*)data.data(), data.size())) {} +SignatureTST::SignatureTST(ASiC_S *asicSDoc, Signer *signer) + : asicSDoc(asicSDoc) +{ + auto *dataFile = static_cast(asicSDoc->dataFiles().front()); + Digest digest; + dataFile->digest(digest); + timestampToken = make_unique(digest, signer->userAgent()); +} + SignatureTST::~SignatureTST() = default; X509Cert SignatureTST::TimeStampCertificate() const diff --git a/src/SignatureTST.h b/src/SignatureTST.h index b70940207..a3cea7f49 100644 --- a/src/SignatureTST.h +++ b/src/SignatureTST.h @@ -32,6 +32,7 @@ class SignatureTST final: public Signature { public: SignatureTST(const std::string &data, ASiC_S *asicSDoc); + SignatureTST(ASiC_S *asicSDoc, Signer *signer); ~SignatureTST(); std::vector messageImprint() const override; diff --git a/src/crypto/Signer.cpp b/src/crypto/Signer.cpp index c95974ccf..b36a0e4c5 100644 --- a/src/crypto/Signer.cpp +++ b/src/crypto/Signer.cpp @@ -20,6 +20,7 @@ #include "Signer.h" #include "ASiC_E.h" +#include "ASiC_S.h" #include "Conf.h" #include "crypto/Digest.h" #include "crypto/X509Cert.h" @@ -38,7 +39,7 @@ class Signer::Private { public: optional method; - string profile = "time-stamp"; + string profile{ASiC_E::ASIC_TS_PROFILE}; string userAgent; string city, streetAddress, stateOrProvince, postalCode, countryName; vector signerRoles; @@ -181,9 +182,10 @@ void Signer::setProfile(const string &profile) {"TSA", ASiC_E::ASIC_TSA_PROFILE}, {ASiC_E::ASIC_TS_PROFILE, ASiC_E::ASIC_TS_PROFILE}, {ASiC_E::ASIC_TSA_PROFILE, ASiC_E::ASIC_TSA_PROFILE}, + {ASiC_S::ASIC_TST_PROFILE, ASiC_S::ASIC_TST_PROFILE}, + {"time-stamp-token", ASiC_S::ASIC_TST_PROFILE} }; - if(auto it = std::find_if(profiles.cbegin(), profiles.cend(), [&profile](const auto &elem) { return elem.first == profile; }); - it != profiles.cend()) + if(auto it = profiles.find(profile); it != profiles.cend()) d->profile = it->second; else THROW("Unsupported profile: %s", profile.c_str()); diff --git a/src/digidoc-tool.1.cmake b/src/digidoc-tool.1.cmake index 94c9356fe..edd974c84 100644 --- a/src/digidoc-tool.1.cmake +++ b/src/digidoc-tool.1.cmake @@ -49,7 +49,7 @@ Command websign: Command sign: Example: digidoc-tool sign demo-container.asice Available options: - --profile= - signature profile, TS, TSA, time-stamp, time-stamp-archive + --profile= - signature profile, TS, TSA, time-stamp, time-stamp-archive, TimeStampToken, time-stamp-token --XAdESEN - use XAdES EN profile --city= - city of production place --street= - streetAddress of production place in XAdES EN profile diff --git a/src/digidoc-tool.cpp b/src/digidoc-tool.cpp index d0e6fd299..23ea5fc28 100644 --- a/src/digidoc-tool.cpp +++ b/src/digidoc-tool.cpp @@ -350,7 +350,7 @@ static int printUsage(const char *executable) << " Command sign:" << endl << " Example: " << executable << " sign demo-container.asice" << endl << " Available options:" << endl - << " --profile= - signature profile, TS, TSA, time-stamp, time-stamp-archive" << endl + << " --profile= - signature profile, TS, TSA, time-stamp, time-stamp-archive, TimeStampToken, time-stamp-token" << endl << " --XAdESEN - use XAdES EN profile" << endl << " --city= - city of production place" << endl << " --street= - streetAddress of production place in XAdES EN profile" << endl From 89543504d5c8fc17f51d7c0c4b691e86f37f5060 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 3 Sep 2024 11:07:16 +0300 Subject: [PATCH 2/4] Extend XAdES LTA signatures IB-7995 Signed-off-by: Raul Metsma --- examples/DigiDocCSharp/Program.cs | 5 + .../ee/ria/libdigidocpp/libdigidocpp.java | 4 + libdigidocpp.i | 28 ++- src/Signature.cpp | 5 + src/Signature.h | 22 ++- src/SignatureXAdES_B.cpp | 3 + src/SignatureXAdES_LTA.cpp | 74 ++++---- src/SignatureXAdES_LTA.h | 4 +- src/XMLDocument.h | 5 + src/digidoc-tool.1.cmake | 7 + src/digidoc-tool.cpp | 174 ++++++++++++------ test/libdigidocpp_boost.cpp | 7 +- 12 files changed, 231 insertions(+), 107 deletions(-) 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 fe3a5733b..9b0ef73b3 100644 --- a/libdigidocpp.i +++ b/libdigidocpp.i @@ -96,15 +96,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)) { @@ -114,7 +116,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(this, cPtr$csinput);" ) std::vector "handleRef$csinput" @@ -132,6 +134,14 @@ static std::vector* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je $modulePINVOKE.ByteVector_free(cPtr); return new System.Security.Cryptography.X509Certificates.X509Certificate2(der); } +%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); %} @@ -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; %} @@ -205,6 +219,9 @@ static std::vector* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je %newobject digidoc::Container::open; %newobject digidoc::Container::create; +%immutable digidoc::TSAInfo::cert; +%immutable digidoc::TSAInfo::time; + %feature("director") digidoc::ContainerOpenCB; %typemap(javacode) digidoc::Conf %{ @@ -262,6 +279,7 @@ def transfer(self): %template(StringMap) std::map; %template(DataFiles) std::vector; %template(Signatures) std::vector; +%template(TSAInfos) std::vector; %extend digidoc::Container { static digidoc::Container* open(const std::string &path, digidoc::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/SignatureXAdES_B.cpp b/src/SignatureXAdES_B.cpp index 02f473b7d..9577107f4 100644 --- a/src/SignatureXAdES_B.cpp +++ b/src/SignatureXAdES_B.cpp @@ -344,6 +344,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_LTA.cpp b/src/SignatureXAdES_LTA.cpp index a79c5700d..46e855f56 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); } 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 138d36768..05e0f8a4f 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; 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 23ea5fc28..1690d7a2f 100644 --- a/src/digidoc-tool.cpp +++ b/src/digidoc-tool.cpp @@ -252,6 +252,18 @@ string ConsolePinSigner::pin(const X509Cert &certificate) const return result; } +struct value: string_view { + constexpr value(string_view arg, string_view param) noexcept + : string_view(arg.size() > param.size() && arg.compare(0, param.size(), param) == 0 ? + arg.substr(param.size()) : string_view{}) + {} + + constexpr operator bool() const noexcept + { + return !empty(); + } +}; + class ToolConfig final: public XmlConfCurrent { public: @@ -374,6 +386,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 @@ -391,40 +409,39 @@ ToolConfig::ToolConfig(int argc, char *argv[]) for(int i = 2; i < argc; i++) { string_view arg(argv[i]); - if(arg.find("--profile=") == 0) - profile = arg.substr(10); - else if(arg.find("--file=") == 0) + if(value v{arg, "--profile="}) profile = v; + else if(value v{arg, "--file="}) { - string_view arg2(i+1 < argc ? argv[i+1] : string_view()); - files.emplace(arg.substr(7), - arg2.find("--mime=") == 0 ? toUTF8(arg2.substr(7)) : "application/octet-stream"); + value mime(i+1 < argc ? argv[i+1] : string_view(), "--mime="); + files.emplace(v, + mime ? toUTF8(mime) : "application/octet-stream"); } #ifdef _WIN32 else if(arg == "--cng") cng = true; else if(arg == "--selectFirst") selectFirst = true; - else if(arg.find("--thumbprint=") == 0) thumbprint = File::hexToBin(arg.substr(arg.find('=') + 1)); + else if(value v{arg, "--thumbprint="}) thumbprint = File::hexToBin(v); #endif else if(arg.find("--pkcs11") == 0) { cng = false; - if(arg.find('=') != string::npos) - pkcs11 = toUTF8(arg.substr(arg.find('=') + 1)); + if(value v{arg, "--pkcs11="}) + pkcs11 = toUTF8(v); } - else if(arg.find("--pkcs12=") == 0) + else if(value v{arg, "--pkcs12="}) { cng = false; - pkcs12 = toUTF8(arg.substr(9)); + pkcs12 = toUTF8(v); } else if(arg == "--dontValidate") dontValidate = true; else if(arg == "--XAdESEN") XAdESEN = true; - else if(arg.find("--pin=") == 0) pin = arg.substr(6); - else if(arg.find("--cert=") == 0) cert = toUTF8(arg.substr(7)); - else if(arg.find("--city=") == 0) city = toUTF8(arg.substr(7)); - else if(arg.find("--street=") == 0) street = toUTF8(arg.substr(9)); - else if(arg.find("--state=") == 0) state = toUTF8(arg.substr(8)); - else if(arg.find("--postalCode=") == 0) postalCode = toUTF8(arg.substr(13)); - else if(arg.find("--country=") == 0) country = toUTF8(arg.substr(10)); - else if(arg.find("--role=") == 0) roles.push_back(toUTF8(arg.substr(7))); + else if(value v{arg, "--pin="}) pin = v; + else if(value v{arg, "--cert="}) cert = toUTF8(v); + else if(value v{arg, "--city="}) city = toUTF8(v); + else if(value v{arg, "--street="}) street = toUTF8(v); + else if(value v{arg, "--state="}) state = toUTF8(v); + else if(value v{arg, "--postalCode="}) postalCode = toUTF8(v); + else if(value v{arg, "--country="}) country = toUTF8(v); + else if(value v{arg, "--role="}) roles.push_back(toUTF8(v)); else if(arg == "--sha224") uri = URI_SHA224; else if(arg == "--sha256") uri = URI_SHA256; else if(arg == "--sha384") uri = URI_SHA384; @@ -439,14 +456,14 @@ ToolConfig::ToolConfig(int argc, char *argv[]) else if(arg == "--sigpsssha512") { siguri = URI_SHA512; rsaPss = true; } else if(arg == "--rsapkcs15") rsaPss = false; else if(arg == "--rsapss") rsaPss = true; - else if(arg.find("--tsurl") == 0) tsurl = arg.substr(8); - else if(arg.find("--tslurl=") == 0) tslurl = arg.substr(9); - else if(arg.find("--tslcert=") == 0) tslcerts = vector{ X509Cert(toUTF8(arg.substr(10))) }; + else if(value v{arg, "--tsurl="}) tsurl = v; + else if(value v{arg, "--tslurl="}) tslurl = v; + else if(value v{arg, "--tslcert="}) tslcerts = vector{ X509Cert(toUTF8(v)) }; else if(arg == "--TSLAllowExpired") expired = true; else if(arg == "--dontsign") doSign = false; else if(arg == "--nocolor") RED = GREEN = YELLOW = RESET = {}; - else if(arg.find("--loglevel=") == 0) _logLevel = atoi(arg.substr(11).data()); - else if(arg.find("--logfile=") == 0) _logFile = toUTF8(arg.substr(10)); + else if(value v{arg, "--loglevel="}) _logLevel = atoi(v.data()); + else if(value v{arg, "--logfile="}) _logFile = toUTF8(v); else path = toUTF8(arg); } } @@ -478,7 +495,7 @@ unique_ptr ToolConfig::getSigner(bool getwebsigner) const #ifdef _WIN32 else if(cng) { - unique_ptr win = make_unique(pin, selectFirst); + auto win = make_unique(pin, selectFirst); win->setThumbprint(thumbprint); signer = std::move(win); } @@ -497,6 +514,12 @@ unique_ptr ToolConfig::getSigner(bool getwebsigner) const return signer; } +/** + * Validate signature. + * + * @param signature Signature to validated + * @return EXIT_FAILURE (1) - failure, EXIT_SUCCESS (0) - success + */ static int validateSignature(const Signature *s, ToolConfig::Warning warning = ToolConfig::WWarning) { int returnCode = EXIT_SUCCESS; @@ -557,20 +580,20 @@ static int open(int argc, char* argv[]) // Parse command line arguments. for(int i = 2; i < argc; i++) { - string arg(ToolConfig::toUTF8(argv[i])); + string_view arg(argv[i]); if(arg == "--list") continue; - if(arg.find("--warnings=") == 0) + if(value v{arg, "--warnings="}) { - if(arg.substr(11, 6) == "ignore") reportwarnings = ToolConfig::WIgnore; - if(arg.substr(11, 5) == "error") reportwarnings = ToolConfig::WError; + if(v == "ignore") reportwarnings = ToolConfig::WIgnore; + if(v == "error") reportwarnings = ToolConfig::WError; } else if(arg.find("--extractAll") == 0) { extractPath = fs::current_path(); if(auto pos = arg.find('='); pos != string::npos) { - fs::path newPath = fs::u8path(arg.substr(pos + 1)); + fs::path newPath = fs::path(arg.substr(pos + 1)); extractPath = newPath.is_relative() ? extractPath / newPath : std::move(newPath); } if(!fs::is_directory(extractPath)) @@ -578,10 +601,10 @@ static int open(int argc, char* argv[]) } else if(arg == "--validateOnExtract") validateOnExtract = true; - else if(arg.find("--offline") == 0) + else if(arg == "--offline") cb.online = false; else - path = std::move(arg); + path = ToolConfig::toUTF8(arg); } if(path.empty()) @@ -670,15 +693,69 @@ 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 signatures; + bool dontValidate = false; + string path, profile; + for(int i = 2; i < argc; i++) + { + string_view arg(argv[i]); + if(value v{arg, "--profile="}) + profile = v; + else if(value v{arg, "--signature="}) + signatures.push_back(unsigned(atoi(v.data()))); + else if(arg == "--dontValidate") + dontValidate = true; + else + path = ToolConfig::toUTF8(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; + } + + for(unsigned int i : signatures) + { + cout << " Extending signature " << i << " to " << profile << endl; + Signature *s = doc->signatures().at(i); + s->extendSignatureProfile(profile); + if(!dontValidate) + validateSignature(s); + } + + doc->save(); + return EXIT_SUCCESS; +} + /** * Remove items from container. * @@ -692,13 +769,13 @@ static int remove(int argc, char *argv[]) string path; for(int i = 2; i < argc; i++) { - string arg(ToolConfig::toUTF8(argv[i])); - if(arg.find("--document=") == 0) - documents.push_back(unsigned(stoi(arg.substr(11)))); - else if(arg.find("--signature=") == 0) - signatures.push_back(unsigned(stoi(arg.substr(12)))); + string_view arg(argv[i]); + if(value v{arg, "--document="}) + documents.push_back(unsigned(atoi(v.data()))); + else if(value v{arg, "--signature="}) + signatures.push_back(unsigned(atoi(v.data()))); else - path = std::move(arg); + path = ToolConfig::toUTF8(arg); } if(path.empty()) @@ -770,18 +847,7 @@ static int add(const ToolConfig &p, const char *program) static int signContainer(Container *doc, const unique_ptr &signer, bool dontValidate = false) { if(Signature *signature = doc->sign(signer.get())) - { - if(dontValidate) - return EXIT_SUCCESS; - try { - signature->validate(); - cout << " Validation: " << ToolConfig::GREEN << "OK" << ToolConfig::RESET << endl; - return EXIT_SUCCESS; - } catch(const Exception &e) { - cout << " Validation: " << ToolConfig::RED << "FAILED" << ToolConfig::RESET << endl; - cout << " Exception:" << endl << e; - } - } + return dontValidate ? EXIT_SUCCESS : validateSignature(signature); return EXIT_FAILURE; } @@ -1032,6 +1098,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); From 8f34452117bf80a5cbe6c9197c3ac70a68f28d0a Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 16 Dec 2024 13:53:39 +0200 Subject: [PATCH 3/4] Validate ASiC-S manifest containers IB-8180 Signed-off-by: Raul Metsma --- src/ASiC_S.cpp | 19 ++++++-- src/ASiC_S.h | 1 + src/ASiContainer.h | 1 + src/SignatureTST.cpp | 98 ++++++++++++++++++++++++++++++++++++---- src/SignatureTST.h | 10 +++- src/SignatureXAdES_B.cpp | 2 - src/SignatureXAdES_B.h | 2 - src/XMLDocument.h | 61 ++++++++++++++++++------- src/crypto/TSL.cpp | 2 - 9 files changed, 158 insertions(+), 38 deletions(-) diff --git a/src/ASiC_S.cpp b/src/ASiC_S.cpp index 95ddfa93e..d27099466 100644 --- a/src/ASiC_S.cpp +++ b/src/ASiC_S.cpp @@ -46,15 +46,20 @@ ASiC_S::ASiC_S(const string &path) : ASiContainer(MIMETYPE_ASIC_S) { auto z = load(path, false, {mediaType()}); + bool foundManifest = false; + 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."); - addSignature(make_unique(z.extract(file).str(), this)); + addSignature(make_unique(true, z, this)); + foundManifest = true; } else if(file == "META-INF/signatures.xml") { @@ -65,8 +70,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") - THROW("ASiCArchiveManifest are not supported."); else if(starts_with(file, "META-INF/")) continue; else if(const auto directory = File::directory(file); @@ -77,6 +80,12 @@ ASiC_S::ASiC_S(const string &path) else addDataFile(dataStream(file, z), file, "application/octet-stream"); } + if(!foundManifest && 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."); @@ -129,8 +138,8 @@ void ASiC_S::save(const ZipSerialize &s) { if(zproperty("META-INF/manifest.xml").size && !createManifest().save(s.addFile("META-INF/manifest.xml", zproperty("META-INF/manifest.xml")), true)) THROW("Failed to create manifest XML"); - if(auto list = signatures(); !list.empty()) - s.addFile("META-INF/timestamp.tst", zproperty("META-INF/timestamp.tst"))(static_cast(list.front())->save()); + for(Signature *sig: signatures()) + static_cast(sig)->save(s); } Signature *ASiC_S::sign(Signer *signer) diff --git a/src/ASiC_S.h b/src/ASiC_S.h index af32d5673..845e61e1a 100644 --- a/src/ASiC_S.h +++ b/src/ASiC_S.h @@ -50,5 +50,6 @@ namespace digidoc void save(const ZipSerialize &s) final; static bool isContainerSimpleFormat(const std::string &path); + 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 105ce8259..120f1b17c 100644 --- a/src/SignatureTST.cpp +++ b/src/SignatureTST.cpp @@ -20,21 +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/DateTime.h" +#include "util/File.h" #include "util/log.h" +#include +#include + using namespace digidoc; using namespace std; -SignatureTST::SignatureTST(const string &data, ASiC_S *asicSDoc) +struct SignatureTST::Data { + std::string name, mime, data; + bool root = false; + + Digest digest(Digest digest = {}) const + { + digest.update((const unsigned char*)data.data(), data.size()); + return digest; + } +}; + +SignatureTST::SignatureTST(bool manifest, const ZipSerialize &z, ASiC_S *asicSDoc) : asicSDoc(asicSDoc) - , timestampToken(make_unique((const unsigned char*)data.data(), data.size())) -{} +{ + 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) : asicSDoc(asicSDoc) @@ -43,10 +84,25 @@ 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; +} + X509Cert SignatureTST::TimeStampCertificate() const { return timestampToken->cert(); @@ -94,15 +150,40 @@ 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); } + for(const auto &manifest: metadata) + { + if(manifest.mime != "text/xml") + continue; + istringstream is(manifest.data); + XMLDocument doc = XMLDocument::openStream(is, {"ASiCManifest", ASiContainer::ASIC_NS}); + for(auto ref = doc/"DataObjectReference"; ref; ref++) + { + string_view method = (ref/DigestMethod)["Algorithm"]; + auto uri = 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()); + } + } } catch (const Exception& e) { @@ -134,7 +215,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 a3cea7f49..7047d04da 100644 --- a/src/SignatureTST.h +++ b/src/SignatureTST.h @@ -27,11 +27,12 @@ namespace digidoc { class ASiC_S; class TS; +class ZipSerialize; class SignatureTST final: public Signature { public: - SignatureTST(const std::string &data, ASiC_S *asicSDoc); + SignatureTST(bool manifest, const ZipSerialize &z, ASiC_S *asicSDoc); SignatureTST(ASiC_S *asicSDoc, Signer *signer); ~SignatureTST(); @@ -53,12 +54,17 @@ class SignatureTST final: public Signature // 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::unique_ptr timestampToken; + struct Data; + std::vector metadata; }; } diff --git a/src/SignatureXAdES_B.cpp b/src/SignatureXAdES_B.cpp index 9577107f4..4fe1e1bf8 100644 --- a/src/SignatureXAdES_B.cpp +++ b/src/SignatureXAdES_B.cpp @@ -87,8 +87,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}; diff --git a/src/SignatureXAdES_B.h b/src/SignatureXAdES_B.h index 67605d1a0..2a5cb538b 100644 --- a/src/SignatureXAdES_B.h +++ b/src/SignatureXAdES_B.h @@ -29,8 +29,6 @@ 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 05e0f8a4f..102132d9c 100644 --- a/src/XMLDocument.h +++ b/src/XMLDocument.h @@ -285,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"}; @@ -399,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 { @@ -449,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 { @@ -473,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 61f371fb6..5e5b10494 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 { From a99f64d22ca8a67ef2e425fb8d65ba4872cd6c3e Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 16 Dec 2024 15:45:52 +0200 Subject: [PATCH 4/4] ASiC-S LTA extending support IB-8182 Signed-off-by: Raul Metsma --- src/SignatureTST.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++ src/SignatureTST.h | 1 + 2 files changed, 57 insertions(+) diff --git a/src/SignatureTST.cpp b/src/SignatureTST.cpp index 120f1b17c..6532b6978 100644 --- a/src/SignatureTST.cpp +++ b/src/SignatureTST.cpp @@ -27,6 +27,7 @@ #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" @@ -103,6 +104,61 @@ std::vector SignatureTST::ArchiveTimeStamps() const 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(); diff --git a/src/SignatureTST.h b/src/SignatureTST.h index 7047d04da..d6da41b0a 100644 --- a/src/SignatureTST.h +++ b/src/SignatureTST.h @@ -50,6 +50,7 @@ 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;