Skip to content

Commit

Permalink
Validate ASiC-S manifest containers
Browse files Browse the repository at this point in the history
IB-8180

Signed-off-by: Raul Metsma <[email protected]>
  • Loading branch information
metsma committed Dec 16, 2024
1 parent 2f868de commit 24eeef4
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 38 deletions.
19 changes: 14 additions & 5 deletions src/ASiC_S.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,20 @@ ASiC_S::ASiC_S(const string &path)
: ASiContainer(MIMETYPE_ASIC_S)
{
auto z = load(path, false, {mediaType()});
bool foundManifest = false;
bool foundTimestamp = false;
for(const string &file: z.list())
{
if(file == "mimetype")
continue;
if(file == "META-INF/timestamp.tst")
foundTimestamp = true;
if(file == "META-INF/ASiCArchiveManifest.xml")
{
if(!signatures().empty())
THROW("Can not add signature to ASiC-S container which already contains a signature.");
addSignature(make_unique<SignatureTST>(z.extract<stringstream>(file).str(), this));
addSignature(make_unique<SignatureTST>(true, z, this));
foundManifest = true;
}
else if(file == "META-INF/signatures.xml")
{
Expand All @@ -65,8 +70,6 @@ ASiC_S::ASiC_S(const string &path)
for(auto s = signatures->signature(); s; s++)
addSignature(make_unique<SignatureXAdES_LTA>(signatures, s, this));
}
else if(file == "META-INF/ASiCArchiveManifest.xml")
THROW("ASiCArchiveManifest are not supported.");
else if(starts_with(file, "META-INF/"))
continue;
else if(const auto directory = File::directory(file);
Expand All @@ -77,6 +80,12 @@ ASiC_S::ASiC_S(const string &path)
else
addDataFile(dataStream(file, z), file, "application/octet-stream");
}
if(!foundManifest && foundTimestamp)
{
if(!signatures().empty())
THROW("Can not add signature to ASiC-S container which already contains a signature.");
addSignature(make_unique<SignatureTST>(false, z, this));
}

if(dataFiles().empty())
THROW("ASiC-S container does not contain any data objects.");
Expand Down Expand Up @@ -129,8 +138,8 @@ void ASiC_S::save(const ZipSerialize &s)
{
if(zproperty("META-INF/manifest.xml").size && !createManifest().save(s.addFile("META-INF/manifest.xml", zproperty("META-INF/manifest.xml")), true))
THROW("Failed to create manifest XML");
if(auto list = signatures(); !list.empty())
s.addFile("META-INF/timestamp.tst", zproperty("META-INF/timestamp.tst"))(static_cast<SignatureTST*>(list.front())->save());
for(Signature *sig: signatures())
static_cast<SignatureTST*>(sig)->save(s);
}

Signature *ASiC_S::sign(Signer *signer)
Expand Down
1 change: 1 addition & 0 deletions src/ASiC_S.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ namespace digidoc
void save(const ZipSerialize &s) final;

static bool isContainerSimpleFormat(const std::string &path);
friend class SignatureTST;
};
}
1 change: 1 addition & 0 deletions src/ASiContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace digidoc
//https://signa.mitsoft.lt/static/signa-web/webResources/docs/ADOC_specification_approved20090907_EN.pdf
static constexpr std::string_view MIMETYPE_ADOC = "application/vnd.lt.archyvai.adoc-2008";
static constexpr std::string_view MANIFEST_NS = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0";
static constexpr std::string_view ASIC_NS = "http://uri.etsi.org/02918/v1.2.1#";

~ASiContainer() override;
std::string mediaType() const override;
Expand Down
98 changes: 90 additions & 8 deletions src/SignatureTST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,62 @@
#include "SignatureTST.h"

#include "ASiC_S.h"
#include "Conf.h"
#include "DataFile_p.h"
#include "XMLDocument.h"
#include "crypto/Digest.h"
#include "crypto/Signer.h"
#include "crypto/TS.h"
#include "crypto/X509Cert.h"
#include "util/DateTime.h"
#include "util/File.h"
#include "util/log.h"

