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