Skip to content

Commit 67699b8

Browse files
committed
Validate ASiC-S manifest containers
IB-8180 Signed-off-by: Raul Metsma <[email protected]>
1 parent e1b7256 commit 67699b8

File tree

9 files changed

+158
-38
lines changed

9 files changed

+158
-38
lines changed

src/ASiC_S.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,20 @@ ASiC_S::ASiC_S(const string &path)
4646
: ASiContainer(MIMETYPE_ASIC_S)
4747
{
4848
auto z = load(path, false, {mediaType()});
49+
bool foundManifest = false;
50+
bool foundTimestamp = false;
4951
for(const string &file: z.list())
5052
{
5153
if(file == "mimetype")
5254
continue;
5355
if(file == "META-INF/timestamp.tst")
56+
foundTimestamp = true;
57+
if(file == "META-INF/ASiCArchiveManifest.xml")
5458
{
5559
if(!signatures().empty())
5660
THROW("Can not add signature to ASiC-S container which already contains a signature.");
57-
addSignature(make_unique<SignatureTST>(z.extract<stringstream>(file).str(), this));
61+
addSignature(make_unique<SignatureTST>(true, z, this));
62+
foundManifest = true;
5863
}
5964
else if(file == "META-INF/signatures.xml")
6065
{
@@ -65,8 +70,6 @@ ASiC_S::ASiC_S(const string &path)
6570
for(auto s = signatures->signature(); s; s++)
6671
addSignature(make_unique<SignatureXAdES_LTA>(signatures, s, this));
6772
}
68-
else if(file == "META-INF/ASiCArchiveManifest.xml")
69-
THROW("ASiCArchiveManifest are not supported.");
7073
else if(starts_with(file, "META-INF/"))
7174
continue;
7275
else if(const auto directory = File::directory(file);
@@ -77,6 +80,12 @@ ASiC_S::ASiC_S(const string &path)
7780
else
7881
addDataFile(dataStream(file, z), file, "application/octet-stream");
7982
}
83+
if(!foundManifest && foundTimestamp)
84+
{
85+
if(!signatures().empty())
86+
THROW("Can not add signature to ASiC-S container which already contains a signature.");
87+
addSignature(make_unique<SignatureTST>(false, z, this));
88+
}
8089

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

136145
Signature *ASiC_S::sign(Signer *signer)

src/ASiC_S.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@ namespace digidoc
5050
void save(const ZipSerialize &s) final;
5151

5252
static bool isContainerSimpleFormat(const std::string &path);
53+
friend class SignatureTST;
5354
};
5455
}

src/ASiContainer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ namespace digidoc
4545
//https://signa.mitsoft.lt/static/signa-web/webResources/docs/ADOC_specification_approved20090907_EN.pdf
4646
static constexpr std::string_view MIMETYPE_ADOC = "application/vnd.lt.archyvai.adoc-2008";
4747
static constexpr std::string_view MANIFEST_NS = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0";
48+
static constexpr std::string_view ASIC_NS = "http://uri.etsi.org/02918/v1.2.1#";
4849

4950
~ASiContainer() override;
5051
std::string mediaType() const override;

src/SignatureTST.cpp

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,62 @@
2020
#include "SignatureTST.h"
2121

2222
#include "ASiC_S.h"
23+
#include "Conf.h"
2324
#include "DataFile_p.h"
25+
#include "XMLDocument.h"
2426
#include "crypto/Digest.h"
2527
#include "crypto/Signer.h"
2628
#include "crypto/TS.h"
2729
#include "crypto/X509Cert.h"
2830
#include "util/DateTime.h"
31+
#include "util/File.h"
2932
#include "util/log.h"
3033

34+
#include <functional>
35+
#include <sstream>
36+
3137
using namespace digidoc;
3238
using namespace std;
3339

