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/* ************************************************************************** */
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//_____________________________________________________________________________
156int 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//_____________________________________________________________________________
196int 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//_____________________________________________________________________________
228int 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//_____________________________________________________________________________
346int 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//_____________________________________________________________________________
386int 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//_____________________________________________________________________________
580bool 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//_____________________________________________________________________________
621bool 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)
#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 fopen(a, b)
Definition XrdPosix.hh:49
#define unlink(a)
Definition XrdPosix.hh:108
#define stat(a, b)
Definition XrdPosix.hh:96
@ 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 length() const
int tokenize(XrdOucString &tok, int from, char del=':')
const char * c_str() const
XrdSutCacheEntry * Get(const char *tag)