XRootD
XrdCryptosslX509Crl.cc
Go to the documentation of this file.
1 /******************************************************************************/
2 /* */
3 /* X r d C r y p t o s s l X 5 0 9 C r l. c c */
4 /* */
5 /* (c) 2005 G. Ganis , CERN */
6 /* */
7 /* This file is part of the XRootD software suite. */
8 /* */
9 /* XRootD is free software: you can redistribute it and/or modify it under */
10 /* the terms of the GNU Lesser General Public License as published by the */
11 /* Free Software Foundation, either version 3 of the License, or (at your */
12 /* option) any later version. */
13 /* */
14 /* XRootD is distributed in the hope that it will be useful, but WITHOUT */
15 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
16 /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
17 /* License for more details. */
18 /* */
19 /* You should have received a copy of the GNU Lesser General Public License */
20 /* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
21 /* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
22 /* */
23 /* The copyright holder's institutional names and contributor's names may not */
24 /* be used to endorse or promote products derived from this software without */
25 /* specific prior written permission of the institution or contributor. */
26 /* */
27 /******************************************************************************/
28 
29 /* ************************************************************************** */
30 /* */
31 /* OpenSSL implementation of XrdCryptoX509Crl */
32 /* */
33 /* ************************************************************************** */
38 
39 #include <openssl/bn.h>
40 #include <openssl/pem.h>
41 
42 #include <cerrno>
43 #include <ctime>
44 
45 #include <fcntl.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <unistd.h>
49 
50 //_____________________________________________________________________________
53 {
54  // Constructor certificate from file 'cf'.
55  EPNAME("X509Crl::XrdCryptosslX509Crl_file");
56 
57  // Make sure file name is defined;
58  if (opt == 0) {
59  if (Init(cf) != 0) {
60  DEBUG("could not initialize the CRL from "<<cf);
61  return;
62  }
63  } else {
64  if (InitFromURI(cf, 0) != 0) {
65  DEBUG("could not initialize the CRL from URI"<<cf);
66  return;
67  }
68  }
69 }
70 
71 //_____________________________________________________________________________
73 {
74  // Constructe CRL from a FILE handle `fc` with (assumed) filename `cf`.
75  EPNAME("X509Crl::XrdCryptosslX509Crl_file");
76 
77  if (Init(fc, cf)) {
78  DEBUG("could not initialize the CRL from " << cf);
79  return;
80  }
81 }
82 
83 //_____________________________________________________________________________
86 {
87  // Constructor certificate from CA certificate 'cacert'. This constructor
88  // extracts the information about the location of the CRL cerificate from the
89  // CA certificate extension 'crlDistributionPoints', downloads the file and
90  // loads it in the cache
91  EPNAME("X509Crl::XrdCryptosslX509Crl_CA");
92 
93  // The CA certificate must be defined
94  if (!cacert || cacert->type != XrdCryptoX509::kCA) {
95  DEBUG("the CA certificate is undefined or not CA! ("<<cacert<<")");
96  return;
97  }
98 
99  // Get the extension
100  X509_EXTENSION *crlext = (X509_EXTENSION *) cacert->GetExtension("crlDistributionPoints");
101  if (!crlext) {
102  DEBUG("extension 'crlDistributionPoints' not found in the CA certificate");
103  return;
104  }
105 
106  // Bio for exporting the extension
107  BIO *bext = BIO_new(BIO_s_mem());
108  const ASN1_OBJECT *obj = X509_EXTENSION_get_object(crlext);
109  i2a_ASN1_OBJECT(bext, obj);
110  X509V3_EXT_print(bext, crlext, 0, 4);
111  // data length
112  char *cbio = 0;
113  int lbio = (int) BIO_get_mem_data(bext, &cbio);
114  char *buf = (char *) malloc(lbio+1);
115  // Read key from BIO to buf
116  memcpy(buf, cbio, lbio);
117  buf[lbio] = 0;
118  BIO_free(bext);
119  // Save it
120  XrdOucString uris(buf);
121  free(buf);
122 
123  DEBUG("URI string: "<< uris);
124 
125  XrdOucString uri;
126  int from = 0;
127  while ((from = uris.tokenize(uri, from, ' ')) != -1) {
128  if (uri.beginswith("URI:")) {
129  uri.replace("URI:","");
130  uri.replace("\n","");
131  if (InitFromURI(uri.c_str(), cacert->SubjectHash()) == 0) {
132  crluri = uri;
133  // We are done
134  break;
135  }
136  }
137  }
138 }
139 
140 //_____________________________________________________________________________
142 {
143  // Destructor
144 
145  // Cleanup CRL
146  if (crl)
147  X509_CRL_free(crl);
148 }
149 
150 //_____________________________________________________________________________
151 int XrdCryptosslX509Crl::Init(const char *cf)
152 {
153  // Load a CRL from an open file handle; for debugging purposes,
154  // we assume it's loaded from file named `cf`.
155  EPNAME("X509Crl::Init");
156 
157  // Make sure file name is defined;
158  if (!cf) {
159  DEBUG("file name undefined");
160  return -1;
161  }
162 
163  // Make sure file exists;
164  int fd = open(cf, O_RDONLY);
165 
166  if (fd == -1) {
167  if (errno == ENOENT) {
168  DEBUG("file "<<cf<<" does not exist - do nothing");
169  } else {
170  DEBUG("cannot open file "<<cf<<" (errno: "<<errno<<")");
171  }
172  return -1;
173  }
174 
175  // Open file in read mode
176  FILE *fc = fdopen(fd, "r");
177 
178  if (!fc) {
179  DEBUG("cannot open file "<<cf<<" (errno: "<<errno<<")");
180  close(fd);
181  return -1;
182  }
183 
184  auto rval = Init(fc, cf);
185 
186  //
187  // Close the file
188  fclose(fc);
189 
190  return rval;
191 }
192 
193 
194 //_____________________________________________________________________________
195 int XrdCryptosslX509Crl::Init(FILE *fc, const char *cf)
196 {
197  // Constructor certificate from file 'cf'.
198  // Return 0 on success, -1 on failure
199  EPNAME("X509Crl::Init");
200 
201  //
202  // Read the content:
203  if (!PEM_read_X509_CRL(fc, &crl, 0, 0)) {
204  DEBUG("Unable to load CRL from file");
205  return -1;
206  }
207 
208  //
209  // Notify
210  DEBUG("CRL successfully loaded from "<< cf);
211 
212  //
213  // Save source file name
214  srcfile = cf;
215  //
216  // Init some of the private members (the others upon need)
217  Issuer();
218  //
219  // Load into cache
220  LoadCache();
221  //
222  // Done
223  return 0;
224 }
225 
226 //_____________________________________________________________________________
227 int XrdCryptosslX509Crl::InitFromURI(const char *uri, const char *hash)
228 {
229  // Initialize the CRL taking the file indicated by URI. Download and
230  // reformat the file first.
231  // Returns 0 on success, -1 on failure.
232  EPNAME("X509Crl::InitFromURI");
233 
234  // Make sure file name is defined;
235  if (!uri) {
236  DEBUG("uri undefined");
237  return -1;
238  }
239  XrdOucString u(uri), h(hash);
240  if (h == "") {
241  int isl = u.rfind('/');
242  if (isl != STR_NPOS) h.assign(u, isl + 1);
243  }
244  if (h == "") h = "hashtmp";
245 
246  // Create local output file path
247  XrdOucString outtmp(getenv("TMPDIR")), outpem;
248  if (outtmp.length() <= 0) outtmp = "/tmp";
249  if (!outtmp.endswith("/")) outtmp += "/";
250  outtmp += h;
251  outtmp += ".crltmp";
252 
253  // Prepare 'wget' command
254  XrdOucString cmd("wget ");
255  cmd += uri;
256  cmd += " -O ";
257  cmd += outtmp;
258 
259  // Execute 'wget'
260  DEBUG("executing ... "<<cmd);
261  if (system(cmd.c_str()) == -1) {
262  DEBUG("'system' could not fork to execute command '"<<cmd<<"'");
263  return -1;
264  }
265  struct stat st;
266  if (stat(outtmp.c_str(), &st) != 0) {
267  DEBUG("did not manage to get the CRL file from "<<uri);
268  return -1;
269  }
270  outpem = outtmp;
271 
272  // Find out the file type
273  int needsopenssl = GetFileType(outtmp.c_str());
274  if (needsopenssl < 0) {
275  DEBUG("did not manage to coorectly parse "<<outtmp);
276  return -1;
277  }
278 
279  if (needsopenssl > 0) {
280  // Put it in PEM format
281  outpem.replace(".crltmp", ".pem");
282  cmd = "openssl crl -inform DER -in ";
283  cmd += outtmp;
284  cmd += " -out ";
285  cmd += outpem;
286  cmd += " -text";
287 
288  // Execute 'openssl crl'
289  DEBUG("executing ... "<<cmd);
290  if (system(cmd.c_str()) == -1) {
291  DEBUG("system: problem executing: "<<cmd);
292  return -1;
293  }
294 
295  // Cleanup the temporary files
296  if (unlink(outtmp.c_str()) != 0) {
297  DEBUG("problems removing "<<outtmp);
298  }
299  }
300 
301  // Make sure the file is there
302  if (stat(outpem.c_str(), &st) != 0) {
303  DEBUG("did not manage to change format from DER to PEM ("<<outpem<<")");
304  return -1;
305  }
306 
307  // Now init from the new file
308  if (Init(outpem.c_str()) != 0) {
309  DEBUG("could not initialize the CRL from "<<outpem);
310  return -1;
311  }
312 
313  // Cleanup the temporary files
314  unlink(outpem.c_str());
315 
316  //
317  // Done
318  return 0;
319 }
320 
321 //_____________________________________________________________________________
323 {
324  // Write the CRL's contents to a file in the PEM format.
325  EPNAME("ToFile");
326 
327  if (!crl) {
328  DEBUG("CRL object invalid; cannot write to a file");
329  return false;
330  }
331 
332  if (PEM_write_X509_CRL(fh, crl) == 0) {
333  DEBUG("Unable to write CRL to file");
334  return false;
335  }
336 
337  //
338  // Notify
339  DEBUG("CRL successfully written to file");
340 
341  return true;
342 }
343 
344 //_____________________________________________________________________________
345 int XrdCryptosslX509Crl::GetFileType(const char *crlfn)
346 {
347  // Try to understand if file 'crlfn' is in DER (binary) or PEM (ASCII)
348  // format (assume that is not ASCII is a DER).
349  // Return 1 if not-PEM, 0 if PEM, -1 if any error occurred
350  EPNAME("GetFileType");
351 
352  if (!crlfn || strlen(crlfn) <= 0) {
353  PRINT("file name undefined!");
354  return -1;
355  }
356 
357  char line[1024] = {0};
358  FILE *f = fopen(crlfn, "r");
359  if (!f) {
360  PRINT("could not open file "<<crlfn<<" - errno: "<<(int)errno);
361  return -1;
362  }
363 
364  int rc = 1;
365  while (fgets(line, 1024, f)) {
366  // Skip empty lines at beginning
367  if (line[0] == '\n') continue;
368  // Analyse line for '-----BEGIN X509 CRL-----'
369  if (strstr(line, "BEGIN X509 CRL")) rc = 0;
370  break;
371  }
372  // Close the files
373  fclose(f);
374  // Done
375  return rc;
376 }
377 
379  // If the X509_CRL_get_ext_by_critical() function returns -1, no critical extension
380  // has been found
381  return X509_CRL_get_ext_by_critical(crl,1,-1) != -1;
382 }
383 
384 //_____________________________________________________________________________
385 int XrdCryptosslX509Crl::LoadCache()
386 {
387  // Load relevant info into the cache
388  // Return 0 if ok, -1 in case of error
389  EPNAME("LoadCache");
390 
391  // The CRL must exists
392  if (!crl) {
393  DEBUG("CRL undefined");
394  return -1;
395  }
396 
397  // Parse CRL
398  STACK_OF(X509_REVOKED *) rsk = X509_CRL_get_REVOKED(crl);
399  if (!rsk) {
400  DEBUG("could not get stack of revoked instances");
401  return -1;
402  }
403 
404  // Number of revocations
405  nrevoked = sk_X509_REVOKED_num(rsk);
406  DEBUG(nrevoked << "certificates have been revoked");
407  if (nrevoked <= 0) {
408  DEBUG("no valid certificate has been revoked - nothing to do");
409  return 0;
410  }
411 
412  // Get serial numbers of revoked certificates
413  char *tagser = 0;
414  int i = 0;
415  for (; i < nrevoked; i++ ){
416  X509_REVOKED *rev = sk_X509_REVOKED_value(rsk,i);
417  if (rev) {
418  BIGNUM *bn = BN_new();
419  ASN1_INTEGER_to_BN(X509_REVOKED_get0_serialNumber(rev), bn);
420  tagser = BN_bn2hex(bn);
421  BN_free(bn);
422  TRACE(Dump, "certificate with serial number: "<<tagser<<
423  " has been revoked");
424  // Add to the cache
425  bool rdlock = false;
426  XrdSutCacheEntry *cent = cache.Get((const char *)tagser, rdlock);
427  if (!cent) {
428  DEBUG("problems getting entry in the cache");
429  OPENSSL_free(tagser);
430  return -1;
431  }
432  // Add revocation date
433  cent->mtime = XrdCryptosslASN1toUTC(X509_REVOKED_get0_revocationDate(rev));
434  // Set status
435  cent->status = kCE_ok;
436  // Release the string for the serial number
437  OPENSSL_free(tagser);
438  // Unlock the entry
439  cent->rwmtx.UnLock();
440  }
441  }
442 
443  return 0;
444 }
445 
446 //_____________________________________________________________________________
448 {
449  // Time of last update
450 
451  // If we do not have it already, try extraction
452  if (lastupdate < 0) {
453  // Make sure we have a CRL
454  if (crl)
455  // Extract UTC time in secs from Epoch
456  lastupdate = XrdCryptosslASN1toUTC(X509_CRL_get0_lastUpdate(crl));
457  }
458  // return what we have
459  return lastupdate;
460 }
461 
462 //_____________________________________________________________________________
464 {
465  // Time of next update
466 
467  // If we do not have it already, try extraction
468  if (nextupdate < 0) {
469  // Make sure we have a CRL
470  if (crl)
471  // Extract UTC time in secs from Epoch
472  nextupdate = XrdCryptosslASN1toUTC(X509_CRL_get0_nextUpdate(crl));
473  }
474  // return what we have
475  return nextupdate;
476 }
477 
478 //_____________________________________________________________________________
480 {
481  // Return issuer name
482  EPNAME("X509Crl::Issuer");
483 
484  // If we do not have it already, try extraction
485  if (issuer.length() <= 0) {
486 
487  // Make sure we have a CRL
488  if (!crl) {
489  DEBUG("WARNING: no CRL available - cannot extract issuer name");
490  return (const char *)0;
491  }
492 
493  // Extract issuer name
494  XrdCryptosslNameOneLine(X509_CRL_get_issuer(crl), issuer);
495  }
496 
497  // return what we have
498  return (issuer.length() > 0) ? issuer.c_str() : (const char *)0;
499 }
500 
501 //_____________________________________________________________________________
503 {
504  // Return hash of issuer name
505  // Use default algorithm (X509_NAME_hash) for alg = 0, old algorithm
506  // (for v>=1.0.0) when alg = 1
507  EPNAME("X509::IssuerHash");
508 
509  if (alg == 1) {
510  // md5 based
511  if (issueroldhash.length() <= 0) {
512  // Make sure we have a certificate
513  if (crl) {
514  char chash[30] = {0};
515  snprintf(chash, sizeof(chash),
516  "%08lx.0",X509_NAME_hash_old(X509_CRL_get_issuer(crl)));
517  issueroldhash = chash;
518  } else {
519  DEBUG("WARNING: no certificate available - cannot extract issuer hash (md5)");
520  }
521  }
522  // return what we have
523  return (issueroldhash.length() > 0) ? issueroldhash.c_str() : (const char *)0;
524  }
525 
526  // If we do not have it already, try extraction
527  if (issuerhash.length() <= 0) {
528 
529  // Make sure we have a certificate
530  if (crl) {
531  char chash[30] = {0};
532  snprintf(chash, sizeof(chash),
533  "%08lx.0",X509_NAME_hash(X509_CRL_get_issuer(crl)));
534  issuerhash = chash;
535  } else {
536  DEBUG("WARNING: no certificate available - cannot extract issuer hash (default)");
537  }
538  }
539 
540  // return what we have
541  return (issuerhash.length() > 0) ? issuerhash.c_str() : (const char *)0;
542 }
543 
544 //_____________________________________________________________________________
546 {
547  // Verify certificate signature with pub key of ref cert
548 
549  // We must have been initialized
550  if (!crl)
551  return 0;
552 
553  // We must have something to check with
554  X509 *r = ref ? (X509 *)(ref->Opaque()) : 0;
555  EVP_PKEY *rk = r ? X509_get_pubkey(r) : 0;
556  if (!rk)
557  return 0;
558 
559  // Ok: we can verify
560  return (X509_CRL_verify(crl, rk) > 0);
561 }
562 
563 //_____________________________________________________________________________
564 bool XrdCryptosslX509Crl::IsRevoked(int serialnumber, int when)
565 {
566  // Check if certificate with serialnumber is in the
567  // list of revocated certificates
568  EPNAME("IsRevoked");
569 
570  // Reference time
571  int now = (when > 0) ? when : time(0);
572 
573  // Warn if CRL should be updated
574  if (now > NextUpdate()) {
575  DEBUG("WARNING: CRL is expired: you should download the updated one");
576  }
577 
578  // We must have something to check against
579  if (nrevoked <= 0) {
580  DEBUG("No certificate in the list");
581  return 0;
582  }
583 
584  // A serial number is never negative
585  if (serialnumber < 0) {
586  DEBUG("invalid serial number: "<<serialnumber);
587  return 0;
588  }
589 
590  // Ok, build the tag: the cache is keyed with the serial number in the
591  // format produced by BN_bn2hex(), i.e. upper case with an even number
592  // of digits, so we must use the same format here
593  BIGNUM *bn = BN_new();
594  if (!bn) {
595  DEBUG("could not allocate a big number");
596  return 0;
597  }
598  BN_set_word(bn, (BN_ULONG)serialnumber);
599  char *tagser = BN_bn2hex(bn);
600  BN_free(bn);
601  if (!tagser) {
602  DEBUG("could not format the serial number");
603  return 0;
604  }
605 
606  // Look into the cache
607  bool revoked = false;
608  XrdSutCacheEntry *cent = cache.Get((const char *)tagser);
609  if (cent) {
610  // Check the revocation time
611  if (cent->status == kCE_ok && now > cent->mtime) {
612  DEBUG("certificate "<<tagser<<" has been revoked");
613  revoked = true;
614  }
615  cent->rwmtx.UnLock();
616  }
617  OPENSSL_free(tagser);
618  if (revoked) return 1;
619 
620  // Certificate not revoked
621  return 0;
622 }
623 
624 //_____________________________________________________________________________
625 bool XrdCryptosslX509Crl::IsRevoked(const char *sernum, int when)
626 {
627  // Check if certificate with 'sernum' is in the
628  // list of revocated certificates
629  EPNAME("IsRevoked");
630 
631  // Reference time
632  int now = (when > 0) ? when : time(0);
633 
634  // Warn if CRL should be updated
635  if (now > NextUpdate()) {
636  DEBUG("WARNING: CRL is expired: you should download the updated one");
637  }
638 
639  // We must have something to check against
640  if (nrevoked <= 0) {
641  DEBUG("No certificate in the list");
642  return 0;
643  }
644 
645  // Look into the cache
646  bool revoked = false;
647  XrdSutCacheEntry *cent = cache.Get((const char *)sernum);
648  if (cent) {
649  // Check the revocation time
650  if (cent->status == kCE_ok && now > cent->mtime) {
651  DEBUG("certificate "<<sernum<<" has been revoked");
652  revoked = true;
653  }
654  cent->rwmtx.UnLock();
655  }
656  if (revoked) return 1;
657 
658  // Certificate not revoked
659  return 0;
660 }
661 
662 //_____________________________________________________________________________
664 {
665  // Dump content
666  EPNAME("X509Crl::Dump");
667 
668  // Time strings
669  struct tm tst;
670  char stbeg[256] = {0};
671  time_t tbeg = LastUpdate();
672  localtime_r(&tbeg,&tst);
673  asctime_r(&tst,stbeg);
674  stbeg[strlen(stbeg)-1] = 0;
675  char stend[256] = {0};
676  time_t tend = NextUpdate();
677  localtime_r(&tend,&tst);
678  asctime_r(&tst,stend);
679  stend[strlen(stend)-1] = 0;
680 
681  PRINT("+++++++++++++++ X509 CRL dump +++++++++++++++++++++++");
682  PRINT("+");
683  PRINT("+ File: "<<ParentFile());
684  PRINT("+");
685  PRINT("+ Issuer: "<<Issuer());
686  PRINT("+ Issuer hash: "<<IssuerHash(0));
687  PRINT("+");
688  if (IsExpired()) {
689  PRINT("+ Validity: (expired!)");
690  } else {
691  PRINT("+ Validity:");
692  }
693  PRINT("+ LastUpdate: "<<tbeg<<" UTC - "<<stbeg);
694  PRINT("+ NextUpdate: "<<tend<<" UTC - "<<stend);
695  PRINT("+");
696  PRINT("+ Number of revoked certificates: "<<nrevoked);
697  PRINT("+");
698  PRINT("+++++++++++++++++++++++++++++++++++++++++++++++++");
699 }
#define DEBUG(x)
Definition: XrdBwmTrace.hh:54
#define EPNAME(x)
Definition: XrdBwmTrace.hh:56
time_t XrdCryptosslASN1toUTC(const ASN1_TIME *tsn1)
void XrdCryptosslNameOneLine(const X509_NAME *nm, XrdOucString &s)
#define PRINT(y)
#define STR_NPOS
int fclose(FILE *stream)
#define close(a)
Definition: XrdPosix.hh:48
#define fopen(a, b)
Definition: XrdPosix.hh:54
#define open
Definition: XrdPosix.hh:78
#define unlink(a)
Definition: XrdPosix.hh:119
#define stat(a, b)
Definition: XrdPosix.hh:105
@ kCE_ok
#define TRACE(act, x)
Definition: XrdTrace.hh:63
virtual bool IsExpired(int when=0)
const char * IssuerHash()
virtual XrdCryptoX509data GetExtension(const char *oid)
virtual XrdCryptoX509data Opaque()
virtual const char * SubjectHash(int)
EX509Type type
XrdCryptosslX509Crl(const char *crlf, int opt=0)
bool IsRevoked(int serialnumber, int when=0)
bool Verify(XrdCryptoX509 *ref)
const char * c_str() const
bool beginswith(char c)
int replace(const char *s1, const char *s2, int from=0, int to=-1)
int length() const
int tokenize(XrdOucString &tok, int from, char del=':')
XrdSysRWLock rwmtx
XrdSutCacheEntry * Get(const char *tag)
Definition: XrdSutCache.hh:54