#include <functional>
#include <sstream>

using namespace digidoc;
using namespace std;

SignatureTST::SignatureTST(const string &data, ASiC_S *asicSDoc)
struct SignatureTST::Data {
std::string name, mime, data;
bool root = false;

Digest digest(Digest digest = {}) const
{
digest.update((const unsigned char*)data.data(), data.size());
return digest;
}
};

SignatureTST::SignatureTST(bool manifest, const ZipSerialize &z, ASiC_S *asicSDoc)
: asicSDoc(asicSDoc)
, timestampToken(make_unique<TS>((const unsigned char*)data.data(), data.size()))
{}
{
auto data = z.extract<stringstream>("META-INF/timestamp.tst").str();
timestampToken = make_unique<TS>((const unsigned char*)data.data(), data.size());
metadata.push_back({"META-INF/timestamp.tst", "application/vnd.etsi.timestamp-token", std::move(data)});
if(!manifest)
return;
XMLSchema schema(util::File::path(Conf::instance()->xsdPath(), "en_31916201v010101.xsd"));
function<void(const string &, string_view)> add = [this, &schema, &add, &z](const string &file, string_view mime) {
auto xml = z.extract<stringstream>(file);
XMLDocument doc = XMLDocument::openStream(xml, {"ASiCManifest", ASiContainer::ASIC_NS});
schema.validate(doc);

for(auto ref = doc/"DataObjectReference"; ref; ref++)
{
if(ref["Rootfile"] == "true")
add(util::File::fromUriPath(ref["URI"]), ref["MimeType"]);
}

auto ref = doc/"SigReference";
string uri = util::File::fromUriPath(ref["URI"]);
string tst = z.extract<stringstream>(uri).str();
metadata.push_back({file, string(mime), xml.str()});
metadata.push_back({uri, string(ref["MimeType"]), std::move(tst)});
};
add("META-INF/ASiCArchiveManifest.xml", "text/xml");
}

SignatureTST::SignatureTST(ASiC_S *asicSDoc, Signer *signer)
: asicSDoc(asicSDoc)
Expand All @@ -43,10 +84,25 @@ SignatureTST::SignatureTST(ASiC_S *asicSDoc, Signer *signer)
Digest digest;
dataFile->digest(digest);
timestampToken = make_unique<TS>(digest, signer->userAgent());
vector<unsigned char> der = *timestampToken;
metadata.push_back({"META-INF/timestamp.tst", "application/vnd.etsi.timestamp-token", {der.cbegin(), der.cend()}});
}

SignatureTST::~SignatureTST() = default;

std::vector<TSAInfo> SignatureTST::ArchiveTimeStamps() const
{
std::vector<TSAInfo> result;
for(auto i = metadata.cbegin() + 1; i != metadata.cend(); ++i)
{
if(i->mime != "application/vnd.etsi.timestamp-token")
continue;
TS ts((const unsigned char*)i->data.data(), i->data.size());
result.push_back({ts.cert(), util::date::to_string(ts.time())});
}
return result;
}

