Skip to content

Commit e232b19

Browse files
metsmakristelmerilain
authored andcommitted
Adjust TSL init to throw exceptions
IB-8710, Fixes #694 Signed-off-by: Raul Metsma <raul@metsma.ee>
1 parent bd6edb6 commit e232b19

File tree

9 files changed

+96
-99
lines changed

9 files changed

+96
-99
lines changed

src/Container.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,17 @@ void digidoc::initialize(const string &appInfo, const string &userAgent, initCal
139139
{
140140
thread([callBack]{
141141
try {
142-
X509CertStore::instance();
142+
X509CertStore::instance()->update();
143143
callBack(nullptr);
144144
}
145145
catch(const Exception &e) {
146+
ERR("Failed to load TSL lists: %s", e.msg().c_str());
146147
callBack(&e);
147148
}
148149
}).detach();
149150
}
150151
else
151-
X509CertStore::instance();
152+
X509CertStore::instance()->update();
152153
}
153154

154155
/**

src/crypto/TSL.cpp

Lines changed: 53 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828

2929
#include <array>
3030
#include <charconv>
31+
#include <chrono>
3132
#include <fstream>
3233
#include <future>
3334

3435
using namespace digidoc;
3536
using namespace digidoc::util;
3637
using namespace std;
38+
using namespace chrono_literals;
3739

3840
namespace digidoc {
3941

@@ -102,14 +104,14 @@ TSL::TSL(string file)
102104
WARN("Failed to parse configuration: %s", path.c_str());
103105
}
104106

105-
bool TSL::activate(string territory)
107+
bool TSL::activate(string_view territory)
106108
{
107109
if(territory.size() != 2)
108110
return false;
109111
if(territory == "GR")
110-
territory = "EL"; // Greece is EL in EU TL
112+
territory = "EL"; // Greece is EL in EU TL
111113
string cache = CONF(TSLCache);
112-
string path = cache + '/' + territory + ".xml";
114+
string path = cache + '/' + territory.data() + ".xml";
113115
if(File::fileExists(path))
114116
return false;
115117
ofstream(File::encodeName(path), ofstream::binary) << ' ';
@@ -192,75 +194,70 @@ string_view TSL::operatorName() const noexcept
192194
return toString(schemeInformation/"SchemeOperatorName");
193195
}
194196

195-
vector<TSL::Service> TSL::parse()
196-
{
197-
string url = CONF(TSLUrl);
198-
string cache = CONF(TSLCache);
199-
vector<X509Cert> cert = CONF(TSLCerts);
200-
File::createDirectory(cache);
201-
return parse(url, cert, cache, string(File::fileName(url)));
202-
}
203-
204197
vector<TSL::Service> TSL::parse(const string &url, const vector<X509Cert> &certs,
205-
const string &cache, const string &territory)
198+
const string &cache, string_view territory)
206199
{
207-
vector<Service> list;
208-
try {
209-
TSL tsl = parseTSL(url, certs, cache, territory);
210-
if(tsl.pointers().empty())
211-
return tsl.services();
200+
TSL tsl = parseTSL(url, certs, cache, territory);
201+
auto pointers = tsl.pointers();
202+
if(pointers.empty())
203+
return tsl.services();
212204

213-
vector< future< vector<TSL::Service> > > futures;
214-
for(const TSL::Pointer &p: tsl.pointers())
215-
{
216-
if(!File::fileExists(cache + "/" + p.territory + ".xml"))
217-
continue;
218-
futures.push_back(async(launch::async, [p, cache]{
205+
vector< future< vector<TSL::Service> > > futures;
206+
for(const TSL::Pointer &p: pointers)
207+
{
208+
if(!File::fileExists(cache + "/" + p.territory + ".xml"))
209+
continue;
210+
futures.push_back(async(launch::async, [p, cache] noexcept -> vector<TSL::Service> {
211+
try {
219212
return parse(p.location, p.certs, cache, p.territory + ".xml");
220-
}));
221-
}
222-
for(auto &f: futures)
223-
{
224-
vector<Service> services = f.get();
225-
list.insert(list.end(), make_move_iterator(services.begin()), make_move_iterator(services.end()));
226-
}
213+
}
214+
catch(const Exception &e)
215+
{
216+
debugException(e);
217+
ERR("TSL %s Failed to validate list", p.territory.c_str());
218+
return {};
219+
}
220+
}));
227221
}
228-
catch(const Exception &e)
222+
vector<Service> list;
223+
for(auto &f: futures)
229224
{
230-
debugException(e);
231-
ERR("TSL %s Failed to validate list", territory.c_str());
232-
list.clear();
225+
vector<Service> services = f.get();
226+
list.insert(list.end(), make_move_iterator(services.begin()), make_move_iterator(services.end()));
233227
}
234228
return list;
235229
}
236230

237231
TSL TSL::parseTSL(const string &url, const vector<X509Cert> &certs,
238-
const string &cache, const string &territory)
232+
const string &cache, string_view territory)
239233
{
240234
string path = File::path(cache, territory);
241235
TSL valid;
242236
try {
243237
TSL tsl(path);
244238
tsl.validate(certs);
245239
valid = std::move(tsl);
246-
DEBUG("TSL %s (%llu) signature is valid", territory.c_str(), valid.sequenceNumber());
240+
DEBUG("TSL %.*s (%llu) signature is valid", STR_VIEW_FMT(territory), valid.sequenceNumber());
247241

248242
if(valid.isExpired())
249243
{
250244
if(!CONF(TSLAutoUpdate) && CONF(TSLAllowExpired))
251245
return valid;
252-
THROW("TSL %s (%llu) is expired", territory.c_str(), valid.sequenceNumber());
246+
THROW("TSL %.*s (%llu) is expired", STR_VIEW_FMT(territory), valid.sequenceNumber());
253247
}
254248

255-
if(CONF(TSLOnlineDigest) && (File::modifiedTime(valid.path) < (time(nullptr) - (60 * 60 * 24))))
249+
if(CONF(TSLOnlineDigest))
256250
{
257-
if(valid.validateETag(url))
258-
File::updateModifiedTime(valid.path, time(nullptr));
251+
auto encPath = File::encodeName(valid.path);
252+
auto now = chrono::file_clock::now();
253+
error_code ec;
254+
if(filesystem::last_write_time(encPath, ec) < now - 24h && valid.validateETag(url))
255+
filesystem::last_write_time(encPath, now, ec);
259256
}
260257

261258
return valid;
262259
} catch(const Exception &) {
263-
ERR("TSL %s signature is invalid", territory.c_str());
260+
ERR("TSL %.*s signature is invalid", STR_VIEW_FMT(territory));
264261
if(!CONF(TSLAutoUpdate))
265262
throw;
266263
}
@@ -272,22 +269,22 @@ TSL TSL::parseTSL(const string &url, const vector<X509Cert> &certs,
272269
tsl.validate(certs);
273270
valid = std::move(tsl);
274271

275-
ofstream(File::encodeName(path), ofstream::binary|fstream::trunc)
276-
<< ifstream(File::encodeName(valid.path), fstream::binary).rdbuf();
277272
error_code ec;
278-
filesystem::remove(File::encodeName(valid.path), ec);
273+
filesystem::copy_file(File::encodeName(valid.path), File::encodeName(path), filesystem::copy_options::overwrite_existing, ec);
274+
if(ec == errc{})
275+
filesystem::remove(File::encodeName(valid.path), ec);
279276

280277
ofstream(File::encodeName(path + ".etag"), ofstream::trunc) << etag;
281278

282-
DEBUG("TSL %s (%llu) signature is valid", territory.c_str(), valid.sequenceNumber());
279+
DEBUG("TSL %.*s (%llu) signature is valid", STR_VIEW_FMT(territory), valid.sequenceNumber());
283280
} catch(const Exception &) {
284-
ERR("TSL %s signature is invalid", territory.c_str());
281+
ERR("TSL %.*s signature is invalid", STR_VIEW_FMT(territory));
285282
if(!valid)
286283
throw;
287284
}
288285

289286
if(valid.isExpired() && !CONF(TSLAllowExpired))
290-
THROW("TSL %s (%llu) is expired", territory.c_str(), valid.sequenceNumber());
287+
THROW("TSL %.*s (%llu) is expired", STR_VIEW_FMT(territory), valid.sequenceNumber());
291288

292289
return valid;
293290
}
@@ -393,9 +390,9 @@ vector<string> TSL::pivotURLs() const
393390

394391
vector<TSL::Pointer> TSL::pointers() const
395392
{
396-
if(!contains(SCHEMES_URI, type()))
397-
return {};
398393
vector<Pointer> pointer;
394+
if(!contains(SCHEMES_URI, type()))
395+
return pointer;
399396
for(auto other = schemeInformation/"PointersToOtherTSL"/"OtherTSLPointer"; other; other++)
400397
{
401398
Pointer p;
@@ -439,12 +436,12 @@ vector<X509Cert> TSL::serviceDigitalIdentity(XMLNode service, string_view ctx)
439436
result.emplace_back(cert);
440437
continue;
441438
} catch(const Exception &e) {
442-
DEBUG("Failed to parse %.*s certificate, Testing also parse as PEM: %s", int(ctx.size()), ctx.data(), e.msg().c_str());
439+
DEBUG("Failed to parse %.*s certificate, Testing also parse as PEM: %s", STR_VIEW_FMT(ctx), e.msg().c_str());
443440
}
444441
try {
445442
result.emplace_back(cert, X509Cert::Pem);
446443
} catch(const Exception &e) {
447-
DEBUG("Failed to parse %.*s certificate as PEM: %s", int(ctx.size()), ctx.data(), e.msg().c_str());
444+
DEBUG("Failed to parse %.*s certificate as PEM: %s", STR_VIEW_FMT(ctx), e.msg().c_str());
448445
}
449446
}
450447
}
@@ -502,9 +499,9 @@ void TSL::validate() const
502499
THROW("Failed to parse XML");
503500
auto signature = (*this)/XMLName{"Signature", DSIG_NS};
504501
if(!signature)
505-
THROW("TSL %s Failed to verify signature", territory().data());
502+
THROW("TSL %.*s Failed to verify signature", STR_VIEW_FMT(territory()));
506503
if(!XMLDocument::verifySignature(signature))
507-
THROW("TSL %s Signature is invalid", territory().data());
504+
THROW("TSL %.*s Signature is invalid", STR_VIEW_FMT(territory()));
508505
}
509506

510507
void TSL::validate(const vector<X509Cert> &certs, int recursion) const
@@ -521,7 +518,7 @@ void TSL::validate(const vector<X509Cert> &certs, int recursion) const
521518

522519
vector<string> urls = pivotURLs();
523520
if(urls.empty())
524-
THROW("TSL %s Signature is signed with untrusted certificate", territory().data());
521+
THROW("TSL %.*s Signature is signed with untrusted certificate", STR_VIEW_FMT(territory()));
525522

526523
// https://ec.europa.eu/tools/lotl/pivot-lotl-explanation.html
527524
string path = File::path(CONF(TSLCache), File::fileName(urls[0]));
@@ -604,6 +601,6 @@ bool TSL::validateRemoteDigest(const string &url)
604601
sha.update(is);
605602

606603
if(!digest.empty() && digest != sha.result())
607-
THROW("TSL %s remote digest does not match local. TSL might be outdated", territory().data());
604+
THROW("TSL %.*s remote digest does not match local. TSL might be outdated", STR_VIEW_FMT(territory()));
608605
return true;
609606
}

src/crypto/TSL.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ class TSL: private XMLDocument
5555
std::vector<Pointer> pointers() const;
5656
std::vector<Service> services() const;
5757

58-
static bool activate(std::string territory);
59-
static std::vector<Service> parse();
58+
static bool activate(std::string_view territory);
59+
static std::vector<Service> parse(const std::string &url, const std::vector<X509Cert> &certs,
60+
const std::string &cache, std::string_view territory);
6061

6162
private:
6263
std::vector<std::string> pivotURLs() const;
@@ -67,10 +68,8 @@ class TSL: private XMLDocument
6768

6869
static std::string fetch(const std::string &url, const std::string &path);
6970
static void debugException(const Exception &e);
70-
static std::vector<Service> parse(const std::string &url, const std::vector<X509Cert> &certs,
71-
const std::string &cache, const std::string &territory);
7271
static TSL parseTSL(const std::string &url, const std::vector<X509Cert> &certs,
73-
const std::string &cache, const std::string &territory) ;
72+
const std::string &cache, std::string_view territory) ;
7473
static bool parseInfo(XMLNode info, Service &s);
7574
static std::vector<X509Cert> serviceDigitalIdentity(XMLNode other, std::string_view ctx);
7675
static std::vector<X509Cert> serviceDigitalIdentities(XMLNode other, std::string_view ctx);

src/crypto/X509CertStore.cpp

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "crypto/TSL.h"
2626
#include "util/algorithm.h"
2727
#include "util/DateTime.h"
28+
#include "util/File.h"
2829
#include "util/log.h"
2930

3031
#include <openssl/conf.h>
@@ -47,34 +48,28 @@ const X509CertStore::Type X509CertStore::OCSP {
4748
"http://uri.etsi.org/TrstSvc/Svctype/Certstatus/OCSP/QC",
4849
};
4950

50-
class X509CertStore::Private: public vector<TSL::Service> {
51-
public:
52-
void update()
53-
{
54-
vector<TSL::Service> list = TSL::parse();
55-
swap(list);
56-
INFO("Loaded %zu certificates into TSL certificate store.", size());
57-
}
58-
};
51+
struct X509CertStore::Private: public vector<TSL::Service> {};
5952

6053
/**
6154
* X509CertStore constructor.
6255
*/
6356
X509CertStore::X509CertStore()
6457
: d(make_unique<Private>())
65-
{
66-
d->update();
67-
}
58+
{}
6859

