XRootD
Loading...
Searching...
No Matches
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"
44#include "XrdVersion.hh"
45
46#include "XrdTlsTempCA.hh"
47
48#include <sstream>
49#include <vector>
50#include <atomic>
51
52namespace {
53
54typedef std::unique_ptr<FILE, int(*)(FILE*)> file_smart_ptr;
55
56
57static 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
67class Set {
68public:
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;
75protected:
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
82class CASet : public Set {
83public:
84 CASet(int output_fd, XrdSysError &err):Set(output_fd,err){}
85
97 bool processFile(file_smart_ptr &fd, const std::string &fname);
98
99private:
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
108bool
109CASet::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
150class CRLSet : public Set {
151public:
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
179private:
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
192bool
193CRLSet::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
236bool CRLSet::atLeastOneValidCRLFound() const {
237 return m_atLeastOneValidCRLFound;
238}
239
240bool 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
261std::unique_ptr<XrdTlsTempCA::TempCAGuard>
262XrdTlsTempCA::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
307bool
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
331XrdTlsTempCA::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
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
386bool
387XrdTlsTempCA::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
478void *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 fclose(FILE *stream)
int fflush(FILE *stream)
#define close(a)
Definition XrdPosix.hh:43
#define write(a, b, c)
Definition XrdPosix.hh:110
#define mkdir(a, b)
Definition XrdPosix.hh:69
#define closedir(a)
Definition XrdPosix.hh:45
#define unlink(a)
Definition XrdPosix.hh:108
#define stat(a, b)
Definition XrdPosix.hh:96
#define rename(a, b)
Definition XrdPosix.hh:87
#define readdir(a)
Definition XrdPosix.hh:81
#define read(a, b, c)
Definition XrdPosix.hh:77
XrdCryptoX509 * Next()
XrdCryptoX509 * Begin()
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)