X509Cert SignatureTST::TimeStampCertificate() const
{
return timestampToken->cert();
Expand Down Expand Up @@ -94,15 +150,40 @@ void SignatureTST::validate() const
}
try
{
timestampToken->verify(dataToSign());
if(auto digestMethod = signatureMethod();
!Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) &&
auto digestMethod = signatureMethod();
DataFile *file = asicSDoc->dataFiles().front();
timestampToken->verify(file->calcDigest(digestMethod));
if(!Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) &&
Digest::isWeakDigest(digestMethod))
{
Exception e(EXCEPTION_PARAMS("TimeStamp '%s' digest weak", digestMethod.c_str()));
e.setCode(Exception::ReferenceDigestWeak);
exception.addCause(e);
}
for(const auto &manifest: metadata)
{
if(manifest.mime != "text/xml")
continue;
istringstream is(manifest.data);
XMLDocument doc = XMLDocument::openStream(is, {"ASiCManifest", ASiContainer::ASIC_NS});
for(auto ref = doc/"DataObjectReference"; ref; ref++)
{
string_view method = (ref/DigestMethod)["Algorithm"];
auto uri = util::File::fromUriPath(ref["URI"]);
vector<unsigned char> digest;
if(file->fileName() == uri)
digest = file->calcDigest(string(method));
else
{
auto i = find_if(metadata.cbegin(), metadata.cend(), [&uri](const auto &d) { return d.name == uri; });
if(i == metadata.cend())
THROW("File not found %s.", uri.c_str());
digest = i->digest(method).result();
}
if(vector<unsigned char> digestValue = ref/DigestValue; digest != digestValue)
THROW("Reference %s digest does not match", uri.c_str());
}
}
}
catch (const Exception& e)
{
Expand Down Expand Up @@ -134,7 +215,8 @@ string SignatureTST::profile() const
return string(ASiC_S::ASIC_TST_PROFILE);
}

std::vector<unsigned char> SignatureTST::save() const
void SignatureTST::save(const ZipSerialize &z) const
{
return *timestampToken;
for(const auto &[name, mime, data, root]: metadata)
z.addFile(name, asicSDoc->zproperty(name))(data);
}
10 changes: 8 additions & 2 deletions src/SignatureTST.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ namespace digidoc
{
class ASiC_S;
class TS;
class ZipSerialize;

class SignatureTST final: public Signature
{
public:
SignatureTST(const std::string &data, ASiC_S *asicSDoc);
SignatureTST(bool manifest, const ZipSerialize &z, ASiC_S *asicSDoc);
SignatureTST(ASiC_S *asicSDoc, Signer *signer);
~SignatureTST();

Expand All @@ -53,12 +54,17 @@ class SignatureTST final: public Signature
// Xades properties
std::string profile() const final;

std::vector<unsigned char> save() const;
//TSA profile properties
std::vector<TSAInfo> ArchiveTimeStamps() const final;

void save(const ZipSerialize &s) const;

private:
DISABLE_COPY(SignatureTST);
ASiC_S *asicSDoc {};
std::unique_ptr<TS> timestampToken;
struct Data;
std::vector<Data> metadata;
};

}
2 changes: 0 additions & 2 deletions src/SignatureXAdES_B.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ const map<string_view,SignatureXAdES_B::Policy> SignatureXAdES_B::policylist{
namespace digidoc
{

constexpr XMLName DigestMethod {"DigestMethod", DSIG_NS};
constexpr XMLName DigestValue {"DigestValue", DSIG_NS};
constexpr XMLName X509IssuerName {"X509IssuerName", DSIG_NS};
constexpr XMLName X509SerialNumber {"X509SerialNumber", DSIG_NS};

Expand Down
2 changes: 0 additions & 2 deletions src/SignatureXAdES_B.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ namespace digidoc
{
constexpr std::string_view ASIC_NS {"http://uri.etsi.org/02918/v1.2.1#"};
constexpr std::string_view OPENDOCUMENT_NS {"urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0"};
constexpr std::string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"};
constexpr std::string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"};
constexpr std::string_view XADESv141_NS {"http://uri.etsi.org/01903/v1.4.1#"};
constexpr std::string_view REF_TYPE {"http://uri.etsi.org/01903#SignedProperties"};

Expand Down
61 changes: 44 additions & 17 deletions src/XMLDocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ struct XMLNode: public XMLElem<xmlNode>
}
};

struct XMLSchema;
struct XMLDocument: public unique_free_t<xmlDoc>, public XMLNode
{
static constexpr std::string_view C14D_ID_1_0 {"http://www.w3.org/TR/2001/REC-xml-c14n-20010315"};
Expand Down Expand Up @@ -399,23 +400,7 @@ struct XMLDocument: public unique_free_t<xmlDoc>, public XMLNode
return xmlSaveFormatFileTo(buf, get(), "UTF-8", format) > 0;
}

void validateSchema(const std::string &schemaPath) const
{
auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(schemaPath.c_str()), xmlSchemaFreeParserCtxt);
if(!parser)
THROW("Failed to create schema parser context %s", schemaPath.c_str());
xmlSchemaSetParserErrors(parser.get(), schemaValidationError, schemaValidationWarning, nullptr);
auto schema = make_unique_ptr(xmlSchemaParse(parser.get()), xmlSchemaFree);
if(!schema)
THROW("Failed to parse schema %s", schemaPath.c_str());
auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(schema.get()), xmlSchemaFreeValidCtxt);
if(!validate)
THROW("Failed to create schema validation context %s", schemaPath.c_str());
Exception e(EXCEPTION_PARAMS("Failed to XML with schema"));
xmlSchemaSetValidErrors(validate.get(), schemaValidationError, schemaValidationWarning, &e);
if(xmlSchemaValidateDoc(validate.get(), get()) != 0)
throw e;
}
inline void validateSchema(const XMLSchema &schema) const;

