Skip to content

Commit 12cbbf0

Browse files
committed
Validate ASiC-S manifest containers
IB-8180, IB-8330 Signed-off-by: Raul Metsma <[email protected]>
1 parent 9491bcc commit 12cbbf0

File tree

9 files changed

+155
-144
lines changed

9 files changed

+155
-144
lines changed

src/ASiC_S.cpp

Lines changed: 26 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
#include "ASiC_S.h"
2121

22-
#include "Conf.h"
2322
#include "SignatureTST.h"
2423
#include "SignatureXAdES_LTA.h"
2524
#include "crypto/Signer.h"
@@ -33,18 +32,6 @@ using namespace digidoc;
3332
using namespace digidoc::util;
3433
using namespace std;
3534

36-
struct ASiC_S::Data {
37-
std::string name, mime, data;
38-
39-
Digest digest(Digest digest = {}) const
40-
{
41-
digest.update((const unsigned char*)data.data(), data.size());
42-
return digest;
43-
}
44-
};
45-
46-
47-
4835
/**
4936
* Initialize ASiCS container.
5037
*/
@@ -59,17 +46,19 @@ ASiC_S::ASiC_S(const string &path)
5946
: ASiContainer(MIMETYPE_ASIC_S)
6047
{
6148
auto z = load(path, false, {mediaType()});
49+
bool foundTimestamp = false;
6250
for(const string &file: z.list())
6351
{
6452
if(file == "mimetype")
6553
continue;
6654
if(file == "META-INF/timestamp.tst")
55+
foundTimestamp = true;
56+
if(file == "META-INF/ASiCArchiveManifest.xml")
6757
{
6858
if(!signatures().empty())
6959
THROW("Can not add signature to ASiC-S container which already contains a signature.");
70-
string tst = z.extract<stringstream>(file).str();
71-
addSignature(make_unique<SignatureTST>(tst, this));
72-
metadata.push_back({file, "application/vnd.etsi.timestamp-token", std::move(tst)});
60+
addSignature(make_unique<SignatureTST>(true, z, this));
61+
foundTimestamp = false; // Manifest contains timestamp
7362
}
7463
else if(file == "META-INF/signatures.xml")
7564
{
@@ -80,28 +69,6 @@ ASiC_S::ASiC_S(const string &path)
8069
for(auto s = signatures->signature(); s; s++)
8170
addSignature(make_unique<SignatureXAdES_LTA>(signatures, s, this));
8271
}
83-
else if(file == "META-INF/ASiCArchiveManifest.xml")
84-
{
85-
function<void(const string &, string_view)> add = [this, &add, &z](const string &file, string_view mime) {
86-
auto xml = z.extract<stringstream>(file);
87-
XMLDocument doc = XMLDocument::openStream(xml, {"ASiCManifest", ASIC_NS});
88-
doc.validateSchema(util::File::path(Conf::instance()->xsdPath(), "en_31916201v010101.xsd"));
89-
90-
for(auto ref = doc/"DataObjectReference"; ref; ref++)
91-
{
92-
if(ref["Rootfile"] == "true")
93-
add(util::File::fromUriPath(ref["URI"]), ref["MimeType"]);
94-
}
95-
96-
auto ref = doc/"SigReference";
97-
string uri = util::File::fromUriPath(ref["URI"]);
98-
string tst = z.extract<stringstream>(uri).str();
99-
addSignature(make_unique<SignatureTST>(file, ::move(doc), tst, this));
100-
metadata.push_back({file, string(mime), xml.str()});
101-
metadata.push_back({uri, string(ref["MimeType"]), std::move(tst)});
102-
};
103-
add(file, "text/xml");
104-
}
10572
else if(starts_with(file, "META-INF/"))
10673
continue;
10774
else if(const auto directory = File::directory(file);
@@ -112,6 +79,12 @@ ASiC_S::ASiC_S(const string &path)
11279
else
11380
addDataFile(dataStream(file, z), file, "application/octet-stream");
11481
}
82+
if(foundTimestamp)
83+
{
84+
if(!signatures().empty())
85+
THROW("Can not add signature to ASiC-S container which already contains a signature.");
86+
addSignature(make_unique<SignatureTST>(false, z, this));
87+
}
11588

11689
if(dataFiles().empty())
11790
THROW("ASiC-S container does not contain any data objects.");
@@ -147,20 +120,20 @@ void ASiC_S::canSave()
147120
THROW("ASiC-S container supports only saving TimeStampToken signatures.");
148121
}
149122

150-
Digest ASiC_S::fileDigest(const string &file, string_view method) const
151-
{
152-
if(auto i = find_if(metadata.cbegin(), metadata.cend(), [&file](const auto &d) { return d.name == file; });
153-
i != metadata.cend())
154-
return i->digest(method);
155-
THROW("File not found %s.", file.c_str());
156-
}
157-
158123
unique_ptr<Container> ASiC_S::openInternal(const string &path, ContainerOpenCB * /*cb*/)
159124
{
160-
if (!isContainerSimpleFormat(path))
161-
return {};
162125
DEBUG("ASiC_S::openInternal(%s)", path.c_str());
163-
return unique_ptr<Container>(new ASiC_S(path));
126+
try
127+
{
128+
if(util::File::fileExtension(path, {"asice", "sce", "bdoc"}))
129+
return {};
130+
return unique_ptr<Container>(new ASiC_S(path));
131+
}
132+
catch(const Exception &)
133+
{
134+
// Ignore the exception: not ASiC/zip document
135+
}
136+
return {};
164137
}
165138

166139
Signature* ASiC_S::prepareSignature(Signer * /*signer*/)
@@ -170,10 +143,11 @@ Signature* ASiC_S::prepareSignature(Signer * /*signer*/)
170143

171144
void ASiC_S::save(const ZipSerialize &s)
172145
{
173-
if(zproperty("META-INF/manifest.xml").size && !createManifest().save(s.addFile("META-INF/manifest.xml", zproperty("META-INF/manifest.xml")), true))
146+
if(const auto &prop = zproperty("META-INF/manifest.xml");
147+
prop.size && !createManifest().save(s.addFile("META-INF/manifest.xml", prop), true))
174148
THROW("Failed to create manifest XML");
175-
for(const auto &[name, mime, data]: metadata)
176-
s.addFile(name, zproperty(name))(data);
149+
for(Signature *sig: signatures())
150+
static_cast<SignatureTST*>(sig)->save(s);
177151
}
178152

179153
Signature *ASiC_S::sign(Signer *signer)
@@ -184,31 +158,3 @@ Signature *ASiC_S::sign(Signer *signer)
184158
THROW("ASiC-S container supports only one TimeStampToken signature.");
185159
return addSignature(make_unique<SignatureTST>(this, signer));
186160
}
187-
188-
/**
189-
* Detect ASiC format based on file extentions, mimetype or zip contents.<br/>
190-
* Container format is simple (ASiC-S) or extended (ASiC-E).
191-
*
192-
* @param path Path of the container.
193-
* @throws Exception
194-
*/
195-
bool ASiC_S::isContainerSimpleFormat(const string &path)
196-
{
197-
DEBUG("isContainerSimpleFormat(path = '%s')", path.c_str());
198-
if(util::File::fileExtension(path, {"asice", "sce", "bdoc"}))
199-
return false;
200-
if(util::File::fileExtension(path, {"asics", "scs"}))
201-
return true;
202-
DEBUG("Check if ASiC/zip containter");
203-
try
204-
{
205-
ZipSerialize z(path, false);
206-
vector<string> list = z.list();
207-
return list.front() == "mimetype" && readMimetype(z) == MIMETYPE_ASIC_S;
208-
}
209-
catch(const Exception &)
210-
{
211-
// Ignore the exception: not ASiC/zip document
212-
}
213-
return false;
214-
}

