XRootD
Loading...
Searching...
No Matches
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//_____________________________________________________________________________
151int 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//_____________________________________________________________________________
195int 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//_____________________________________________________________________________
227int 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//_____________________________________________________________________________
345int 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//_____________________________________________________________________________
385int 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//_____________________________________________________________________________
564bool 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//_____________________________________________________________________________
625bool 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)
#define EPNAME(x)
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)
XrdCryptosslX509Crl(const char *crlf, int opt=0)
const char * IssuerHash(int)
bool IsRevoked(int serialnumber, int when=0)
bool Verify(XrdCryptoX509 *ref)
bool beginswith(char c)
int replace(const char *s1, const char *s2, int from=0, int to=-1)
int tokenize(XrdOucString &tok, int from, char del=':')
const char * c_str() const