34-
SignatureTST::SignatureTST(const string &data, ASiC_S *asicSDoc)
40+
struct SignatureTST::Data {
41+
std::string name, mime, data;
42+
bool root = false;
43+
44+
Digest digest(Digest digest = {}) const
45+
{
46+
digest.update((const unsigned char*)data.data(), data.size());
47+
return digest;
48+
}
49+
};
50+
51+
SignatureTST::SignatureTST(bool manifest, const ZipSerialize &z, ASiC_S *asicSDoc)
3552
: asicSDoc(asicSDoc)
36-
, timestampToken(make_unique<TS>((const unsigned char*)data.data(), data.size()))
37-
{}
53+
{
54+
auto data = z.extract<stringstream>("META-INF/timestamp.tst").str();
55+
timestampToken = make_unique<TS>((const unsigned char*)data.data(), data.size());
56+
metadata.push_back({"META-INF/timestamp.tst", "application/vnd.etsi.timestamp-token", std::move(data)});
57+
if(!manifest)
58+
return;
59+
XMLSchema schema(util::File::path(Conf::instance()->xsdPath(), "en_31916201v010101.xsd"));
60+
function<void(const string &, string_view)> add = [this, &schema, &add, &z](const string &file, string_view mime) {
61+
auto xml = z.extract<stringstream>(file);
62+
XMLDocument doc = XMLDocument::openStream(xml, {"ASiCManifest", ASiContainer::ASIC_NS});
63+
schema.validate(doc);
64+
65+
for(auto ref = doc/"DataObjectReference"; ref; ref++)
66+
{
67+
if(ref["Rootfile"] == "true")
68+
add(util::File::fromUriPath(ref["URI"]), ref["MimeType"]);
69+
}
70+
71+
auto ref = doc/"SigReference";
72+
string uri = util::File::fromUriPath(ref["URI"]);
73+
string tst = z.extract<stringstream>(uri).str();
74+
metadata.push_back({file, string(mime), xml.str()});
75+
metadata.push_back({uri, string(ref["MimeType"]), std::move(tst)});
76+
};
77+
add("META-INF/ASiCArchiveManifest.xml", "text/xml");
78+
}
3879

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

4891
SignatureTST::~SignatureTST() = default;
4992

93+
std::vector<TSAInfo> SignatureTST::ArchiveTimeStamps() const
94+
{
95+
std::vector<TSAInfo> result;
96+
for(auto i = metadata.cbegin() + 1; i != metadata.cend(); ++i)
97+
{
98+
if(i->mime != "application/vnd.etsi.timestamp-token")
99+
continue;
100+
TS ts((const unsigned char*)i->data.data(), i->data.size());
101+
result.push_back({ts.cert(), util::date::to_string(ts.time())});
102+
}
103+
return result;
104+
}
105+
50106
X509Cert SignatureTST::TimeStampCertificate() const
51107
{
52108
return timestampToken->cert();
@@ -94,15 +150,40 @@ void SignatureTST::validate() const
94150
}
95151
try
96152
{
97-
timestampToken->verify(dataToSign());
98-
if(auto digestMethod = signatureMethod();
99-
!Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) &&
153+
auto digestMethod = signatureMethod();
154+
DataFile *file = asicSDoc->dataFiles().front();
155+
timestampToken->verify(file->calcDigest(digestMethod));
156+
if(!Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) &&
100157
Digest::isWeakDigest(digestMethod))
101158
{
102159
Exception e(EXCEPTION_PARAMS("TimeStamp '%s' digest weak", digestMethod.c_str()));
103160
e.setCode(Exception::ReferenceDigestWeak);
104161
exception.addCause(e);
105162
}
163+
for(const auto &manifest: metadata)
164+
{
165+
if(manifest.mime != "text/xml")
166+
continue;
167+
istringstream is(manifest.data);
168+
XMLDocument doc = XMLDocument::openStream(is, {"ASiCManifest", ASiContainer::ASIC_NS});
169+
for(auto ref = doc/"DataObjectReference"; ref; ref++)
170+
{
171+
string_view method = (ref/DigestMethod)["Algorithm"];
172+
auto uri = util::File::fromUriPath(ref["URI"]);
173+
vector<unsigned char> digest;
174+
if(file->fileName() == uri)
175+
digest = file->calcDigest(string(method));
176+
else
177+
{
178+
auto i = find_if(metadata.cbegin(), metadata.cend(), [&uri](const auto &d) { return d.name == uri; });
179+
if(i == metadata.cend())
180+
THROW("File not found %s.", uri.c_str());
181+
digest = i->digest(method).result();
182+
}
183+
if(vector<unsigned char> digestValue = ref/DigestValue; digest != digestValue)
184+
THROW("Reference %s digest does not match", uri.c_str());
185+
}
186+
}
106187
}
107188
catch (const Exception& e)
108189
{
@@ -134,7 +215,8 @@ string SignatureTST::profile() const
134215
return string(ASiC_S::ASIC_TST_PROFILE);
135216
}
136217