src/ASiC_S.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323

2424
namespace digidoc
2525
{
26-
class Digest;
27-
2826
/**
2927
* Implements the ASiC-S specification of the timestamped digital document container.
3028
* Container contains a single datafile object and one time assertion file.
@@ -39,8 +37,6 @@ namespace digidoc
3937
Signature* prepareSignature(Signer *signer) override;
4038
Signature* sign(Signer* signer) override;
4139

42-
Digest fileDigest(const std::string &file, std::string_view method = {}) const;
43-
4440
static std::unique_ptr<Container> createInternal(const std::string &path);
4541
static std::unique_ptr<Container> openInternal(const std::string &path, ContainerOpenCB *cb);
4642

@@ -53,9 +49,6 @@ namespace digidoc
5349
void canSave() final;
5450
void save(const ZipSerialize &s) final;
5551

56-
static bool isContainerSimpleFormat(const std::string &path);
57-
58-
struct Data;
59-
std::vector<Data> metadata;
52+
friend class SignatureTST;
6053
};
6154
}

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: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +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"
30+
#include "util/algorithm.h"
2831
#include "util/DateTime.h"
2932
#include "util/File.h"
3033
#include "util/log.h"
3134

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

35-
constexpr std::string_view DSIG_NS {"http://www.w3.org/2000/09/xmldsig#"};
36-
constexpr XMLName DigestMethod {"DigestMethod", DSIG_NS};
37-
constexpr XMLName DigestValue {"DigestValue", DSIG_NS};
41+
struct SignatureTST::Data {
42+
std::string name, mime, data;
43+
bool root = false;
3844

39-
SignatureTST::SignatureTST(const string &data, ASiC_S *asicSDoc)
40-
: asicSDoc(asicSDoc)
41-
, timestampToken(make_unique<TS>((const unsigned char*)data.data(), data.size()))
42-
{}
45+
Digest digest(Digest digest = {}) const
46+
{
47+
digest.update((const unsigned char*)data.data(), data.size());
48+
return digest;
49+
}
50+
};
4351

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

5181
SignatureTST::SignatureTST(ASiC_S *asicSDoc, Signer *signer)
@@ -55,6 +85,8 @@ SignatureTST::SignatureTST(ASiC_S *asicSDoc, Signer *signer)
5585
Digest digest;
5686
dataFile->digest(digest);
5787
timestampToken = make_unique<TS>(digest, signer->userAgent());
88+
vector<unsigned char> der = *timestampToken;
89+
metadata.push_back({"META-INF/timestamp.tst", "application/vnd.etsi.timestamp-token", {der.cbegin(), der.cend()}});
5890
}
5991

6092
SignatureTST::~SignatureTST() = default;
@@ -106,28 +138,48 @@ void SignatureTST::validate() const
106138
}
107139
try
108140
{
109-
timestampToken->verify(dataToSign());
110-
if(auto digestMethod = signatureMethod();
111-
!Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) &&
141+
auto digestMethod = signatureMethod();
142+
DataFile *file = asicSDoc->dataFiles().front();
143+
timestampToken->verify(file->calcDigest(digestMethod));
144+
if(!Exception::hasWarningIgnore(Exception::ReferenceDigestWeak) &&
112145
Digest::isWeakDigest(digestMethod))
113146
{
114147
Exception e(EXCEPTION_PARAMS("TimeStamp '%s' digest weak", digestMethod.c_str()));
115148
e.setCode(Exception::ReferenceDigestWeak);
116149
exception.addCause(e);
117150
}
118-
if(doc)
151+
vector<string> list {file->fileName()};
152+
for(const auto &manifest: metadata)
119153
{
120-
DataFile *file = asicSDoc->dataFiles().front();
154+
if(manifest.mime != "text/xml")
155+
continue;
156+
istringstream is(manifest.data);
157+
XMLDocument doc = XMLDocument::openStream(is, {"ASiCManifest", ASiContainer::ASIC_NS});
158+
vector<string> add;
121159
for(auto ref = doc/"DataObjectReference"; ref; ref++)
122160
{
123161
string_view method = (ref/DigestMethod)["Algorithm"];
124-
auto uri = util::File::fromUriPath(ref["URI"]);
125-
vector<unsigned char> digest = file->fileName() == uri ?
126-
dynamic_cast<const DataFilePrivate*>(file)->calcDigest(string(method)) :
127-
asicSDoc->fileDigest(uri, method).result();
162+
const auto &uri = add.emplace_back(util::File::fromUriPath(ref["URI"]));
163+
vector<unsigned char> digest;
164+
if(file->fileName() == uri)
165+
digest = file->calcDigest(string(method));
166+
else
167+
{
168+
auto i = find_if(metadata.cbegin(), metadata.cend(), [&uri](const auto &d) { return d.name == uri; });
169+
if(i == metadata.cend())
170+
THROW("File not found '%s'.", uri.c_str());
171+
digest = i->digest(method).result();
172+
}
128173
if(vector<unsigned char> digestValue = ref/DigestValue; digest != digestValue)
129-
THROW("Reference %s digest does not match", uri.c_str());
174+
THROW("Reference '%s' digest does not match", uri.c_str());
175+
}
176+
// Check if all files in previous scope are present
177+
for(const string &uri: list)
178+
{
179+
if(!contains(add, uri))
180+
THROW("Reference '%s' not found in manifest", uri.c_str());
130181
}
182+
list = std::move(add);
131183
}
132184
}
133185
catch (const Exception& e)
@@ -141,8 +193,6 @@ void SignatureTST::validate() const
141193

142194
std::vector<unsigned char> SignatureTST::dataToSign() const
143195
{
144-
if(!file.empty())
145-
return asicSDoc->fileDigest(file, signatureMethod()).result();
146196
return asicSDoc->dataFiles().front()->calcDigest(signatureMethod());
147197
}
148198

@@ -162,7 +212,8 @@ string SignatureTST::profile() const
162212
return string(ASiC_S::ASIC_TST_PROFILE);
163213
}
164214

165-
std::vector<unsigned char> SignatureTST::save() const
215+
void SignatureTST::save(const ZipSerialize &z) const
166216
{
167-
return *timestampToken;
217+
for(const auto &[name, mime, data, root]: metadata)
218+
z.addFile(name, asicSDoc->zproperty(name))(data);
168219
}

0 commit comments

Comments
 (0)