35 #include <unordered_set>
44 #include "XrdVersion.hh"
54 typedef std::unique_ptr<FILE, int(*)(FILE*)> file_smart_ptr;
57 static uint64_t monotonic_time_s() {
59 clock_gettime(CLOCK_MONOTONIC, &tp);
60 return tp.tv_sec + (tp.tv_nsec >= 500000000);
69 Set(
int output_fd,
XrdSysError & err) : m_log(err),m_output_fp(file_smart_ptr(fdopen(XrdSysFD_Dup(output_fd),
"w"), &
fclose)){
70 if(!m_output_fp.get()) {
74 virtual ~Set() =
default;
79 file_smart_ptr m_output_fp;
82 class CASet :
public Set {
84 CASet(
int output_fd,
XrdSysError &err):Set(output_fd,err){}
97 bool processFile(file_smart_ptr &fd,
const std::string &fname);
104 std::unordered_set<std::string> m_known_cas;
109 CASet::processFile(file_smart_ptr &fp,
const std::string &fname)
116 auto ca = chain.
Begin();
117 if (!m_output_fp.get()) {
118 m_log.Emsg(
"CAset",
"No output file has been opened", fname.c_str());
123 auto hash_ptr = ca->SubjectHash();
127 auto iter = m_known_cas.find(hash_ptr);
128 if (iter != m_known_cas.end()) {
134 m_known_cas.insert(hash_ptr);
137 m_log.Emsg(
"CAset",
"Failed to write out CA", fname.c_str());
143 fflush(m_output_fp.get());
150 class CRLSet :
public Set {
152 CRLSet(
int output_fd,
XrdSysError &err):Set(output_fd,err){}
164 bool processFile(file_smart_ptr &fd,
const std::string &fname);
170 bool atLeastOneValidCRLFound()
const;
177 bool processCRLWithCriticalExt();
184 std::unordered_set<std::string> m_known_crls;
185 std::atomic<bool> m_atLeastOneValidCRLFound;
188 std::vector<std::unique_ptr<XrdCryptosslX509Crl>> m_crls_critical_extension;
193 CRLSet::processFile(file_smart_ptr &fp,
const std::string &fname)
195 if (!m_output_fp.get()) {
196 m_log.Emsg(
"CRLSet",
"No output file has been opened", fname.c_str());
201 for (std::unique_ptr<XrdCryptosslX509Crl> xrd_crl(
new XrdCryptosslX509Crl(fp.get(), fname.c_str()));
203 xrd_crl = std::unique_ptr<XrdCryptosslX509Crl>(
new XrdCryptosslX509Crl(fp.get(), fname.c_str())))
205 auto hash_ptr = xrd_crl->IssuerHash(1);
209 m_atLeastOneValidCRLFound =
true;
210 auto iter = m_known_crls.find(hash_ptr);
211 if (iter != m_known_crls.end()) {
216 m_known_crls.insert(hash_ptr);
218 if(xrd_crl->hasCriticalExtension()) {
221 m_crls_critical_extension.emplace_back(std::move(xrd_crl));
224 if (!xrd_crl->ToFile(m_output_fp.get())) {
225 m_log.Emsg(
"CRLset",
"Failed to write out CRL", fname.c_str());
226 fflush(m_output_fp.get());
231 fflush(m_output_fp.get());
236 bool CRLSet::atLeastOneValidCRLFound()
const {
237 return m_atLeastOneValidCRLFound;
240 bool CRLSet::processCRLWithCriticalExt() {
241 if(!m_crls_critical_extension.empty()) {
242 if (!m_output_fp.get()) {
243 m_log.Emsg(
"CRLSet",
"No output file has been opened to add CRLs with critical extension");
246 for (
const auto &crl: m_crls_critical_extension) {
247 if (!crl->ToFile(m_output_fp.get())) {
248 m_log.Emsg(
"CRLset",
"Failed to write out CRL with critical extension", crl->ParentFile());
249 fflush(m_output_fp.get());
253 fflush(m_output_fp.get());
261 std::unique_ptr<XrdTlsTempCA::TempCAGuard>
264 if (-1 ==
mkdir(ca_tmp_dir.c_str(), S_IRWXU) && errno != EEXIST) {
265 err.
Emsg(
"TempCA",
"Unable to create CA temp directory", ca_tmp_dir.c_str(), strerror(errno));
268 std::stringstream ss;
269 ss << ca_tmp_dir <<
"/ca_file.XXXXXX.pem";
270 std::vector<char> ca_fname;
271 ca_fname.resize(ss.str().size() + 1);
272 memcpy(ca_fname.data(), ss.str().c_str(), ss.str().size());
274 int ca_fd = mkstemps(ca_fname.data(), 4);
276 err.
Emsg(
"TempCA",
"Failed to create temp file:", strerror(errno));
277 return std::unique_ptr<TempCAGuard>();
280 std::stringstream ss2;
281 ss2 << ca_tmp_dir <<
"/crl_file.XXXXXX.pem";
282 std::vector<char> crl_fname;
283 crl_fname.resize(ss2.str().size() + 1);
284 memcpy(crl_fname.data(), ss2.str().c_str(), ss2.str().size());
286 int crl_fd = mkstemps(crl_fname.data(), 4);
288 err.
Emsg(
"TempCA",
"Failed to create temp file:", strerror(errno));
289 return std::unique_ptr<TempCAGuard>();
291 return std::unique_ptr<TempCAGuard>(
new TempCAGuard(ca_fd, crl_fd, ca_tmp_dir, ca_fname.data(), crl_fname.data()));
297 unlink(m_ca_fname.c_str());
301 unlink(m_crl_fname.c_str());
309 if (m_ca_fd < 0 || m_ca_tmp_dir.empty()) {
return false;}
312 std::string ca_fname = m_ca_tmp_dir +
"/ca_file.pem";
313 if (-1 ==
rename(m_ca_fname.c_str(), ca_fname.c_str())) {
316 m_ca_fname = ca_fname;
318 if (m_crl_fd < 0 || m_ca_tmp_dir.empty()) {
return false;}
321 std::string crl_fname = m_ca_tmp_dir +
"/crl_file.pem";
322 if (-1 ==
rename(m_crl_fname.c_str(), crl_fname.c_str())) {
325 m_crl_fname = crl_fname;
332 : 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)
339 m_build_store(build_store)
344 if (-1 == XrdSysFD_Pipe(pipes)) {
345 m_log.
Emsg(
"XrdTlsTempCA",
"Failed to create communication pipes", strerror(errno));
348 m_maintenance_pipe_r = pipes[0];
349 m_maintenance_pipe_w = pipes[1];
350 if (-1 == XrdSysFD_Pipe(pipes)) {
351 m_log.
Emsg(
"XrdTlsTempCA",
"Failed to create communication pipes", strerror(errno));
354 m_maintenance_thread_pipe_r = pipes[0];
355 m_maintenance_thread_pipe_w = pipes[1];
356 if (!Maintenance()) {
return;}
360 static_cast<void*
>(
this), 0,
"CA/CRL refresh");
362 m_log.
Emsg(
"XrdTlsTempCA",
"Failed to launch CA monitoring thread");
372 if (m_maintenance_pipe_w >= 0) {
375 do {rval =
write(m_maintenance_pipe_w, indicator, 1);}
while (rval != -1 || errno == EINTR);
376 if (m_maintenance_thread_pipe_r >= 0) {
377 do {rval =
read(m_maintenance_thread_pipe_r, indicator, 1);}
while (rval != -1 || errno == EINTR);
378 close(m_maintenance_thread_pipe_r);
379 close(m_maintenance_thread_pipe_w);
381 close(m_maintenance_pipe_r);
382 close(m_maintenance_pipe_w);
387 std::shared_ptr<X509_STORE>
388 XrdTlsTempCA::BuildCAStore(
const std::string &ca_fname,
const std::string &crl_fname,
391 std::shared_ptr<X509_STORE> store(X509_STORE_new(), &X509_STORE_free);
393 m_log.
Emsg(
"TempCA",
"Failed to allocate a certificate store");
397 if (1 != X509_STORE_load_locations(store.get(), ca_fname.c_str(),
nullptr)) {
398 m_log.
Emsg(
"TempCA",
"Failed to load the CA bundle into the certificate store",
407 unsigned long x509flags;
410 X509_LOOKUP *lookup = X509_STORE_add_lookup(store.get(), X509_LOOKUP_file());
412 m_log.
Emsg(
"TempCA",
"Failed to add a file lookup to the certificate store");
415 if (X509_load_crl_file(lookup, crl_fname.c_str(), X509_FILETYPE_PEM) <= 0) {
416 m_log.
Emsg(
"TempCA",
"Failed to load the CRL bundle into the certificate store",
420 x509flags = X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL;
428 x509flags = X509_V_FLAG_PARTIAL_CHAIN;
431 X509_STORE_set_flags(store.get(), x509flags);
438 sk_X509_OBJECT_sort(X509_STORE_get0_objects(store.get()));
445 XrdTlsTempCA::Maintenance()
447 m_log.
Emsg(
"TempCA",
"Reloading the list of CAs and CRLs in directory");
449 auto adminpath = getenv(
"XRDADMINPATH");
451 m_log.
Emsg(
"TempCA",
"Admin path is not set!");
454 std::string ca_tmp_dir = std::string(adminpath) +
"/.xrdtls";
458 m_log.
Emsg(
"TempCA",
"Failed to create a new temp CA / CRL file");
462 int fddir = XrdSysFD_Open(m_ca_dir.c_str(), O_DIRECTORY);
464 m_log.
Emsg(
"TempCA",
"Failed to open the CA directory", m_ca_dir.c_str());
468 DIR *dirp = fdopendir(fddir);
470 m_log.
Emsg(
"Maintenance",
"Failed to allocate a directory pointer");
474 struct dirent *result;
475 bool atLeastOneCRLFound =
false;
478 CASet ca_builder(new_file->getCAFD(), m_log);
479 CRLSet crl_builder(new_file->getCRLFD(), m_log);
480 while ((result =
readdir(dirp))) {
482 if (result->d_name[0] ==
'.') {
continue;}
483 if (result->d_type != DT_REG)
484 {
if (result->d_type != DT_UNKNOWN && result->d_type != DT_LNK)
487 if (fstatat(fddir, result->d_name, &
Stat, 0))
488 {m_log.
Emsg(
"Maintenance",
"Failed to stat certificate file",
489 result->d_name, strerror(errno));
492 if (!S_ISREG(
Stat.st_mode))
continue;
494 int fd = XrdSysFD_Openat(fddir, result->d_name, O_RDONLY);
496 m_log.
Emsg(
"Maintenance",
"Failed to open certificate file", result->d_name, strerror(errno));
500 file_smart_ptr fp(fdopen(fd,
"r"), &
fclose);
502 if (!ca_builder.processFile(fp, result->d_name)) {
503 m_log.
Emsg(
"Maintenance",
"Failed to process file for CAs", result->d_name);
506 if (!crl_builder.processFile(fp, result->d_name)) {
507 m_log.
Emsg(
"Maintenance",
"Failed to process file for CRLs", result->d_name);
512 m_log.
Emsg(
"Maintenance",
"Failure during readdir", strerror(errno));
518 if (!crl_builder.processCRLWithCriticalExt()) {
519 m_log.
Emsg(
"Maintenance",
"Failed to insert CRLs with critical extension for CRLs", result->d_name);
521 atLeastOneCRLFound = crl_builder.atLeastOneValidCRLFound();
524 if (!new_file->commit()) {
525 m_log.
Emsg(
"Maintenance",
"Failed to finalize new CA / CRL files");
530 const std::string ca_fname = new_file->getCAFilename();
531 const std::string crl_fname = new_file->getCRLFilename();
533 std::shared_ptr<X509_STORE> new_store;
538 struct stat crl_stat;
539 const bool use_crls = atLeastOneCRLFound
540 && !
stat(crl_fname.c_str(), &crl_stat)
541 && crl_stat.st_size > 0;
543 std::stringstream ss;
544 ss <<
"No valid CRL file has been found in the file " << crl_fname
545 <<
". Disabling CRL checking.";
546 m_log.
Emsg(
"Maintenance", ss.str().c_str());
552 new_store = BuildCAStore(ca_fname, crl_fname, use_crls);
560 m_log.
Emsg(
"Maintenance",
"Failed to build the certificate store; "
561 "retaining the previously loaded CAs and CRLs");
567 m_ca_file.reset(
new std::string(ca_fname));
568 m_crl_file.reset(
new std::string(crl_fname));
569 m_atLeastOneCRLFound = atLeastOneCRLFound;
570 m_ca_store = std::move(new_store);
576 void *XrdTlsTempCA::MaintenanceThread(
void *myself_raw)
580 auto now = monotonic_time_s();
581 auto next_update = now + m_update_interval;
583 now = monotonic_time_s();
584 auto remaining = next_update - now;
586 fds.fd = myself->m_maintenance_pipe_r;
588 auto rval = poll(&fds, 1, remaining*1000);
590 if (rval == EINTR)
continue;
592 }
else if (rval == 0) {
593 if (myself->Maintenance()) {
594 next_update = monotonic_time_s() + m_update_interval;
596 next_update = monotonic_time_s() + m_update_interval_failure;
599 if (fds.revents & POLLIN) {
601 do {rval =
read(myself->m_maintenance_pipe_r, indicator, 1);}
while (rval != -1 || errno == EINTR);
606 myself->m_log.Emsg(
"Maintenance",
"Failed to poll for events from parent object");
608 char indicator =
'1';
610 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)
int unlink(const char *path)
int rename(const char *oldpath, const char *newpath)
int mkdir(const char *path, mode_t mode)
ssize_t write(int fildes, const void *buf, size_t nbyte)
ssize_t read(int fildes, void *buf, size_t nbyte)
void Cleanup(bool keepCA=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)