From 747f416832a9114808e2351b4c47e8ec486d8551 Mon Sep 17 00:00:00 2001 From: Raul Metsma <raul@metsma.ee> Date: Tue, 3 Sep 2024 11:07:16 +0300 Subject: [PATCH] Extend XAdES LTA signatures IB-7994, IB-7995 Signed-off-by: Raul Metsma <raul@metsma.ee> --- examples/DigiDocCSharp/Program.cs | 5 + .../ee/ria/libdigidocpp/libdigidocpp.java | 4 + libdigidocpp.i | 28 ++- src/Signature.cpp | 19 +- src/Signature.h | 22 ++- src/SignatureXAdES_B.cpp | 3 + src/SignatureXAdES_LTA.cpp | 72 +++----- src/SignatureXAdES_LTA.h | 6 +- src/XMLDocument.h | 5 + src/digidoc-tool.1.cmake | 7 + src/digidoc-tool.cpp | 174 ++++++++++++------ test/libdigidocpp_boost.cpp | 7 +- 12 files changed, 235 insertions(+), 117 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<unsigned char>* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je %{ $1 = SWIG_JavaArrayToVectorUnsignedChar(jenv, $input); %} %typemap(out, fragment="SWIG_VectorUnsignedCharToJavaArray") std::vector<unsigned char>, digidoc::X509Cert %{ $result = SWIG_VectorUnsignedCharToJavaArray(jenv, $1); %} -%typemap(jtype) std::vector<unsigned char>, digidoc::X509Cert "byte[]" +%typemap(out, fragment="SWIG_VectorUnsignedCharToJavaArray") digidoc::X509Cert * +%{ $result = SWIG_VectorUnsignedCharToJavaArray(jenv, *$1); %} +%typemap(jtype) std::vector<unsigned char>, digidoc::X509Cert, digidoc::X509Cert * "byte[]" %typemap(jstype) std::vector<unsigned char> "byte[]" -%typemap(jstype) digidoc::X509Cert "java.security.cert.X509Certificate" -%typemap(jni) std::vector<unsigned char>, digidoc::X509Cert "jbyteArray" +%typemap(jstype) digidoc::X509Cert, digidoc::X509Cert* "java.security.cert.X509Certificate" +%typemap(jni) std::vector<unsigned char>, digidoc::X509Cert, digidoc::X509Cert * "jbyteArray" %typemap(javain) std::vector<unsigned char>, digidoc::X509Cert "$javainput" %typemap(javaout) std::vector<unsigned char> { 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<unsigned char>* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je #elif defined(SWIGCSHARP) %typemap(cstype) std::vector<unsigned char> "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<unsigned char> "handleRef$csinput" @@ -132,6 +134,14 @@ static std::vector<unsigned char>* 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<unsigned char> %{ $result = new std::vector<unsigned char>(std::move($1)); %} %typemap(out) digidoc::X509Cert %{ $result = new std::vector<unsigned char>($1); %} @@ -154,6 +164,10 @@ static std::vector<unsigned char>* SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *je std::vector<unsigned char> temp = $1; $result = PyBytes_FromStringAndSize((const char*)temp.data(), temp.size()); } +%typemap(out) digidoc::X509Cert * { + std::vector<unsigned char> temp = *$1; + $result = PyBytes_FromStringAndSize((const char*)temp.data(), temp.size()); +} #endif %typemap(freearg) std::vector<unsigned char> %{ delete $1; %} @@ -205,6 +219,9 @@ static std::vector<unsigned char>* 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<std::string,std::string>; %template(DataFiles) std::vector<digidoc::DataFile*>; %template(Signatures) std::vector<digidoc::Signature*>; +%template(TSAInfos) std::vector<digidoc::TSAInfo>; %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..10d52bdaa 100644 --- a/src/Signature.cpp +++ b/src/Signature.cpp @@ -223,12 +223,27 @@ string Signature::TimeStampTime() const { return {}; } /** * Returns signature Archive TimeStampToken certificate. */ -X509Cert Signature::ArchiveTimeStampCertificate() const { return X509Cert(); } +X509Cert Signature::ArchiveTimeStampCertificate() const +{ + if(auto list = ArchiveTimeStamps(); !list.empty()) + return list.back().cert; + return X509Cert(); +} /** * Returns signature Archive TimeStampToken time. */ -string Signature::ArchiveTimeStampTime() const { return {}; } +string Signature::ArchiveTimeStampTime() const +{ + if(auto list = ArchiveTimeStamps(); !list.empty()) + return list.back().time; + return {}; +} + +/** + * Returns signature Archive TimeStampTokens. + */ +vector<TSAInfo> Signature::ArchiveTimeStamps() const { return {}; } struct Signature::Validator::Private { 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 <string> -#include <vector> +#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<std::string> signerRoles() const; - //TM profile properties + // TM profile properties virtual std::string OCSPProducedAt() const; virtual X509Cert OCSPCertificate() const; DIGIDOCPP_DEPRECATED virtual std::vector<unsigned char> 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<TSAInfo> 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> &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..c687a0df7 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<const DataFilePrivate*>(*file)->digest(digest); + dynamic_cast<const DataFilePrivate*>(*file)->digest(digest); } for(const auto *name: {"SignedInfo", "SignatureValue", "KeyInfo"}) @@ -75,65 +75,46 @@ 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 -{ - try { - return {unsignedSignatureProperties()/ArchiveTimeStamp/EncapsulatedTimeStamp}; - } catch(const Exception &) {} - return {}; -} - -X509Cert SignatureXAdES_LTA::ArchiveTimeStampCertificate() const -{ - return tsaFromBase64().cert(); -} - -string SignatureXAdES_LTA::ArchiveTimeStampTime() const +vector<TSAInfo> SignatureXAdES_LTA::ArchiveTimeStamps() const { - return date::to_string(tsaFromBase64().time()); + vector<TSAInfo> 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 +138,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..34c5b5202 100644 --- a/src/SignatureXAdES_LTA.h +++ b/src/SignatureXAdES_LTA.h @@ -29,16 +29,14 @@ class SignatureXAdES_LTA final: public SignatureXAdES_LT public: using SignatureXAdES_LT::SignatureXAdES_LT; - X509Cert ArchiveTimeStampCertificate() const final; - std::string ArchiveTimeStampTime() const final; + std::vector<TSAInfo> 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 94c9356fe..fa3bced30 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 16f05b6e0..5882fe7c6 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>{ 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>{ 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<Signer> ToolConfig::getSigner(bool getwebsigner) const #ifdef _WIN32 else if(cng) { - unique_ptr<WinSigner> win = make_unique<WinSigner>(pin, selectFirst); + auto win = make_unique<WinSigner>(pin, selectFirst); win->setThumbprint(thumbprint); signer = std::move(win); } @@ -497,6 +514,12 @@ unique_ptr<Signer> 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<unsigned int> 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<Container> 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> &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);