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);