static bool verifySignature(XMLNode signature, [[maybe_unused]] Exception *e = {}) noexcept
{
Expand Down Expand Up @@ -449,6 +434,36 @@ struct XMLDocument: public unique_free_t<xmlDoc>, public XMLNode
return false;
return ctx->status == xmlSecDSigStatusSucceeded;
}
};

struct XMLSchema
{
auto parser(const std::string &path)
{
auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(path.c_str()), xmlSchemaFreeParserCtxt);
if(!parser)
THROW("Failed to create schema parser context %s", path.c_str());
xmlSchemaSetParserErrors(parser.get(), schemaValidationError, schemaValidationWarning, nullptr);
return parser;
}

XMLSchema(const std::string &path)
: d(make_unique_ptr(xmlSchemaParse(parser(path).get()), xmlSchemaFree))
{
if(!d)
THROW("Failed to parse schema %s", path.c_str());
}

void validate(const XMLDocument &doc) const
{
auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(d.get()), xmlSchemaFreeValidCtxt);
if(!validate)
THROW("Failed to create schema validation context");
Exception e(EXCEPTION_PARAMS("Failed to XML with schema"));
xmlSchemaSetValidErrors(validate.get(), schemaValidationError, schemaValidationWarning, &e);
if(xmlSchemaValidateDoc(validate.get(), doc.get()) != 0)
throw e;
}

static void schemaValidationError(void *ctx, const char *msg, ...) noexcept
{
Expand All @@ -473,6 +488,18 @@ struct XMLDocument: public unique_free_t<xmlDoc>, public XMLNode
va_end(args);
WARN("Schema validation warning: %s", m.c_str());
}

unique_free_t<xmlSchema> d;
};

inline void XMLDocument::validateSchema(const XMLSchema &schema) const
{
schema.validate(*this);
}

constexpr std::string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"};
constexpr std::string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"};
constexpr XMLName DigestMethod {"DigestMethod", DSIG_NS};
constexpr XMLName DigestValue {"DigestValue", DSIG_NS};

}
2 changes: 0 additions & 2 deletions src/crypto/TSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ namespace digidoc {
constexpr string_view TSL_NS {"http://uri.etsi.org/02231/v2#"};
constexpr string_view ADD_NS {"http://uri.etsi.org/02231/v2/additionaltypes#"};
constexpr string_view ECC_NS {"http://uri.etsi.org/TrstSvc/SvcInfoExt/eSigDir-1999-93-EC-TrustedList/#"};
constexpr string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"};
constexpr string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"};
constexpr string_view XML_NS {"http://www.w3.org/XML/1998/namespace"};

constexpr array SCHEMES_URI {
Expand Down

0 comments on commit 24eeef4

Please sign in to comment.