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 #include "XrdVersion.hh"
45 
46 #include "XrdTlsTempCA.hh"
47 
48 #include <sstream>
49 #include <vector>
50 #include <atomic>
51 
52 namespace {
53 
54 typedef std::unique_ptr<FILE, int(*)(FILE*)> file_smart_ptr;
55 
56 
57 static uint64_t monotonic_time_s() {
58  struct timespec tp;
59  clock_gettime(CLOCK_MONOTONIC, &tp);
60  return tp.tv_sec + (tp.tv_nsec >= 500000000);
61 }
62 
67 class Set {
68 public:
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()) {
71  m_output_fp.reset();
72  }
73  }
74  virtual ~Set() = default;
75 protected:
76  // Reference to the logging that can be used by the inheriting classes.
77  XrdSysError &m_log;
78  // Pointer to the CA or CRL output file
79  file_smart_ptr m_output_fp;
80 };
81 
82 class CASet : public Set {
83 public:
84  CASet(int output_fd, XrdSysError &err):Set(output_fd,err){}
85 
97  bool processFile(file_smart_ptr &fd, const std::string &fname);
98 
99 private:
100 
101  // Grid CA directories tend to keep everything in triplicate;
102  // we keep a unique hash of all known CAs so we write out each
103  // one only once.
104  std::unordered_set<std::string> m_known_cas;
105 };
106 
107 
108 bool
109 CASet::processFile(file_smart_ptr &fp, const std::string &fname)
110 {
111  XrdCryptoX509Chain chain;
112  // Not checking return value here; function returns `0` on error and
113  // if no certificate is found.
114  XrdCryptosslX509ParseFile(fp.get(), &chain, fname.c_str());
115 
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());
119  chain.Cleanup();
120  return false;
121  }
122  while (ca) {
123  auto hash_ptr = ca->SubjectHash();
124  if (!hash_ptr) {
125  continue;
126  }
127  auto iter = m_known_cas.find(hash_ptr);
128  if (iter != m_known_cas.end()) {
129  //m_log.Emsg("CAset", "Skipping known CA with hash", fname.c_str(), hash_ptr);
130  ca = chain.Next();
131  continue;
132  }
133  //m_log.Emsg("CAset", "New CA with hash", fname.c_str(), hash_ptr);
134  m_known_cas.insert(hash_ptr);
135 
136  if (XrdCryptosslX509ToFile(ca, m_output_fp.get(), fname.c_str())) {
137  m_log.Emsg("CAset", "Failed to write out CA", fname.c_str());
138  chain.Cleanup();
139  return false;
140  }
141  ca = chain.Next();
142  }
143  fflush(m_output_fp.get());
144  chain.Cleanup();
145 
146  return true;
147 }
148 
149 
150 class CRLSet : public Set {
151 public:
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();
178 
179 private:
180 
181  // Grid CA directories tend to keep everything in triplicate;
182  // we keep a unique hash of all known CRLs so we write out each
183  // one only once.
184  std::unordered_set<std::string> m_known_crls;
185  std::atomic<bool> m_atLeastOneValidCRLFound;
186  //Store the CRLs containing critical extensions to defer their insertion
187  //at the end of the bundled CRL file. Issue https://github.com/xrootd/xrootd/issues/2065
188  std::vector<std::unique_ptr<XrdCryptosslX509Crl>> m_crls_critical_extension;
189 };
190 
191 
192 bool
193 CRLSet::processFile(file_smart_ptr &fp, const std::string &fname)
194 {
195  if (!m_output_fp.get()) {
196  m_log.Emsg("CRLSet", "No output file has been opened", fname.c_str());
197  return false;
198  }
199  // Assume we can safely ignore a failure to parse; we load every file in
200  // the directory and that will naturally include a number of non-CRL files.
201  for (std::unique_ptr<XrdCryptosslX509Crl> xrd_crl(new XrdCryptosslX509Crl(fp.get(), fname.c_str()));
202  xrd_crl->IsValid();
203  xrd_crl = std::unique_ptr<XrdCryptosslX509Crl>(new XrdCryptosslX509Crl(fp.get(), fname.c_str())))
204  {
205  auto hash_ptr = xrd_crl->IssuerHash(1);
206  if (!hash_ptr) {
207  continue;
208  }
209  m_atLeastOneValidCRLFound = true;
210  auto iter = m_known_crls.find(hash_ptr);
211  if (iter != m_known_crls.end()) {
212  //m_log.Emsg("CRLset", "Skipping known CRL with hash", fname.c_str(), hash_ptr);
213  continue;
214  }
215  //m_log.Emsg("CRLset", "New CRL with hash", fname.c_str(), hash_ptr);
216  m_known_crls.insert(hash_ptr);
217 
218  if(xrd_crl->hasCriticalExtension()) {
219  // Issue https://github.com/xrootd/xrootd/issues/2065
220  // This CRL will be put at the end of the bundled file
221  m_crls_critical_extension.emplace_back(std::move(xrd_crl));
222  } else {
223  // No critical extension found on that CRL, just insert it on the CRL bundled file
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());
227  return false;
228  }
229  }
230  }
231  fflush(m_output_fp.get());
232 
233  return true;
234 }
235 
236 bool CRLSet::atLeastOneValidCRLFound() const {
237  return m_atLeastOneValidCRLFound;
238 }
239 
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");
244  return false;
245  }
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());
250  return false;
251  }
252  }
253  fflush(m_output_fp.get());
254  }
255  return true;
256 }
257 
258 }
259 
260 
261 std::unique_ptr<XrdTlsTempCA::TempCAGuard>
262 XrdTlsTempCA::TempCAGuard::create(XrdSysError &err, const std::string &ca_tmp_dir) {
263 
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));
266  }
267 
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());
273 
274  int ca_fd = mkstemps(ca_fname.data(), 4);
275  if (ca_fd < 0) {
276  err.Emsg("TempCA", "Failed to create temp file:", strerror(errno));
277  return std::unique_ptr<TempCAGuard>();
278  }
279 
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());
285 
286  int crl_fd = mkstemps(crl_fname.data(), 4);
287  if (crl_fd < 0) {
288  err.Emsg("TempCA", "Failed to create temp file:", strerror(errno));
289  return std::unique_ptr<TempCAGuard>();
290  }
291  return std::unique_ptr<TempCAGuard>(new TempCAGuard(ca_fd, crl_fd, ca_tmp_dir, ca_fname.data(), crl_fname.data()));
292 }
293 
294 
296  if (m_ca_fd >= 0) {
297  unlink(m_ca_fname.c_str());
298  close(m_ca_fd);
299  }
300  if (m_crl_fd >= 0) {
301  unlink(m_crl_fname.c_str());
302  close(m_crl_fd);
303  }
304 }
305 
306 
307 bool
309  if (m_ca_fd < 0 || m_ca_tmp_dir.empty()) {return false;}
310  close(m_ca_fd);
311  m_ca_fd = -1;
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())) {
314  return false;
315  }
316  m_ca_fname = ca_fname;
317 
318  if (m_crl_fd < 0 || m_ca_tmp_dir.empty()) {return false;}
319  close(m_crl_fd);
320  m_crl_fd = -1;
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())) {
323  return false;
324  }
325  m_crl_fname = crl_fname;
326 
327  return true;
328 }
329 
330 
331 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)
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)
333  {}
334 
335 
336 XrdTlsTempCA::XrdTlsTempCA(XrdSysError *err, std::string ca_dir)
337  : m_log(*err),
338  m_ca_dir(ca_dir)
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 bool
387 XrdTlsTempCA::Maintenance()
388 {
389  m_log.Emsg("TempCA", "Reloading the list of CAs and CRLs in directory");
390 
391  auto adminpath = getenv("XRDADMINPATH");
392  if (!adminpath) {
393  m_log.Emsg("TempCA", "Admin path is not set!");
394  return false;
395  }
396  std::string ca_tmp_dir = std::string(adminpath) + "/.xrdtls";
397 
398  std::unique_ptr<TempCAGuard> new_file(TempCAGuard::create(m_log, ca_tmp_dir));
399  if (!new_file) {
400  m_log.Emsg("TempCA", "Failed to create a new temp CA / CRL file");
401  return false;
402  }
403 
404  int fddir = XrdSysFD_Open(m_ca_dir.c_str(), O_DIRECTORY);
405  if (fddir < 0) {
406  m_log.Emsg("TempCA", "Failed to open the CA directory", m_ca_dir.c_str());
407  return false;
408  }
409 
410  DIR *dirp = fdopendir(fddir);
411  if (!dirp) {
412  m_log.Emsg("Maintenance", "Failed to allocate a directory pointer");
413  return false;
414  }
415 
416  struct dirent *result;
417  errno = 0;
418  {
419  CASet ca_builder(new_file->getCAFD(), m_log);
420  CRLSet crl_builder(new_file->getCRLFD(), m_log);
421  while ((result = readdir(dirp))) {
422  //m_log.Emsg("Will parse file for CA certificates", result->d_name);
423  if (result->d_name[0] == '.') {continue;}
424  if (result->d_type != DT_REG)
425  {if (result->d_type != DT_UNKNOWN && result->d_type != DT_LNK)
426  continue;
427  struct stat Stat;
428  if (fstatat(fddir, result->d_name, &Stat, 0))
429  {m_log.Emsg("Maintenance", "Failed to stat certificate file",
430  result->d_name, strerror(errno));
431  continue;
432  }
433  if (!S_ISREG(Stat.st_mode)) continue;
434  }
435  int fd = XrdSysFD_Openat(fddir, result->d_name, O_RDONLY);
436  if (fd < 0) {
437  m_log.Emsg("Maintenance", "Failed to open certificate file", result->d_name, strerror(errno));
438  closedir(dirp);
439  return false;
440  }
441  file_smart_ptr fp(fdopen(fd, "r"), &fclose);
442 
443  if (!ca_builder.processFile(fp, result->d_name)) {
444  m_log.Emsg("Maintenance", "Failed to process file for CAs", result->d_name);
445  }
446  rewind(fp.get());
447  if (!crl_builder.processFile(fp, result->d_name)) {
448  m_log.Emsg("Maintenance", "Failed to process file for CRLs", result->d_name);
449  }
450  errno = 0;
451  }
452  if (errno) {
453  m_log.Emsg("Maintenance", "Failure during readdir", strerror(errno));
454  closedir(dirp);
455  return false;
456  }
457  closedir(dirp);
458 
459  if (!crl_builder.processCRLWithCriticalExt()) {
460  m_log.Emsg("Maintenance", "Failed to insert CRLs with critical extension for CRLs", result->d_name);
461  }
462  m_atLeastOneCRLFound = crl_builder.atLeastOneValidCRLFound();
463  }
464 
465  if (!new_file->commit()) {
466  m_log.Emsg("Maintenance", "Failed to finalize new CA / CRL files");
467  return false;
468  }
469  //m_log.Emsg("Maintenance", "Successfully created CA and CRL files", new_file->getCAFilename().c_str(),
470  // new_file->getCRLFilename().c_str());
471  m_ca_file.reset(new std::string(new_file->getCAFilename()));
472  m_crl_file.reset(new std::string(new_file->getCRLFilename()));
473 
474  return true;
475 }
476 
477 
478 void *XrdTlsTempCA::MaintenanceThread(void *myself_raw)
479 {
480  auto myself = static_cast<XrdTlsTempCA *>(myself_raw);
481 
482  auto now = monotonic_time_s();
483  auto next_update = now + m_update_interval;
484  while (true) {
485  now = monotonic_time_s();
486  auto remaining = next_update - now;
487  struct pollfd fds;
488  fds.fd = myself->m_maintenance_pipe_r;
489  fds.events = POLLIN;
490  auto rval = poll(&fds, 1, remaining*1000);
491  if (rval == -1) {
492  if (rval == EINTR) continue;
493  else break;
494  } else if (rval == 0) { // timeout! Let's run maintenance.
495  if (myself->Maintenance()) {
496  next_update = monotonic_time_s() + m_update_interval;
497  } else {
498  next_update = monotonic_time_s() + m_update_interval_failure;
499  }
500  } else { // FD ready; let's shutdown
501  if (fds.revents & POLLIN) {
502  char indicator[1];
503  do {rval = read(myself->m_maintenance_pipe_r, indicator, 1);} while (rval != -1 || errno == EINTR);
504  }
505  }
506  }
507  if (errno) {
508  myself->m_log.Emsg("Maintenance", "Failed to poll for events from parent object");
509  }
510  char indicator = '1';
511  int rval;
512  do {rval = write(myself->m_maintenance_thread_pipe_w, &indicator, 1);} while (rval != -1 || errno == EINTR);
513 
514  return nullptr;
515 }
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 stat(const char *path, struct stat *buf)
struct dirent * readdir(DIR *dirp)
int unlink(const char *path)
int rename(const char *oldpath, const char *newpath)
int mkdir(const char *path, mode_t mode)
int fclose(FILE *stream)
ssize_t write(int fildes, const void *buf, size_t nbyte)
int closedir(DIR *dirp)
int fflush(FILE *stream)
ssize_t read(int fildes, void *buf, size_t nbyte)
#define close(a)
Definition: XrdPosix.hh:43
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:95
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)