137-
std::vector<unsigned char> SignatureTST::save() const
218+
void SignatureTST::save(const ZipSerialize &z) const
138219
{
139-
return *timestampToken;
220+
for(const auto &[name, mime, data, root]: metadata)
221+
z.addFile(name, asicSDoc->zproperty(name))(data);
140222
}

src/SignatureTST.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ namespace digidoc
2727
{
2828
class ASiC_S;
2929
class TS;
30+
class ZipSerialize;
3031

3132
class SignatureTST final: public Signature
3233
{
3334
public:
34-
SignatureTST(const std::string &data, ASiC_S *asicSDoc);
35+
SignatureTST(bool manifest, const ZipSerialize &z, ASiC_S *asicSDoc);
3536
SignatureTST(ASiC_S *asicSDoc, Signer *signer);
3637
~SignatureTST();
3738

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

56-
std::vector<unsigned char> save() const;
57+
//TSA profile properties
58+
std::vector<TSAInfo> ArchiveTimeStamps() const final;
59+
60+
void save(const ZipSerialize &s) const;
5761

5862
private:
5963
DISABLE_COPY(SignatureTST);
6064
ASiC_S *asicSDoc {};
6165
std::unique_ptr<TS> timestampToken;
66+
struct Data;
67+
std::vector<Data> metadata;
6268
};
6369

6470
}

src/SignatureXAdES_B.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ const map<string_view,SignatureXAdES_B::Policy> SignatureXAdES_B::policylist{
8686
namespace digidoc
8787
{
8888

89-
constexpr XMLName DigestMethod {"DigestMethod", DSIG_NS};
90-
constexpr XMLName DigestValue {"DigestValue", DSIG_NS};
9189
constexpr XMLName X509IssuerName {"X509IssuerName", DSIG_NS};
9290
constexpr XMLName X509SerialNumber {"X509SerialNumber", DSIG_NS};
9391

src/SignatureXAdES_B.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ namespace digidoc
2929
{
3030
constexpr std::string_view ASIC_NS {"http://uri.etsi.org/02918/v1.2.1#"};
3131
constexpr std::string_view OPENDOCUMENT_NS {"urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0"};
32-
constexpr std::string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"};
33-
constexpr std::string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"};
3432
constexpr std::string_view XADESv141_NS {"http://uri.etsi.org/01903/v1.4.1#"};
3533
constexpr std::string_view REF_TYPE {"http://uri.etsi.org/01903#SignedProperties"};
3634

src/XMLDocument.h

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ struct XMLNode: public XMLElem<xmlNode>
285285
}
286286
};
287287

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

402-
void validateSchema(const std::string &schemaPath) const
403-
{
404-
auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(schemaPath.c_str()), xmlSchemaFreeParserCtxt);
405-
if(!parser)
406-
THROW("Failed to create schema parser context %s", schemaPath.c_str());
407-
xmlSchemaSetParserErrors(parser.get(), schemaValidationError, schemaValidationWarning, nullptr);
408-
auto schema = make_unique_ptr(xmlSchemaParse(parser.get()), xmlSchemaFree);
409-
if(!schema)
410-
THROW("Failed to parse schema %s", schemaPath.c_str());
411-
auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(schema.get()), xmlSchemaFreeValidCtxt);
412-
if(!validate)
413-
THROW("Failed to create schema validation context %s", schemaPath.c_str());
414-
Exception e(EXCEPTION_PARAMS("Failed to XML with schema"));
415-
xmlSchemaSetValidErrors(validate.get(), schemaValidationError, schemaValidationWarning, &e);
416-
if(xmlSchemaValidateDoc(validate.get(), get()) != 0)
417-
throw e;
418-
}
403+
inline void validateSchema(const XMLSchema &schema) const;
419404

