XRootD
XrdTlsTempCA.cc
Go to the documentation of this file.
1 /******************************************************************************/
2 /* */
3 /* X r d T l s T e m p C A . c c */
4 /* */
5 /* (c) 2021 by the Board of Trustees of the Leland Stanford, Jr., University */
6 /* Produced by Brian Bockelman */
7 /* */
8 /* This file is part of the XRootD software suite. */
9 /* */
10 /* XRootD is free software: you can redistribute it and/or modify it under */
11 /* the terms of the GNU Lesser General Public License as published by the */
12 /* Free Software Foundation, either version 3 of the License, or (at your */
13 /* option) any later version. */
14 /* */
15 /* XRootD is distributed in the hope that it will be useful, but WITHOUT */
16 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
17 /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
18 /* License for more details. */
19 /* */
20 /* You should have received a copy of the GNU Lesser General Public License */
21 /* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
22 /* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
23 /* */
24 /* The copyright holder's institutional names and contributor's names may not */
25 /* be used to endorse or promote products derived from this software without */
26 /* specific prior written permission of the institution or contributor. */
27 /******************************************************************************/
28 
29 
30 #include <cstdlib>
31 #include <fcntl.h>
32 #include <dirent.h>
33 #include <poll.h>
34 
35 #include <unordered_set>
36 #include <memory>
37 
38 #include "XrdSys/XrdSysError.hh"
39 #include "XrdSys/XrdSysFD.hh"
40 #include "XrdSys/XrdSysPlugin.hh"
44 
45 #include "XrdTlsTempCA.hh"
46 
47 #include <sstream>
48 #include <vector>
49 #include <atomic>
50 
51 namespace {
52 
53 typedef std::unique_ptr<FILE, int(*)(FILE*)> file_smart_ptr;
54 
55 
56 static uint64_t monotonic_time_s() {
57  struct timespec tp;
58  clock_gettime(CLOCK_MONOTONIC, &tp);
59  return tp.tv_sec + (tp.tv_nsec >= 500000000);
60 }
61 
66 class Set {
67 public:
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()) {
70  m_output_fp.reset();
71  }
72  }
73  virtual ~Set() = default;
74 protected:
75  // Reference to the logging that can be used by the inheriting classes.
76  XrdSysError &m_log;
77  // Pointer to the CA or CRL output file
78  file_smart_ptr m_output_fp;
79 };
80 
81 class CASet : public Set {
82 public:
83  CASet(int output_fd, XrdSysError &err):Set(output_fd,err){}
84 
96  bool processFile(file_smart_ptr &fd, const std::string &fname);
97 
98 private:
99 
100  // Grid CA directories tend to keep everything in triplicate;
101  // we keep a unique hash of all known CAs so we write out each
102  // one only once.
103  std::unordered_set<std::string> m_known_cas;
104 };
105 
106 
107 bool
108 CASet::processFile(file_smart_ptr &fp, const std::string &fname)
109 {
110  XrdCryptoX509Chain chain;
111  // Not checking return value here; function returns `0` on error and
112  // if no certificate is found.
113  XrdCryptosslX509ParseFile(fp.get(), &chain, fname.c_str());
114 
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());
118  chain.Cleanup();
119  return false;
120  }
121  while (ca) {
122  auto hash_ptr = ca->SubjectHash();
123  if (!hash_ptr) {
124  continue;
125  }
126  auto iter = m_known_cas.find(hash_ptr);
127  if (iter != m_known_cas.end()) {
128  //m_log.Emsg("CAset", "Skipping known CA with hash", fname.c_str(), hash_ptr);
129  ca = chain.Next();
130  continue;
131  }
132  //m_log.Emsg("CAset", "New CA with hash", fname.c_str(), hash_ptr);
133  m_known_cas.insert(hash_ptr);
134 
135  if (XrdCryptosslX509ToFile(ca, m_output_fp.get(), fname.c_str())) {
136  m_log.Emsg("CAset", "Failed to write out CA", fname.c_str());
137  chain.Cleanup();
138  return false;
139  }
140  ca = chain.Next();
141  }
142  fflush(m_output_fp.get());
143  chain.Cleanup();
144 
145  return true;
146 }
147 
148 
149 class CRLSet : public Set {
150 public:
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();
177 
178 private:
179 
180  // Grid CA directories tend to keep everything in triplicate;
181  // we keep a unique hash of all known CRLs so we write out each
182  // one only once.
183  std::unordered_set<std::string> m_known_crls;
184  std::atomic<bool> m_atLeastOneValidCRLFound;
185  //Store the CRLs containing critical extensions to defer their insertion
186  //at the end of the bundled CRL file. Issue https://github.com/xrootd/xrootd/issues/2065
187  std::vector<std::unique_ptr<XrdCryptosslX509Crl>> m_crls_critical_extension;
188 };
189 
190 
191 bool
192 CRLSet::processFile(file_smart_ptr &fp, const std::string &fname)
193 {
194  if (!m_output_fp.get()) {
195  m_log.Emsg("CRLSet", "No output file has been opened", fname.c_str());
196  return false;
197  }
198  // Assume we can safely ignore a failure to parse; we load every file in
199  // the directory and that will naturally include a number of non-CRL files.
200  for (std::unique_ptr<XrdCryptosslX509Crl> xrd_crl(new XrdCryptosslX509Crl(fp.get(), fname.c_str()));
201  xrd_crl->IsValid();
202  xrd_crl = std::unique_ptr<XrdCryptosslX509Crl>(new XrdCryptosslX509Crl(fp.get(), fname.c_str())))
203  {
204  auto hash_ptr = xrd_crl->IssuerHash(1);
205  if (!hash_ptr) {
206  continue;
207  }
208  m_atLeastOneValidCRLFound = true;
209  auto iter = m_known_crls.find(hash_ptr);
210  if (iter != m_known_crls.end()) {
211  //m_log.Emsg("CRLset", "Skipping known CRL with hash", fname.c_str(), hash_ptr);
212  continue;
213  }
214  //m_log.Emsg("CRLset", "New CRL with hash", fname.c_str(), hash_ptr);
215  m_known_crls.insert(hash_ptr);
216 
217  if(xrd_crl->hasCriticalExtension()) {
218  // Issue https://github.com/xrootd/xrootd/issues/2065
219  // This CRL will be put at the end of the bundled file
220  m_crls_critical_extension.emplace_back(std::move(xrd_crl));
221  } else {
222  // No critical extension found on that CRL, just insert it on the CRL bundled file
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());
226  return false;
227  }
228  }
229  }
230  fflush(m_output_fp.get());
231 
232  return true;
233 }
234 
235 bool CRLSet::atLeastOneValidCRLFound() const {
236  return m_atLeastOneValidCRLFound;
237 }
238 
239 bool 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");
243  return false;
244  }
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());
249  return false;
250  }
251  }
252  fflush(m_output_fp.get());
253  }
254  return true;
255 }
256 
257 }
258 
259 
260 std::unique_ptr<XrdTlsTempCA::TempCAGuard>
261 XrdTlsTempCA::TempCAGuard::create(XrdSysError &err, const std::string &ca_tmp_dir) {
262 
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));
265  }
266 
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());
272 
273  int ca_fd = mkstemps(ca_fname.data(), 4);
274  if (ca_fd < 0) {
275  err.Emsg("TempCA", "Failed to create temp file:", strerror(errno));
276  return std::unique_ptr<TempCAGuard>();
277  }
278 
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());
284 
285  int crl_fd = mkstemps(crl_fname.data(), 4);
286  if (crl_fd < 0) {
287  err.Emsg("TempCA", "Failed to create temp file:", strerror(errno));
288  return std::unique_ptr<TempCAGuard>();
289  }
290  return std::unique_ptr<TempCAGuard>(new TempCAGuard(ca_fd, crl_fd, ca_tmp_dir, ca_fname.data(), crl_fname.data()));
291 }
292 
293 
295  if (m_ca_fd >= 0) {
296  unlink(m_ca_fname.c_str());
297  close(m_ca_fd);
298  }
299  if (m_crl_fd >= 0) {
300  unlink(m_crl_fname.c_str());
301  close(m_crl_fd);
302  }
303 }
304 
305 
306 bool
308  if (m_ca_fd < 0 || m_ca_tmp_dir.empty()) {return false;}
309  close(m_ca_fd);
310  m_ca_fd = -1;
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())) {
313  return false;
314  }
315  m_ca_fname = ca_fname;
316 
317  if (m_crl_fd < 0 || m_ca_tmp_dir.empty()) {return false;}
318  close(m_crl_fd);
319  m_crl_fd = -1;
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())) {
322  return false;
323  }
324  m_crl_fname = crl_fname;
325 
326  return true;
327 }
328 
329 
330 XrdTlsTempCA::TempCAGuard::TempCAGuard(int ca_fd, int crl_fd, const std::string &ca_tmp_dir, const std::string &ca_fname, const std::string &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)
332  {}
333 
334 
335 XrdTlsTempCA::XrdTlsTempCA(XrdSysError *err, std::string ca_dir, bool build_store)
336  : m_log(*err),
337  m_ca_dir(ca_dir),
338  m_build_store(build_store)
339 {
340  // Setup communication pipes; we write one byte to the child to tell it to shutdown;
341  // it'll write one byte back to acknowledge before our destructor exits.
342  int pipes[2];
343  if (-1 == XrdSysFD_Pipe(pipes)) {
344  m_log.Emsg("XrdTlsTempCA", "Failed to create communication pipes", strerror(errno));
345  return;
346  }
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));
351  return;
352  }
353  m_maintenance_thread_pipe_r = pipes[0];
354  m_maintenance_thread_pipe_w = pipes[1];
355  if (!Maintenance()) {return;}
356 
357  pthread_t tid;
358  auto rc = XrdSysThread::Run(&tid, XrdTlsTempCA::MaintenanceThread,
359  static_cast<void*>(this), 0, "CA/CRL refresh");
360  if (rc) {
361  m_log.Emsg("XrdTlsTempCA", "Failed to launch CA monitoring thread");
362  m_ca_file.reset();
363  m_crl_file.reset();
364  }
365 }
366 
367 
369 {
370  char indicator[1];
371  if (m_maintenance_pipe_w >= 0) {
372  indicator[0] = '1';
373  int rval;
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);
379  }
380  close(m_maintenance_pipe_r);
381  close(m_maintenance_pipe_w);
382  }
383 }
384 
385 
386 std::shared_ptr<X509_STORE>
387 XrdTlsTempCA::BuildCAStore(const std::string &ca_fname, const std::string &crl_fname,
388  bool use_crls)
389 {
390  std::shared_ptr<X509_STORE> store(X509_STORE_new(), &X509_STORE_free);
391  if (!store) {
392  m_log.Emsg("TempCA", "Failed to allocate a certificate store");
393  return nullptr;
394  }
395 
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",
398  ca_fname.c_str());
399  return nullptr;
400  }
401 
402  // The verification flags below mirror what libcurl applies when it is handed
403  // these same files through CURLOPT_CAINFO / CURLOPT_CRLFILE; see
404  // ossl_populate_x509_store() in its lib/vtls/openssl.c. Consumers install this
405  // store in place of the one libcurl built, so any flag left out here is lost.
406  unsigned long x509flags;
407 
408  if (use_crls) {
409  X509_LOOKUP *lookup = X509_STORE_add_lookup(store.get(), X509_LOOKUP_file());
410  if (!lookup) {
411  m_log.Emsg("TempCA", "Failed to add a file lookup to the certificate store");
412  return nullptr;
413  }
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",
416  crl_fname.c_str());
417  return nullptr;
418  }
419  x509flags = X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL;
420  } else {
421  // Treat non-self-signed certificates in the store as trust anchors, so that
422  // a server can be verified from an intermediate alone. This is not an
423  // OpenSSL default, but libcurl enables it unless asked not to, so leaving it
424  // out would reject chains that are accepted today. It is deliberately not
425  // combined with CRL checking, which OpenSSL does not support:
426  // https://github.com/openssl/openssl/issues/5081
427  x509flags = X509_V_FLAG_PARTIAL_CHAIN;
428  }
429 
430  X509_STORE_set_flags(store.get(), x509flags);
431 
432  // Purely an optimization; OpenSSL sorts the store itself when it needs to.
433  // Adding objects to a store leaves its lookup stack unsorted, and OpenSSL
434  // sorts lazily on first use -- under a write lock, while every other thread
435  // verifying against this store waits. Sorting here means the concurrent
436  // verifications that follow a reload only ever need the read lock.
437  sk_X509_OBJECT_sort(X509_STORE_get0_objects(store.get()));
438 
439  return store;
440 }
441 
442 
443 bool
444 XrdTlsTempCA::Maintenance()
445 {
446  m_log.Emsg("TempCA", "Reloading the list of CAs and CRLs in directory");
447 
448  auto adminpath = getenv("XRDADMINPATH");
449  if (!adminpath) {
450  m_log.Emsg("TempCA", "Admin path is not set!");
451  return false;
452  }
453  std::string ca_tmp_dir = std::string(adminpath) + "/.xrdtls";
454 
455  std::unique_ptr<TempCAGuard> new_file(TempCAGuard::create(m_log, ca_tmp_dir));
456  if (!new_file) {
457  m_log.Emsg("TempCA", "Failed to create a new temp CA / CRL file");
458  return false;
459  }
460 
461  int fddir = XrdSysFD_Open(m_ca_dir.c_str(), O_DIRECTORY);
462  if (fddir < 0) {
463  m_log.Emsg("TempCA", "Failed to open the CA directory", m_ca_dir.c_str());
464  return false;
465  }
466 
467  DIR *dirp = fdopendir(fddir);
468  if (!dirp) {
469  m_log.Emsg("Maintenance", "Failed to allocate a directory pointer");
470  return false;
471  }
472 
473  struct dirent *result;
474  bool atLeastOneCRLFound = false;
475  errno = 0;
476  {
477  CASet ca_builder(new_file->getCAFD(), m_log);
478  CRLSet crl_builder(new_file->getCRLFD(), m_log);
479  while ((result = readdir(dirp))) {
480  //m_log.Emsg("Will parse file for CA certificates", result->d_name);
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)
484  continue;
485  struct stat Stat;
486  if (fstatat(fddir, result->d_name, &Stat, 0))
487  {m_log.Emsg("Maintenance", "Failed to stat certificate file",
488  result->d_name, strerror(errno));
489  continue;
490  }
491  if (!S_ISREG(Stat.st_mode)) continue;
492  }
493  int fd = XrdSysFD_Openat(fddir, result->d_name, O_RDONLY);
494  if (fd < 0) {
495  m_log.Emsg("Maintenance", "Failed to open certificate file", result->d_name, strerror(errno));
496  closedir(dirp);
497  return false;
498  }
499  file_smart_ptr fp(fdopen(fd, "r"), &fclose);
500 
501  if (!ca_builder.processFile(fp, result->d_name)) {
502  m_log.Emsg("Maintenance", "Failed to process file for CAs", result->d_name);
503  }
504  rewind(fp.get());
505  if (!crl_builder.processFile(fp, result->d_name)) {
506  m_log.Emsg("Maintenance", "Failed to process file for CRLs", result->d_name);
507  }
508  errno = 0;
509  }
510  if (errno) {
511  m_log.Emsg("Maintenance", "Failure during readdir", strerror(errno));
512  closedir(dirp);
513  return false;
514  }
515  closedir(dirp);
516 
517  if (!crl_builder.processCRLWithCriticalExt()) {
518  m_log.Emsg("Maintenance", "Failed to insert CRLs with critical extension for CRLs", result->d_name);
519  }
520  atLeastOneCRLFound = crl_builder.atLeastOneValidCRLFound();
521  }
522 
523  if (!new_file->commit()) {
524  m_log.Emsg("Maintenance", "Failed to finalize new CA / CRL files");
525  return false;
526  }
527  //m_log.Emsg("Maintenance", "Successfully created CA and CRL files", new_file->getCAFilename().c_str(),
528  // new_file->getCRLFilename().c_str());
529  const std::string ca_fname = new_file->getCAFilename();
530  const std::string crl_fname = new_file->getCRLFilename();
531 
532  std::shared_ptr<X509_STORE> new_store;
533  if (m_build_store) {
534  // An empty CRL bundle makes every verification fail, so CRL checking is only
535  // enabled once we know at least one CRL was written out.
536  // See https://github.com/xrootd/xrootd/issues/1543
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;
541  if (!use_crls) {
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());
546  }
547 
548  // Parse the bundles once here rather than once per consumer. This is the
549  // expensive part of a reload, so it is deliberately done before taking the
550  // lock that publishes the result.
551  new_store = BuildCAStore(ca_fname, crl_fname, use_crls);
552  if (!new_store) {
553  // Deliberately publish nothing and let the maintenance thread retry on
554  // the short interval, leaving consumers on the previous store. The
555  // tempting alternative -- carrying on and letting each consumer load the
556  // bundles for itself -- silently reinstates the per-transfer parsing that
557  // this store exists to avoid, turning a CA refresh problem into memory
558  // exhaustion. See https://github.com/xrootd/xrootd/issues/2873
559  m_log.Emsg("Maintenance", "Failed to build the certificate store; "
560  "retaining the previously loaded CAs and CRLs");
561  return false;
562  }
563  }
564 
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);
570 
571  return true;
572 }
573 
574 
575 void *XrdTlsTempCA::MaintenanceThread(void *myself_raw)
576 {
577  auto myself = static_cast<XrdTlsTempCA *>(myself_raw);
578 
579  auto now = monotonic_time_s();
580  auto next_update = now + m_update_interval;
581  while (true) {
582  now = monotonic_time_s();
583  auto remaining = next_update - now;
584  struct pollfd fds;
585  fds.fd = myself->m_maintenance_pipe_r;
586  fds.events = POLLIN;
587  auto rval = poll(&fds, 1, remaining*1000);
588  if (rval == -1) {
589  if (rval == EINTR) continue;
590  else break;
591  } else if (rval == 0) { // timeout! Let's run maintenance.
592  if (myself->Maintenance()) {
593  next_update = monotonic_time_s() + m_update_interval;
594  } else {
595  next_update = monotonic_time_s() + m_update_interval_failure;
596  }
597  } else { // FD ready; let's shutdown
598  if (fds.revents & POLLIN) {
599  char indicator[1];
600  do {rval = read(myself->m_maintenance_pipe_r, indicator, 1);} while (rval != -1 || errno == EINTR);
601  }
602  }
603  }
604  if (errno) {
605  myself->m_log.Emsg("Maintenance", "Failed to poll for events from parent object");
606  }
607  char indicator = '1';
608  int rval;
609  do {rval = write(myself->m_maintenance_thread_pipe_w, &indicator, 1);} while (rval != -1 || errno == EINTR);
610 
611  return nullptr;
612 }
struct stat Stat
Definition: XrdCks.cc:49
int XrdCryptosslX509ToFile(XrdCryptoX509 *x509, FILE *file, const char *fname)
int XrdCryptosslX509ParseFile(const char *fname, XrdCryptoX509Chain *chain, const char *fkey)
int rename(const char *oldpath, const char *newpath)
int mkdir(const char *path, mode_t mode)
int fclose(FILE *stream)
int closedir(DIR *dirp)
int fflush(FILE *stream)
ssize_t read(int fildes, void *buf, size_t nbyte)
#define close(a)
Definition: XrdPosix.hh:48
#define fstatat(a, b, c, d)
Definition: XrdPosix.hh:64
#define write(a, b, c)
Definition: XrdPosix.hh:121
#define unlink(a)
Definition: XrdPosix.hh:119
#define stat(a, b)
Definition: XrdPosix.hh:105
#define readdir(a)
Definition: XrdPosix.hh:90
XrdCryptoX509 * Next()
XrdCryptoX509 * Begin()
void Cleanup(bool keepCA=0)
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:116
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)