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#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//_____________________________________________________________________________
158int 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//_____________________________________________________________________________
202int 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//_____________________________________________________________________________
234int 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//_____________________________________________________________________________
352int 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//_____________________________________________________________________________
392int 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 return -1;
449 }
450 // Add revocation date
452 // Set status
453 cent->mtime = kCE_ok;
454 // Release the string for the serial number
455 OPENSSL_free(tagser);
456 // Unlock the entry
457 cent->rwmtx.UnLock();
458 }
459 }
460
461 return 0;
462}
463
464//_____________________________________________________________________________
466{
467 // Time of last update
468
469 // If we do not have it already, try extraction
470 if (lastupdate < 0) {
471 // Make sure we have a CRL
472 if (crl)
473 // Extract UTC time in secs from Epoch
475 }
476 // return what we have
477 return lastupdate;
478}
479
480//_____________________________________________________________________________
482{
483 // Time of next update
484
485 // If we do not have it already, try extraction
486 if (nextupdate < 0) {
487 // Make sure we have a CRL
488 if (crl)
489 // Extract UTC time in secs from Epoch
491 }
492 // return what we have
493 return nextupdate;
494}
495
496//_____________________________________________________________________________
498{
499 // Return issuer name
500 EPNAME("X509Crl::Issuer");
501
502 // If we do not have it already, try extraction
503 if (issuer.length() <= 0) {
504
505 // Make sure we have a CRL
506 if (!crl) {
507 DEBUG("WARNING: no CRL available - cannot extract issuer name");
508 return (const char *)0;
509 }
510
511 // Extract issuer name
512 XrdCryptosslNameOneLine(X509_CRL_get_issuer(crl), issuer);
513 }
514
515 // return what we have
516 return (issuer.length() > 0) ? issuer.c_str() : (const char *)0;
517}
518
519//_____________________________________________________________________________
521{
522 // Return hash of issuer name
523 // Use default algorithm (X509_NAME_hash) for alg = 0, old algorithm
524 // (for v>=1.0.0) when alg = 1
525 EPNAME("X509::IssuerHash");
526
527#if (OPENSSL_VERSION_NUMBER >= 0x10000000L && !defined(__APPLE__))
528 if (alg == 1) {
529 // md5 based
530 if (issueroldhash.length() <= 0) {
531 // Make sure we have a certificate
532 if (crl) {
533 char chash[30] = {0};
534 snprintf(chash, sizeof(chash),
535 "%08lx.0",X509_NAME_hash_old(X509_CRL_get_issuer(crl)));
536 issueroldhash = chash;
537 } else {
538 DEBUG("WARNING: no certificate available - cannot extract issuer hash (md5)");
539 }
540 }
541 // return what we have
542 return (issueroldhash.length() > 0) ? issueroldhash.c_str() : (const char *)0;
543 }
544#else
545 if (alg == 1) { }
546#endif
547
548 // If we do not have it already, try extraction
549 if (issuerhash.length() <= 0) {
550
551 // Make sure we have a certificate
552 if (crl) {
553 char chash[30] = {0};
554 snprintf(chash, sizeof(chash),
555 "%08lx.0",X509_NAME_hash(X509_CRL_get_issuer(crl)));
556 issuerhash = chash;
557 } else {
558 DEBUG("WARNING: no certificate available - cannot extract issuer hash (default)");
559 }
560 }
561
562 // return what we have
563 return (issuerhash.length() > 0) ? issuerhash.c_str() : (const char *)0;
564}
565
566//_____________________________________________________________________________
568{
569 // Verify certificate signature with pub key of ref cert
570
571 // We must have been initialized
572 if (!crl)
573 return 0;
574
575 // We must have something to check with
576 X509 *r = ref ? (X509 *)(ref->Opaque()) : 0;
577 EVP_PKEY *rk = r ? X509_get_pubkey(r) : 0;
578 if (!rk)
579 return 0;
580
581 // Ok: we can verify
582 return (X509_CRL_verify(crl, rk) > 0);
583}
584
585//_____________________________________________________________________________
586bool XrdCryptosslX509Crl::IsRevoked(int serialnumber, int when)
587{
588 // Check if certificate with serialnumber is in the
589 // list of revocated certificates
590 EPNAME("IsRevoked");
591
592 // Reference time
593 int now = (when > 0) ? when : time(0);
594
595 // Warn if CRL should be updated
596 if (now > NextUpdate()) {
597 DEBUG("WARNING: CRL is expired: you should download the updated one");
598 }
599
600 // We must have something to check against
601 if (nrevoked <= 0) {
602 DEBUG("No certificate in the list");
603 return 0;
604 }
605
606 // Ok, build the tag
607 char tagser[20] = {0};
608 sprintf(tagser,"%x",serialnumber);
609
610 // Look into the cache
611 XrdSutCacheEntry *cent = cache.Get((const char *)tagser);
612 if (cent && cent->status == kCE_ok) {
613 // Check the revocation time
614 if (now > cent->mtime) {
615 DEBUG("certificate "<<tagser<<" has been revoked");
616 cent->rwmtx.UnLock();
617 return 1;
618 }
619 cent->rwmtx.UnLock();
620 }
621
622 // Certificate not revoked
623 return 0;
624}
625
626//_____________________________________________________________________________
627bool XrdCryptosslX509Crl::IsRevoked(const char *sernum, int when)
628{
629 // Check if certificate with 'sernum' is in the
630 // list of revocated certificates
631 EPNAME("IsRevoked");
632
633 // Reference time
634 int now = (when > 0) ? when : time(0);
635
636 // Warn if CRL should be updated
637 if (now > NextUpdate()) {
638 DEBUG("WARNING: CRL is expired: you should download the updated one");
639 }
640
641 // We must have something to check against
642 if (nrevoked <= 0) {
643 DEBUG("No certificate in the list");
644 return 0;
645 }
646
647 // Look into the cache
648 XrdSutCacheEntry *cent = cache.Get((const char *)sernum);
649 if (cent && cent->status == kCE_ok) {
650 // Check the revocation time
651 if (now > cent->mtime) {
652 DEBUG("certificate "<<sernum<<" has been revoked");
653 cent->rwmtx.UnLock();
654 return 1;
655 }
656 cent->rwmtx.UnLock();
657 }
658
659 // Certificate not revoked
660 return 0;
661}
662
663//_____________________________________________________________________________
665{
666 // Dump content
667 EPNAME("X509Crl::Dump");
668
669 // Time strings
670 struct tm tst;
671 char stbeg[256] = {0};
672 time_t tbeg = LastUpdate();
673 localtime_r(&tbeg,&tst);
674 asctime_r(&tst,stbeg);
675 stbeg[strlen(stbeg)-1] = 0;
676 char stend[256] = {0};
677 time_t tend = NextUpdate();
678 localtime_r(&tend,&tst);
679 asctime_r(&tst,stend);
680 stend[strlen(stend)-1] = 0;
681
682 PRINT("+++++++++++++++ X509 CRL dump +++++++++++++++++++++++");
683 PRINT("+");
684 PRINT("+ File: "<<ParentFile());
685 PRINT("+");
686 PRINT("+ Issuer: "<<Issuer());
687 PRINT("+ Issuer hash: "<<IssuerHash(0));
688 PRINT("+");
689 if (IsExpired()) {
690 PRINT("+ Validity: (expired!)");
691 } else {
692 PRINT("+ Validity:");
693 }
694 PRINT("+ LastUpdate: "<<tbeg<<" UTC - "<<stbeg);
695 PRINT("+ NextUpdate: "<<tend<<" UTC - "<<stend);
696 PRINT("+");
697 PRINT("+ Number of revoked certificates: "<<nrevoked);
698 PRINT("+");
699 PRINT("+++++++++++++++++++++++++++++++++++++++++++++++++");
700}
#define DEBUG(x)
#define EPNAME(x)
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 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 unlink(a)
Definition XrdPosix.hh:113
#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)
XrdCryptosslX509Crl(const char *crlf, int opt=0)
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