35#include <unordered_set>
53typedef std::unique_ptr<FILE, int(*)(FILE*)> file_smart_ptr;
56static uint64_t monotonic_time_s() {
58 clock_gettime(CLOCK_MONOTONIC, &tp);
59 return tp.tv_sec + (tp.tv_nsec >= 500000000);
68 Set(
int output_fd, XrdSysError & err) : m_log(err),m_output_fp(file_smart_ptr(fdopen(XrdSysFD_Dup(output_fd),
"w"), &
fclose)){
69 if(!m_output_fp.get()) {
73 virtual ~Set() =
default;
78 file_smart_ptr m_output_fp;
81class CASet :
public Set {
83 CASet(
int output_fd, XrdSysError &err):Set(output_fd,err){}
96 bool processFile(file_smart_ptr &fd,
const std::string &fname);
103 std::unordered_set<std::string> m_known_cas;
108CASet::processFile(file_smart_ptr &fp,
const std::string &fname)
110 XrdCryptoX509Chain chain;
115 auto ca = chain.
Begin();
116 if (!m_output_fp.get()) {
117 m_log.Emsg(
"CAset",
"No output file has been opened", fname.c_str());
122 auto hash_ptr = ca->SubjectHash();
126 auto iter = m_known_cas.find(hash_ptr);
127 if (iter != m_known_cas.end()) {
133 m_known_cas.insert(hash_ptr);
136 m_log.Emsg(
"CAset",
"Failed to write out CA", fname.c_str());
142 fflush(m_output_fp.get());
149class CRLSet :
public Set {
151 CRLSet(
int output_fd, XrdSysError &err):Set(output_fd,err){}
163 bool processFile(file_smart_ptr &fd,
const std::string &fname);
169 bool atLeastOneValidCRLFound()
const;
176 bool processCRLWithCriticalExt();
183 std::unordered_set<std::string> m_known_crls;
184 std::atomic<bool> m_atLeastOneValidCRLFound;
187 std::vector<std::unique_ptr<XrdCryptosslX509Crl>> m_crls_critical_extension;
192CRLSet::processFile(file_smart_ptr &fp,
const std::string &fname)
194 if (!m_output_fp.get()) {
195 m_log.Emsg(
"CRLSet",
"No output file has been opened", fname.c_str());
200 for (std::unique_ptr<XrdCryptosslX509Crl> xrd_crl(
new XrdCryptosslX509Crl(fp.get(), fname.c_str()));
202 xrd_crl = std::unique_ptr<XrdCryptosslX509Crl>(
new XrdCryptosslX509Crl(fp.get(), fname.c_str())))
204 auto hash_ptr = xrd_crl->IssuerHash(1);
208 m_atLeastOneValidCRLFound =
true;
209 auto iter = m_known_crls.find(hash_ptr);
210 if (iter != m_known_crls.end()) {
215 m_known_crls.insert(hash_ptr);
217 if(xrd_crl->hasCriticalExtension()) {
220 m_crls_critical_extension.emplace_back(std::move(xrd_crl));
223 if (!xrd_crl->ToFile(m_output_fp.get())) {
224 m_log.Emsg(
"CRLset",
"Failed to write out CRL", fname.c_str());
225 fflush(m_output_fp.get());
230 fflush(m_output_fp.get());
235bool CRLSet::atLeastOneValidCRLFound()
const {
236 return m_atLeastOneValidCRLFound;
239bool CRLSet::processCRLWithCriticalExt() {
240 if(!m_crls_critical_extension.empty()) {
241 if (!m_output_fp.get()) {
242 m_log.Emsg(
"CRLSet",
"No output file has been opened to add CRLs with critical extension");
245 for (
const auto &crl: m_crls_critical_extension) {
246 if (!crl->ToFile(m_output_fp.get())) {
247 m_log.Emsg(
"CRLset",
"Failed to write out CRL with critical extension", crl->ParentFile());
248 fflush(m_output_fp.get());
252 fflush(m_output_fp.get());
260std::unique_ptr<XrdTlsTempCA::TempCAGuard>
263 if (-1 ==
mkdir(ca_tmp_dir.c_str(), S_IRWXU) && errno != EEXIST) {
264 err.
Emsg(
"TempCA",
"Unable to create CA temp directory", ca_tmp_dir.c_str(), strerror(errno));
267 std::stringstream ss;
268 ss << ca_tmp_dir <<
"/ca_file.XXXXXX.pem";
269 std::vector<char> ca_fname;
270 ca_fname.resize(ss.str().size() + 1);
271 memcpy(ca_fname.data(), ss.str().c_str(), ss.str().size());
273 int ca_fd = mkstemps(ca_fname.data(), 4);
275 err.
Emsg(
"TempCA",
"Failed to create temp file:", strerror(errno));
276 return std::unique_ptr<TempCAGuard>();
279 std::stringstream ss2;
280 ss2 << ca_tmp_dir <<
"/crl_file.XXXXXX.pem";
281 std::vector<char> crl_fname;
282 crl_fname.resize(ss2.str().size() + 1);
283 memcpy(crl_fname.data(), ss2.str().c_str(), ss2.str().size());
285 int crl_fd = mkstemps(crl_fname.data(), 4);
287 err.
Emsg(
"TempCA",
"Failed to create temp file:", strerror(errno));
288 return std::unique_ptr<TempCAGuard>();
290 return std::unique_ptr<TempCAGuard>(
new TempCAGuard(ca_fd, crl_fd, ca_tmp_dir, ca_fname.data(), crl_fname.data()));
296 unlink(m_ca_fname.c_str());
300 unlink(m_crl_fname.c_str());
308 if (m_ca_fd < 0 || m_ca_tmp_dir.empty()) {
return false;}
311 std::string ca_fname = m_ca_tmp_dir +
"/ca_file.pem";
312 if (-1 ==
rename(m_ca_fname.c_str(), ca_fname.c_str())) {
315 m_ca_fname = ca_fname;
317 if (m_crl_fd < 0 || m_ca_tmp_dir.empty()) {
return false;}
320 std::string crl_fname = m_ca_tmp_dir +
"/crl_file.pem";
321 if (-1 ==
rename(m_crl_fname.c_str(), crl_fname.c_str())) {
324 m_crl_fname = crl_fname;
331 : m_ca_fd(ca_fd), m_crl_fd(crl_fd), m_ca_tmp_dir(ca_tmp_dir), m_ca_fname(ca_fname), m_crl_fname(crl_fname)
338 m_build_store(build_store)
343 if (-1 == XrdSysFD_Pipe(pipes)) {
344 m_log.Emsg(
"XrdTlsTempCA",
"Failed to create communication pipes", strerror(errno));
347 m_maintenance_pipe_r = pipes[0];
348 m_maintenance_pipe_w = pipes[1];
349 if (-1 == XrdSysFD_Pipe(pipes)) {
350 m_log.Emsg(
"XrdTlsTempCA",
"Failed to create communication pipes", strerror(errno));
353 m_maintenance_thread_pipe_r = pipes[0];
354 m_maintenance_thread_pipe_w = pipes[1];
355 if (!Maintenance()) {
return;}
359 static_cast<void*
>(
this), 0,
"CA/CRL refresh");
361 m_log.Emsg(
"XrdTlsTempCA",
"Failed to launch CA monitoring thread");
371 if (m_maintenance_pipe_w >= 0) {
374 do {rval =
write(m_maintenance_pipe_w, indicator, 1);}
while (rval != -1 || errno == EINTR);
375 if (m_maintenance_thread_pipe_r >= 0) {
376 do {rval =
read(m_maintenance_thread_pipe_r, indicator, 1);}
while (rval != -1 || errno == EINTR);
377 close(m_maintenance_thread_pipe_r);
378 close(m_maintenance_thread_pipe_w);
380 close(m_maintenance_pipe_r);
381 close(m_maintenance_pipe_w);
386std::shared_ptr<X509_STORE>
387XrdTlsTempCA::BuildCAStore(
const std::string &ca_fname,
const std::string &crl_fname,
390 std::shared_ptr<X509_STORE> store(X509_STORE_new(), &X509_STORE_free);
392 m_log.
Emsg(
"TempCA",
"Failed to allocate a certificate store");
396 if (1 != X509_STORE_load_locations(store.get(), ca_fname.c_str(),
nullptr)) {
397 m_log.Emsg(
"TempCA",
"Failed to load the CA bundle into the certificate store",
406 unsigned long x509flags;
409 X509_LOOKUP *lookup = X509_STORE_add_lookup(store.get(), X509_LOOKUP_file());
411 m_log.Emsg(
"TempCA",
"Failed to add a file lookup to the certificate store");
414 if (X509_load_crl_file(lookup, crl_fname.c_str(), X509_FILETYPE_PEM) <= 0) {
415 m_log.Emsg(
"TempCA",
"Failed to load the CRL bundle into the certificate store",
419 x509flags = X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL;
427 x509flags = X509_V_FLAG_PARTIAL_CHAIN;
430 X509_STORE_set_flags(store.get(), x509flags);
437 sk_X509_OBJECT_sort(X509_STORE_get0_objects(store.get()));
444XrdTlsTempCA::Maintenance()
446 m_log.Emsg(
"TempCA",
"Reloading the list of CAs and CRLs in directory");
448 auto adminpath = getenv(
"XRDADMINPATH");
450 m_log.Emsg(
"TempCA",
"Admin path is not set!");
453 std::string ca_tmp_dir = std::string(adminpath) +
"/.xrdtls";
457 m_log.Emsg(
"TempCA",
"Failed to create a new temp CA / CRL file");
461 int fddir = XrdSysFD_Open(m_ca_dir.c_str(), O_DIRECTORY);
463 m_log.Emsg(
"TempCA",
"Failed to open the CA directory", m_ca_dir.c_str());
467 DIR *dirp = fdopendir(fddir);
469 m_log.Emsg(
"Maintenance",
"Failed to allocate a directory pointer");
473 struct dirent *result;
474 bool atLeastOneCRLFound =
false;
477 CASet ca_builder(new_file->getCAFD(), m_log);
478 CRLSet crl_builder(new_file->getCRLFD(), m_log);
479 while ((result =
readdir(dirp))) {
481 if (result->d_name[0] ==
'.') {
continue;}
482 if (result->d_type != DT_REG)
483 {
if (result->d_type != DT_UNKNOWN && result->d_type != DT_LNK)
487 {m_log.Emsg(
"Maintenance",
"Failed to stat certificate file",
488 result->d_name, strerror(errno));
491 if (!S_ISREG(
Stat.st_mode))
continue;
493 int fd = XrdSysFD_Openat(fddir, result->d_name, O_RDONLY);
495 m_log.Emsg(
"Maintenance",
"Failed to open certificate file", result->d_name, strerror(errno));
499 file_smart_ptr fp(fdopen(fd,
"r"), &
fclose);
501 if (!ca_builder.processFile(fp, result->d_name)) {
502 m_log.Emsg(
"Maintenance",
"Failed to process file for CAs", result->d_name);
505 if (!crl_builder.processFile(fp, result->d_name)) {
506 m_log.Emsg(
"Maintenance",
"Failed to process file for CRLs", result->d_name);
511 m_log.Emsg(
"Maintenance",
"Failure during readdir", strerror(errno));
517 if (!crl_builder.processCRLWithCriticalExt()) {
518 m_log.Emsg(
"Maintenance",
"Failed to insert CRLs with critical extension for CRLs", result->d_name);
520 atLeastOneCRLFound = crl_builder.atLeastOneValidCRLFound();
523 if (!new_file->commit()) {
524 m_log.Emsg(
"Maintenance",
"Failed to finalize new CA / CRL files");
529 const std::string ca_fname = new_file->getCAFilename();
530 const std::string crl_fname = new_file->getCRLFilename();
532 std::shared_ptr<X509_STORE> new_store;
537 struct stat crl_stat;
538 const bool use_crls = atLeastOneCRLFound
539 && !
stat(crl_fname.c_str(), &crl_stat)
540 && crl_stat.st_size > 0;
542 std::stringstream ss;
543 ss <<
"No valid CRL file has been found in the file " << crl_fname
544 <<
". Disabling CRL checking.";
545 m_log.Emsg(
"Maintenance", ss.str().c_str());
551 new_store = BuildCAStore(ca_fname, crl_fname, use_crls);
559 m_log.Emsg(
"Maintenance",
"Failed to build the certificate store; "
560 "retaining the previously loaded CAs and CRLs");
565 XrdSysMutexHelper lock(m_mutex);
566 m_ca_file.reset(
new std::string(ca_fname));
567 m_crl_file.reset(
new std::string(crl_fname));
568 m_atLeastOneCRLFound = atLeastOneCRLFound;
569 m_ca_store = std::move(new_store);
575void *XrdTlsTempCA::MaintenanceThread(
void *myself_raw)
579 auto now = monotonic_time_s();
580 auto next_update = now + m_update_interval;
582 now = monotonic_time_s();
583 auto remaining = next_update - now;
585 fds.fd = myself->m_maintenance_pipe_r;
587 auto rval = poll(&fds, 1, remaining*1000);
589 if (rval == EINTR)
continue;
591 }
else if (rval == 0) {
592 if (myself->Maintenance()) {
593 next_update = monotonic_time_s() + m_update_interval;
595 next_update = monotonic_time_s() + m_update_interval_failure;
598 if (fds.revents & POLLIN) {
600 do {rval =
read(myself->m_maintenance_pipe_r, indicator, 1);}
while (rval != -1 || errno == EINTR);
605 myself->m_log.Emsg(
"Maintenance",
"Failed to poll for events from parent object");
607 char indicator =
'1';
609 do {rval =
write(myself->m_maintenance_thread_pipe_w, &indicator, 1);}
while (rval != -1 || errno == EINTR);
int XrdCryptosslX509ToFile(XrdCryptoX509 *x509, FILE *file, const char *fname)
int XrdCryptosslX509ParseFile(const char *fname, XrdCryptoX509Chain *chain, const char *fkey)
#define fstatat(a, b, c, d)
void Cleanup(bool keepCA=0)
XrdCryptosslX509Crl(const char *crlf, int opt=0)
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
static int Run(pthread_t *, void *(*proc)(void *), void *arg, int opts=0, const char *desc=0)
static std::unique_ptr< TempCAGuard > create(XrdSysError &, const std::string &ca_tmp_dir)
TempCAGuard(const TempCAGuard &)=delete
XrdTlsTempCA(XrdSysError *log, std::string ca_dir, bool build_store=true)