6960
/**
7061
* Release all certificates.
7162
*/
72-
X509CertStore::~X509CertStore() = default;
63+
X509CertStore::~X509CertStore() noexcept = default;
7364

74-
void X509CertStore::activate(const X509Cert &cert) const
65+
void X509CertStore::activate(const X509Cert &cert) const try
7566
{
7667
if(std::max<bool>(TSL::activate(cert.issuerName("C")), TSL::activate(cert.subjectName("C"))))
77-
d->update();
68+
update();
69+
}
70+
catch(const Exception &e)
71+
{
72+
ERR("Failed to activate TSL: %s", e.msg().c_str());
7873
}
7974

8075
/**
@@ -190,6 +185,17 @@ int X509CertStore::validate(int ok, X509_STORE_CTX *ctx)
190185
return ok;
191186
}
192187

188+
void X509CertStore::update() const
189+
{
190+
string url = CONF(TSLUrl);
191+
string cache = CONF(TSLCache);
192+
vector<X509Cert> cert = CONF(TSLCerts);
193+
util::File::createDirectory(cache);
194+
vector<TSL::Service> list = TSL::parse(url, cert, cache, util::File::fileName(url));
195+
d->swap(list);
196+
INFO("Loaded %zu certificates into TSL certificate store.", d->size());
197+
}
198+
193199
/**
194200
* Check if X509Cert is signed by trusted issuer
195201
* @throw Exception if error

src/crypto/X509CertStore.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,16 @@ namespace digidoc
4949
X509Cert findIssuer(const X509Cert &cert, const Type &type) const;
5050
static X509Cert issuerFromAIA(const X509Cert &cert);
5151
static unique_free_t<X509_STORE> createStore(const Type &type, tm &tm);
52+
void update() const;
5253
bool verify(const X509Cert &cert, bool noqscd, tm validation_time = {}) const;
5354

5455
private:
5556
X509CertStore();
56-
~X509CertStore();
57+
~X509CertStore() noexcept;
5758
DISABLE_COPY(X509CertStore);
5859

5960
static int validate(int ok, X509_STORE_CTX *ctx);
60-
class Private;
61+
struct Private;
6162
std::unique_ptr<Private> d;
6263
};
6364
}

src/util/File.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,6 @@ time_t File::modifiedTime(const string &path)
133133
return f_stat(encodeName(path).c_str(), &fileInfo) ? time(nullptr) : fileInfo.st_mtime;
134134
}
135135

136-
void File::updateModifiedTime(const string &path, time_t time)
137-
{
138-
f_utimbuf u_time { time, time };
139-
if(f_utime(encodeName(path).c_str(), &u_time))
140-
THROW("Failed to update file modified time.");
141-
}
142-
143136
bool File::fileExtension(string_view path, initializer_list<string_view> list)
144137
{
145138
size_t pos = path.find_last_of('.');

src/util/File.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ namespace digidoc
3939
static std::string digidocppPath();
4040
static std::filesystem::path encodeName(std::string_view fileName);
4141
static time_t modifiedTime(const std::string &path);
42-
static void updateModifiedTime(const std::string &path, time_t time);
4342
static bool fileExists(const std::string& path);
4443
static bool fileExtension(std::string_view path, std::initializer_list<std::string_view> list);
4544
static unsigned long fileSize(const std::filesystem::path &path) noexcept;

src/util/log.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ namespace digidoc
4949
#define DEBUG(...) digidoc::Log::out(digidoc::Log::DebugType, __FILE__, __LINE__, __VA_ARGS__)
5050
#define DEBUGMEM(msg, ptr, size) digidoc::Log::dbgPrintfMemImpl(msg, ptr, size, __FILE__, __LINE__)
5151

52+
#define STR_VIEW_FMT(str) int(str.size()), str.data()
5253
#define EXCEPTION_PARAMS(...) __FILE__, __LINE__, digidoc::Log::format(__VA_ARGS__)
5354
#define EXCEPTION_ADD(_main, ...) _main.addCause(digidoc::Exception(EXCEPTION_PARAMS(__VA_ARGS__)))
5455
#define THROW(...) throw digidoc::Exception(EXCEPTION_PARAMS(__VA_ARGS__))

0 commit comments

Comments
 (0)