diff --git a/examples/DigiDocCSharp/DigiDocCSharp.csproj b/examples/DigiDocCSharp/DigiDocCSharp.csproj index 8fb78a012..e92bcdf38 100644 --- a/examples/DigiDocCSharp/DigiDocCSharp.csproj +++ b/examples/DigiDocCSharp/DigiDocCSharp.csproj @@ -3,8 +3,8 @@ net472 Exe true - 0.4.0.0 - 0.4.0.0 + 0.5.0.0 + 0.5.0.0 Copyright © 2015 diff --git a/examples/DigiDocCSharp/Program.cs b/examples/DigiDocCSharp/Program.cs index ed3accb6b..4da521af5 100644 --- a/examples/DigiDocCSharp/Program.cs +++ b/examples/DigiDocCSharp/Program.cs @@ -154,8 +154,7 @@ private static void Websign(string[] args) b.addDataFile(args[i], "application/octet-stream"); } - X509Certificate cert = new X509Certificate(); - cert.Import(args[args.Length - 2]); + var cert = new X509Certificate(args[args.Length - 2]); Signature c = b.prepareWebSignature(cert.Export(X509ContentType.Cert), "time-stamp"); Console.WriteLine("Signature method: " + c.signatureMethod()); Console.WriteLine("Digest to sign: " + BitConverter.ToString(c.dataToSign()).Replace("-", string.Empty)); @@ -207,7 +206,13 @@ private static void Verify(string file) Console.WriteLine(); Console.WriteLine("Time: " + s.trustedSigningTime()); - Console.WriteLine("Cert: " + new X509Certificate2(s.signingCertificateDer()).Subject); + 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 bbe59f2bc..b78820468 100644 --- a/examples/java/src/main/java/ee/ria/libdigidocpp/libdigidocpp.java +++ b/examples/java/src/main/java/ee/ria/libdigidocpp/libdigidocpp.java @@ -149,7 +149,12 @@ static void verify(String file) { System.out.println(); System.out.println("Time: " + signature.trustedSigningTime()); - System.out.println("Cert: " + toX509(signature.signingCertificateDer()).getSubjectDN().toString()); + 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 { @@ -171,7 +176,7 @@ static void verify(String file) { } static void version() { - System.out.println("DigiDocJAVA 0.3 libdigidocpp " + digidoc.version()); + System.out.println("DigiDocJAVA 0.4 libdigidocpp " + digidoc.version()); } static X509Certificate toX509(byte[] der) throws CertificateException { diff --git a/libdigidocpp.i b/libdigidocpp.i index f64ba3ee0..d609ed911 100644 --- a/libdigidocpp.i +++ b/libdigidocpp.i @@ -60,6 +60,9 @@ extern "C" SWIGEXPORT int SWIGSTDCALL ByteVector_size(void *ptr) { return static_cast*>(ptr)->size(); } + SWIGEXPORT void SWIGSTDCALL ByteVector_free(void *ptr) { + delete static_cast*>(ptr); + } SWIGEXPORT void* SWIGSTDCALL ByteVector_to(unsigned char *data, int size) { return new std::vector(data, data + size); } @@ -72,61 +75,11 @@ extern "C" public static extern global::System.IntPtr ByteVector_data(global::System.IntPtr data); [global::System.Runtime.InteropServices.DllImport("$dllimport", EntryPoint="ByteVector_size")] public static extern int ByteVector_size(global::System.IntPtr data); + [global::System.Runtime.InteropServices.DllImport("$dllimport", EntryPoint="ByteVector_free")] + public static extern void ByteVector_free(global::System.IntPtr data); [global::System.Runtime.InteropServices.DllImport("$dllimport", EntryPoint="ByteVector_to")] public static extern global::System.IntPtr ByteVector_to( - [global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPArray)]byte[] data, int size); - - public class UTF8Marshaler : global::System.Runtime.InteropServices.ICustomMarshaler { - static UTF8Marshaler static_instance = new UTF8Marshaler(); - - public global::System.IntPtr MarshalManagedToNative(object managedObj) { - if (managedObj == null) - return global::System.IntPtr.Zero; - if (!(managedObj is string)) - throw new global::System.Runtime.InteropServices.MarshalDirectiveException( - "UTF8Marshaler must be used on a string."); - - // not null terminated - byte[] strbuf = global::System.Text.Encoding.UTF8.GetBytes((string)managedObj); - global::System.IntPtr buffer = global::System.Runtime.InteropServices.Marshal.AllocHGlobal(strbuf.Length + 1); - global::System.Runtime.InteropServices.Marshal.Copy(strbuf, 0, buffer, strbuf.Length); - - // write the terminating null - global::System.Runtime.InteropServices.Marshal.WriteByte(buffer + strbuf.Length, 0); - return buffer; - } - - public unsafe object MarshalNativeToManaged(global::System.IntPtr pNativeData) { - byte* walk = (byte*)pNativeData; - - // find the end of the string - while (*walk != 0) { - walk++; - } - int length = (int)(walk - (byte*)pNativeData); - - // should not be null terminated - byte[] strbuf = new byte[length]; - // skip the trailing null - global::System.Runtime.InteropServices.Marshal.Copy((global::System.IntPtr)pNativeData, strbuf, 0, length); - return global::System.Text.Encoding.UTF8.GetString(strbuf); - } - - public void CleanUpNativeData(global::System.IntPtr pNativeData) { - global::System.Runtime.InteropServices.Marshal.FreeHGlobal(pNativeData); - } - - public void CleanUpManagedData(object managedObj) { - } - - public int GetNativeDataSize() { - return -1; - } - - public static global::System.Runtime.InteropServices.ICustomMarshaler GetInstance(string cookie) { - return static_instance; - } - } + [global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPArray)]byte[] data, int size); %} #ifdef SWIGJAVA @@ -138,45 +91,95 @@ extern "C" jenv->ReleaseByteArrayElements($input, $input_ptr, JNI_ABORT); %} %typemap(out) std::vector %{ - jresult = jenv->NewByteArray((&result)->size()); - jenv->SetByteArrayRegion(jresult, 0, (&result)->size(), (const jbyte*)(&result)->data()); + $result = jenv->NewByteArray((&result)->size()); + jenv->SetByteArrayRegion($result, 0, (&result)->size(), (const jbyte*)(&result)->data()); %} -%typemap(jtype) std::vector "byte[]" +%typemap(out) digidoc::X509Cert %{ + std::vector temp = $1; + $result = jenv->NewByteArray(temp.size()); + jenv->SetByteArrayRegion($result, 0, temp.size(), (const jbyte*)temp.data()); +%} +%typemap(out) digidoc::X509Cert * %{ + std::vector temp = *$1; + $result = jenv->NewByteArray(temp.size()); + jenv->SetByteArrayRegion($result, 0, temp.size(), (const jbyte*)temp.data()); +%} +%typemap(jtype) std::vector, digidoc::X509Cert, digidoc::X509Cert * "byte[]" %typemap(jstype) std::vector "byte[]" -%typemap(jni) std::vector "jbyteArray" -%typemap(javain) std::vector "$javainput" +%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, 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)) { + return (java.security.cert.X509Certificate) cf.generateCertificate(is); + } + } + #elif defined(SWIGCSHARP) %typemap(cstype) std::vector "byte[]" -%typemap(csin, - pre= " global::System.IntPtr cPtr$csinput = digidocPINVOKE.ByteVector_to($csinput, $csinput.Length); - global::System.Runtime.InteropServices.HandleRef handleRef$csinput = new global::System.Runtime.InteropServices.HandleRef(this, cPtr$csinput);" +%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" +%typemap(freearg) std::vector +%{ delete $1; %} %typemap(csout, excode=SWIGEXCODE) std::vector { - global::System.IntPtr data = $imcall;$excode - byte[] result = new byte[$modulePINVOKE.ByteVector_size(data)]; - global::System.Runtime.InteropServices.Marshal.Copy($modulePINVOKE.ByteVector_data(data), result, 0, result.Length); - return result; -} -#elif defined(SWIGPYTHON) - %typemap(in) std::vector %{ - if (PyBytes_Check($input)) { - const char *data = PyBytes_AsString($input); - $1 = new std::vector(data, data + PyBytes_Size($input)); - } else if (PyString_Check($input)) { - const char *data = PyString_AsString($input); - $1 = new std::vector(data, data + PyString_Size($input)); - } else { - PyErr_SetString(PyExc_TypeError, "not a bytes"); - SWIG_fail; + global::System.IntPtr cPtr = $imcall;$excode + byte[] result = new byte[$modulePINVOKE.ByteVector_size(cPtr)]; + global::System.Runtime.InteropServices.Marshal.Copy($modulePINVOKE.ByteVector_data(cPtr), result, 0, result.Length); + $modulePINVOKE.ByteVector_free(cPtr); + return result; + } +%typemap(csout, excode=SWIGEXCODE) digidoc::X509Cert { + 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); + var result = new System.Security.Cryptography.X509Certificates.X509Certificate2(der); + $modulePINVOKE.ByteVector_free(cPtr); + return result; } - %} +%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); + var result = new System.Security.Cryptography.X509Certificates.X509Certificate2(der); + $modulePINVOKE.ByteVector_free(cPtr); + return result; + } %} +%typemap(out) std::vector %{ $result = new std::vector(std::move($1)); %} +%typemap(out) digidoc::X509Cert %{ $result = new std::vector($1); %} + +#elif defined(SWIGPYTHON) +%typemap(in) std::vector %{ + if (PyBytes_Check($input)) { + const char *data = PyBytes_AsString($input); + $1 = new std::vector(data, data + PyBytes_Size($input)); + } else if (PyString_Check($input)) { + const char *data = PyString_AsString($input); + $1 = new std::vector(data, data + PyString_Size($input)); + } else { + PyErr_SetString(PyExc_TypeError, "not a bytes"); + SWIG_fail; + } +%} %typemap(out) std::vector %{ $result = PyBytes_FromStringAndSize((const char*)(&result)->data(), (&result)->size()); %} %typemap(freearg) std::vector %{ delete $1; %} +%typemap(out) digidoc::X509Cert { + 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 %apply std::vector { std::vector const & }; @@ -208,11 +211,6 @@ extern "C" %ignore digidoc::ConfV2::verifyServiceCert; %ignore digidoc::ConfV4::verifyServiceCerts; %ignore digidoc::ConfV5::TSCerts; -%ignore digidoc::Signer::cert; -%ignore digidoc::Signature::signingCertificate; -%ignore digidoc::Signature::OCSPCertificate; -%ignore digidoc::Signature::TimeStampCertificate; -%ignore digidoc::Signature::ArchiveTimeStampCertificate; // hide stream methods, swig cannot generate usable wrappers %ignore digidoc::DataFile::saveAs(std::ostream &os) const; %ignore digidoc::Container::addAdESSignature(std::istream &signature); @@ -231,6 +229,9 @@ extern "C" %newobject digidoc::Container::open; %newobject digidoc::Container::create; +%immutable digidoc::TSAInfo::cert; +%immutable digidoc::TSAInfo::time; + %feature("director") digidoc::ContainerOpenCB; %typemap(javacode) digidoc::Conf %{ @@ -260,15 +261,10 @@ def transfer(self): %include "std_vector.i" %include "std_map.i" #ifdef SWIGCSHARP -namespace std { - %typemap(imtype, - inattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]", - outattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]") - string "string" - %typemap(imtype, - inattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]", - outattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]") const string & "string" -} +%typemap(imtype, + inattributes="[global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]", + outattributes="[return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.LPUTF8Str)]") + std::string, const std::string & "string" #endif // Handle DigiDoc Export declarations @@ -293,32 +289,8 @@ namespace std { %template(StringMap) std::map; %template(DataFiles) std::vector; %template(Signatures) std::vector; +%template(TSAInfos) std::vector; -// override X509Cert methods to return byte array -%extend digidoc::Signer { - std::vector cert() const - { - return $self->cert(); - } -} -%extend digidoc::Signature { - std::vector signingCertificateDer() const - { - return $self->signingCertificate(); - } - std::vector OCSPCertificateDer() const - { - return $self->OCSPCertificate(); - } - std::vector TimeStampCertificateDer() const - { - return $self->TimeStampCertificate(); - } - std::vector ArchiveTimeStampCertificateDer() const - { - return $self->ArchiveTimeStampCertificate(); - } -} %extend digidoc::Container { static digidoc::Container* open(const std::string &path, digidoc::ContainerOpenCB *cb) { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81b15fd88..6c510ddeb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -202,7 +202,7 @@ if(SWIG_FOUND) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/digidoc.py DESTINATION ${Python3_SITELIB}) endif() - set(CMAKE_SWIG_FLAGS -dllimport digidoc_csharp -namespace digidoc) + set(CMAKE_SWIG_FLAGS -namespace digidoc) set(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}/csharp) swig_add_library(digidoc_csharp LANGUAGE csharp SOURCES ../libdigidocpp.i) target_compile_definitions(digidoc_csharp PRIVATE TARGET_NAME="$") diff --git a/src/DataFile.cpp b/src/DataFile.cpp index d363f50e7..f4ba156bf 100644 --- a/src/DataFile.cpp +++ b/src/DataFile.cpp @@ -98,9 +98,8 @@ DataFilePrivate::DataFilePrivate(unique_ptr &&is, string filename, stri m_size = pos < 0 ? 0 : (unsigned long)pos; } -vector DataFilePrivate::calcDigest(const string &method) const +void DataFilePrivate::digest(const Digest &digest) const { - Digest digest(method); array buf{}; m_is->clear(); m_is->seekg(0); @@ -110,7 +109,13 @@ vector DataFilePrivate::calcDigest(const string &method) const if(m_is->gcount() > 0) digest.update(buf.data(), size_t(m_is->gcount())); } - return digest.result(); +} + +std::vector DataFilePrivate::calcDigest(const string &method) const +{ + Digest d(method); + digest(d); + return d.result(); } void DataFilePrivate::saveAs(const string& path) const diff --git a/src/DataFile_p.h b/src/DataFile_p.h index 2e197794b..07fb29955 100644 --- a/src/DataFile_p.h +++ b/src/DataFile_p.h @@ -27,6 +27,7 @@ namespace digidoc { +class Digest; class DataFilePrivate final: public DataFile { public: @@ -37,6 +38,7 @@ class DataFilePrivate final: public DataFile unsigned long fileSize() const final { return m_size; } std::string mediaType() const final { return m_mediatype; } + void digest(const Digest &method) const; std::vector calcDigest(const std::string &method) const final; void saveAs(std::ostream &os) const final; void saveAs(const std::string& path) const final; diff --git a/src/SiVaContainer.cpp b/src/SiVaContainer.cpp index 0d4fc96a1..bbb0e69ba 100644 --- a/src/SiVaContainer.cpp +++ b/src/SiVaContainer.cpp @@ -362,7 +362,7 @@ unique_ptr SiVaContainer::parseDDoc(bool useHashCode) if(!useHashCode) continue; Digest calc(URI_SHA1); - doc.c14n(&calc, XMLDocument::C14D_ID_1_0, dataFile); + doc.c14n(calc, XMLDocument::C14D_ID_1_0, dataFile); dataFile.setProperty("ContentType", "HASHCODE"); dataFile.setProperty("DigestType", "sha1"); dataFile.setProperty("DigestValue", to_base64(calc.result())); diff --git a/src/Signature.cpp b/src/Signature.cpp index abe30ed2f..1b13ad5d1 100644 --- a/src/Signature.cpp +++ b/src/Signature.cpp @@ -214,6 +214,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 c078e1e04..58e74e2d9 100644 --- a/src/Signature.h +++ b/src/Signature.h @@ -21,12 +21,17 @@ #include "Exception.h" -#include -#include +#include "crypto/X509Cert.h" namespace digidoc { class X509Cert; + + struct TSAInfo { + X509Cert cert; + std::string time; + }; + class DIGIDOCPP_EXPORT Signature { public: @@ -85,18 +90,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; @@ -110,6 +115,9 @@ namespace digidoc // Other virtual std::vector messageImprint() const; + //TSA profile properties + virtual std::vector ArchiveTimeStamps() const; + protected: Signature(); diff --git a/src/SignatureXAdES_B.cpp b/src/SignatureXAdES_B.cpp index 157456074..b06953401 100644 --- a/src/SignatureXAdES_B.cpp +++ b/src/SignatureXAdES_B.cpp @@ -284,7 +284,7 @@ SignatureXAdES_B::SignatureXAdES_B(unsigned int id, ASiContainer *container, Sig } Digest calc(digestMethod); - signatures->c14n(&calc, canonMethod, signedProperties); + signatures->c14n(calc, canonMethod, signedProperties); addReference("#" + nr + "-SignedProperties", calc.uri(), calc.result(), REF_TYPE, canonMethod); } @@ -343,6 +343,9 @@ SignatureXAdES_B::SignatureXAdES_B(const std::shared_ptr &signatures "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); @@ -566,7 +569,7 @@ vector SignatureXAdES_B::dataToSign() const { Digest calc(signatureMethod()); auto signedInfo = signature/"SignedInfo"; - signatures->c14n(&calc, (signedInfo/CanonicalizationMethod)["Algorithm"], signedInfo); + signatures->c14n(calc, (signedInfo/CanonicalizationMethod)["Algorithm"], signedInfo); return calc.result(); } diff --git a/src/SignatureXAdES_LTA.cpp b/src/SignatureXAdES_LTA.cpp index 965dd0e03..92ec3ad03 100644 --- a/src/SignatureXAdES_LTA.cpp +++ b/src/SignatureXAdES_LTA.cpp @@ -29,7 +29,6 @@ #include "util/File.h" #include -#include using namespace digidoc; using namespace digidoc::util; @@ -40,8 +39,7 @@ namespace digidoc constexpr XMLName ArchiveTimeStamp {"ArchiveTimeStamp", XADESv141_NS}; } -void SignatureXAdES_LTA::calcArchiveDigest(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++) { @@ -66,16 +64,7 @@ void SignatureXAdES_LTA::calcArchiveDigest(Digest *digest, if(file == files.cend()) THROW("Filed to find reference URI in container"); - std::istream *is = static_cast(*file)->m_is.get(); - array buf{}; - is->clear(); - is->seekg(0); - while(*is) - { - is->read((char*)buf.data(), streamsize(buf.size())); - if(is->gcount() > 0) - digest->update(buf.data(), size_t(is->gcount())); - } + dynamic_cast(*file)->digest(digest); } for(const auto *name: {"SignedInfo", "SignatureValue", "KeyInfo"}) @@ -86,65 +75,60 @@ void SignatureXAdES_LTA::calcArchiveDigest(Digest *digest, 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(const string &profile) { - SignatureXAdES_LT::extendSignatureProfile(profile); + if(SignatureXAdES_LTA::profile().find(ASiC_E::ASIC_TS_PROFILE) == string::npos) + SignatureXAdES_LT::extendSignatureProfile(profile); if(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(CONF(TSUrl), calc); 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 @@ -168,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](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 9972a39da..c73d183f9 100644 --- a/src/SignatureXAdES_LTA.h +++ b/src/SignatureXAdES_LTA.h @@ -31,15 +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(const std::string &profile) final; private: DISABLE_COPY(SignatureXAdES_LTA); - void calcArchiveDigest(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/SignatureXAdES_T.cpp b/src/SignatureXAdES_T.cpp index 39ae1ef19..5dfea49d0 100644 --- a/src/SignatureXAdES_T.cpp +++ b/src/SignatureXAdES_T.cpp @@ -70,7 +70,7 @@ void SignatureXAdES_T::extendSignatureProfile(const std::string &profile) Digest calc; auto method = canonicalizationMethod(); - signatures->c14n(&calc, method, signatureValue()); + signatures->c14n(calc, method, signatureValue()); TS tsa(CONF(TSUrl), calc); auto ts = usp + "SignatureTimeStamp"; @@ -109,7 +109,7 @@ void SignatureXAdES_T::validate(const std::string &policy) const if(ts + 1) THROW("More than one SignatureTimeStamp is not supported"); - TS tsa = verifyTS(ts, exception, [this](Digest *digest, string_view canonicalizationMethod) { + TS tsa = verifyTS(ts, exception, [this](const Digest &digest, string_view canonicalizationMethod) { signatures->c14n(digest, canonicalizationMethod, signatureValue()); }); @@ -163,7 +163,7 @@ void SignatureXAdES_T::validate(const std::string &policy) const for(auto sigAndRefsTS = usp/"SigAndRefsTimeStamp"; sigAndRefsTS; sigAndRefsTS++) { - verifyTS(sigAndRefsTS, exception, [this, usp](Digest *digest, string_view canonicalizationMethod) { + verifyTS(sigAndRefsTS, exception, [this, usp](const Digest &digest, string_view canonicalizationMethod) { signatures->c14n(digest, canonicalizationMethod, signatureValue()); for(const auto *name: { "SignatureTimeStamp", @@ -195,7 +195,7 @@ XMLNode SignatureXAdES_T::unsignedSignatureProperties() const } TS SignatureXAdES_T::verifyTS(XMLNode timestamp, digidoc::Exception &exception, - std::function &&calcDigest) + std::function &&calcDigest) { auto ets = timestamp/EncapsulatedTimeStamp; if(!ets) @@ -205,7 +205,7 @@ TS SignatureXAdES_T::verifyTS(XMLNode timestamp, digidoc::Exception &exception, TS ts(ets); Digest calc(ts.digestMethod()); - calcDigest(&calc, (timestamp/CanonicalizationMethod)["Algorithm"]); + calcDigest(calc, (timestamp/CanonicalizationMethod)["Algorithm"]); ts.verify(calc.result()); if(ts.digestMethod() == URI_SHA1 && diff --git a/src/SignatureXAdES_T.h b/src/SignatureXAdES_T.h index 5d618097a..a0cdcb833 100644 --- a/src/SignatureXAdES_T.h +++ b/src/SignatureXAdES_T.h @@ -47,7 +47,7 @@ class SignatureXAdES_T: public SignatureXAdES_B TS TimeStamp() const; static TS verifyTS(XMLNode timestamp, Exception &exception, - std::function &&calcDigest); + std::function &&calcDigest); private: DISABLE_COPY(SignatureXAdES_T); diff --git a/src/XMLDocument.h b/src/XMLDocument.h index cdd03f4b4..1ea44485e 100644 --- a/src/XMLDocument.h +++ b/src/XMLDocument.h @@ -159,6 +159,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; @@ -355,7 +360,7 @@ struct XMLDocument: public unique_xml_t, public XMLNode return doc; } - void c14n(Digest *digest, std::string_view algo, XMLNode node) + void c14n(const Digest &digest, std::string_view algo, XMLNode node) { xmlC14NMode mode = XML_C14N_1_0; int with_comments = 0; @@ -383,7 +388,7 @@ struct XMLDocument: public unique_xml_t, public XMLNode auto *digest = static_cast(context); digest->update(pcxmlChar(buffer), size_t(len)); return len; - }, nullptr, digest, nullptr), xmlOutputBufferClose); + }, nullptr, const_cast(&digest), nullptr), xmlOutputBufferClose); int size = xmlC14NExecute(get(), [](void *root, xmlNodePtr node, xmlNodePtr parent) constexpr noexcept { if(root == node) return 1; diff --git a/src/crypto/Digest.cpp b/src/crypto/Digest.cpp index 312da8338..b65638e22 100644 --- a/src/crypto/Digest.cpp +++ b/src/crypto/Digest.cpp @@ -44,11 +44,6 @@ Digest::Digest(string_view uri) THROW_OPENSSLEXCEPTION("Failed to initialize %.*s digest calculator", int(uri.size()), uri.data()); } -/** - * Destroys OpenSSL digest calculator. - */ -Digest::~Digest() = default; - vector Digest::addDigestInfo(vector digest, string_view uri) { switch(toMethod(uri)) @@ -224,7 +219,7 @@ std::string Digest::toUri(int nid) * @throws Exception throws exception if update failed. * @see result() */ -void Digest::update(const unsigned char *data, size_t length) +void Digest::update(const unsigned char *data, size_t length) const { if(!data) THROW("Can not update digest value from NULL pointer."); diff --git a/src/crypto/Digest.h b/src/crypto/Digest.h index 876b84844..05d8917ea 100644 --- a/src/crypto/Digest.h +++ b/src/crypto/Digest.h @@ -71,8 +71,7 @@ namespace digidoc { public: Digest(std::string_view uri = {}); - ~Digest(); - void update(const unsigned char *data, size_t length); + void update(const unsigned char *data, size_t length) const; std::vector result(const std::vector &data); std::vector result() const; std::string uri() const; @@ -88,7 +87,6 @@ namespace digidoc static std::string digestInfoUri(const std::vector &digest); private: - DISABLE_COPY(Digest); std::unique_ptr d; }; diff --git a/src/digidoc-tool.1.cmake b/src/digidoc-tool.1.cmake index 857a9e18d..aab8f2280 100644 --- a/src/digidoc-tool.1.cmake +++ b/src/digidoc-tool.1.cmake @@ -68,6 +68,13 @@ Command sign: --tsurl - option to change TS URL (default http://demo.sk.ee/tsa) --dontValidate - Don't validate container on signature creation +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 731830079..3c0585554 100644 --- a/src/digidoc-tool.cpp +++ b/src/digidoc-tool.cpp @@ -297,6 +297,19 @@ class ToolConfig final: public XmlConfCurrent static string_view RED, GREEN, YELLOW, RESET; }; +struct value: std::string_view { + constexpr value(std::string_view arg, std::string_view param) noexcept + : std::string_view(arg.size() > param.size() && arg.compare(0, param.size(), param) == 0 ? + arg.substr(param.size()) : std::string_view{}) + {} + + constexpr operator bool() const noexcept + { + return !empty(); + } +}; + + /** @@ -367,6 +380,12 @@ static int printUsage(const char *executable) << " --rsapss - Use RSA PSS padding" << endl << " --tsurl - option to change TS URL (default " << CONF(TSUrl) << ")" << endl << " --dontValidate - Don't validate container on signature creation" << 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 @@ -384,40 +403,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; @@ -432,14 +450,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); } } @@ -471,7 +489,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); } @@ -489,6 +507,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; @@ -549,20 +573,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)) @@ -570,12 +594,12 @@ static int open(int argc, char* argv[]) } else if(arg == "--validateOnExtract") validateOnExtract = true; - else if(arg.find("--policy=") == 0) - policy = arg.substr(9); - else if(arg.find("--offline") == 0) + else if(value v{arg, "--policy="}) + policy = v; + else if(arg == "--offline") cb.online = false; else - path = std::move(arg); + path = ToolConfig::toUTF8(arg); } if(path.empty()) @@ -664,15 +688,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. * @@ -686,13 +764,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()) @@ -764,18 +842,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; } @@ -1027,6 +1094,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 13da2fc84..8d6284c28 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.profile())); + 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);