420405
static bool verifySignature(XMLNode signature, [[maybe_unused]] Exception *e = {}) noexcept
421406
{
@@ -449,6 +434,36 @@ struct XMLDocument: public unique_free_t<xmlDoc>, public XMLNode
449434
return false;
450435
return ctx->status == xmlSecDSigStatusSucceeded;
451436
}
437+
};
438+
439+
struct XMLSchema
440+
{
441+
auto parser(const std::string &path)
442+
{
443+
auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(path.c_str()), xmlSchemaFreeParserCtxt);
444+
if(!parser)
445+
THROW("Failed to create schema parser context %s", path.c_str());
446+
xmlSchemaSetParserErrors(parser.get(), schemaValidationError, schemaValidationWarning, nullptr);
447+
return parser;
448+
}
449+
450+
XMLSchema(const std::string &path)
451+
: d(make_unique_ptr(xmlSchemaParse(parser(path).get()), xmlSchemaFree))
452+
{
453+
if(!d)
454+
THROW("Failed to parse schema %s", path.c_str());
455+
}
456+
457+
void validate(const XMLDocument &doc) const
458+
{
459+
auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(d.get()), xmlSchemaFreeValidCtxt);
460+
if(!validate)
461+
THROW("Failed to create schema validation context");
462+
Exception e(EXCEPTION_PARAMS("Failed to XML with schema"));
463+
xmlSchemaSetValidErrors(validate.get(), schemaValidationError, schemaValidationWarning, &e);
464+
if(xmlSchemaValidateDoc(validate.get(), doc.get()) != 0)
465+
throw e;
466+
}
452467

453468
static void schemaValidationError(void *ctx, const char *msg, ...) noexcept
454469
{
@@ -473,6 +488,18 @@ struct XMLDocument: public unique_free_t<xmlDoc>, public XMLNode
473488
va_end(args);
474489
WARN("Schema validation warning: %s", m.c_str());
475490
}
491+
492+
unique_free_t<xmlSchema> d;
476493
};
477494

495+
inline void XMLDocument::validateSchema(const XMLSchema &schema) const
496+
{
497+
schema.validate(*this);
498+
}
499+
500+
constexpr std::string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"};
501+
constexpr std::string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"};
502+
constexpr XMLName DigestMethod {"DigestMethod", DSIG_NS};
503+
constexpr XMLName DigestValue {"DigestValue", DSIG_NS};
504+
478505
}

src/crypto/TSL.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ namespace digidoc {
4040
constexpr string_view TSL_NS {"http://uri.etsi.org/02231/v2#"};
4141
constexpr string_view ADD_NS {"http://uri.etsi.org/02231/v2/additionaltypes#"};
4242
constexpr string_view ECC_NS {"http://uri.etsi.org/TrstSvc/SvcInfoExt/eSigDir-1999-93-EC-TrustedList/#"};
43-
constexpr string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"};
44-
constexpr string_view XADES_NS {"http://uri.etsi.org/01903/v1.3.2#"};
4543
constexpr string_view XML_NS {"http://www.w3.org/XML/1998/namespace"};
4644

4745
constexpr array SCHEMES_URI {

0 commit comments

Comments
 (0)