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