Logo Search packages:      
Sourcecode: strongswan version File versions  Download package

crl.c

/* Support of X.509 certificate revocation lists (CRLs)
 * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * RCSID $Id: crl.c,v 1.12 2005/12/06 22:49:57 as Exp $
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <sys/types.h>

#include <freeswan.h>
#include <freeswan/ipsec_policy.h>

#include "constants.h"
#include "defs.h"
#include "log.h"
#include "asn1.h"
#include "oid.h"
#include "x509.h"
#include "crl.h"
#include "ca.h"
#include "certs.h"
#include "keys.h"
#include "whack.h"
#include "fetch.h"
#include "sha1.h"

/* chained lists of X.509 crls */

static x509crl_t  *x509crls      = NULL;

/* ASN.1 definition of an X.509 certificate list */

static const asn1Object_t crlObjects[] = {
  { 0, "certificateList",           ASN1_SEQUENCE,     ASN1_OBJ  }, /*  0 */
  { 1,   "tbsCertList",             ASN1_SEQUENCE,     ASN1_OBJ  }, /*  1 */
  { 2,     "version",               ASN1_INTEGER,      ASN1_OPT |
                                             ASN1_BODY }, /*  2 */
  { 2,     "end opt",               ASN1_EOC,          ASN1_END  }, /*  3 */
  { 2,     "signature",             ASN1_EOC,          ASN1_RAW  }, /*  4 */
  { 2,     "issuer",                ASN1_SEQUENCE,     ASN1_OBJ  }, /*  5 */
  { 2,     "thisUpdate",            ASN1_EOC,          ASN1_RAW  }, /*  6 */
  { 2,     "nextUpdate",            ASN1_EOC,          ASN1_RAW  }, /*  7 */
  { 2,     "revokedCertificates",   ASN1_SEQUENCE,     ASN1_OPT |
                                             ASN1_LOOP }, /*  8 */
  { 3,       "certList",            ASN1_SEQUENCE,     ASN1_NONE }, /*  9 */
  { 4,         "userCertificate",   ASN1_INTEGER,      ASN1_BODY }, /* 10 */
  { 4,         "revocationDate",    ASN1_EOC,          ASN1_RAW  }, /* 11 */
  { 4,         "crlEntryExtensions",      ASN1_SEQUENCE,     ASN1_OPT |
                                             ASN1_LOOP }, /* 12 */
  { 5,           "extension",       ASN1_SEQUENCE,     ASN1_NONE }, /* 13 */
  { 6,             "extnID",        ASN1_OID,          ASN1_BODY }, /* 14 */
  { 6,             "critical",            ASN1_BOOLEAN,      ASN1_DEF |
                                             ASN1_BODY }, /* 15 */
  { 6,             "extnValue",           ASN1_OCTET_STRING, ASN1_BODY }, /* 16 */
  { 4,         "end opt or loop",   ASN1_EOC,          ASN1_END  }, /* 17 */
  { 2,     "end opt or loop",       ASN1_EOC,          ASN1_END  }, /* 18 */
  { 2,     "optional extensions",   ASN1_CONTEXT_C_0,  ASN1_OPT  }, /* 19 */
  { 3,       "crlExtensions",       ASN1_SEQUENCE,     ASN1_LOOP }, /* 20 */
  { 4,         "extension",         ASN1_SEQUENCE,     ASN1_NONE }, /* 21 */
  { 5,           "extnID",          ASN1_OID,          ASN1_BODY }, /* 22 */
  { 5,           "critical",        ASN1_BOOLEAN,      ASN1_DEF |
                                             ASN1_BODY }, /* 23 */
  { 5,           "extnValue",       ASN1_OCTET_STRING, ASN1_BODY }, /* 24 */
  { 3,       "end loop",            ASN1_EOC,          ASN1_END  }, /* 25 */
  { 2,     "end opt",               ASN1_EOC,          ASN1_END  }, /* 26 */
  { 1,   "signatureAlgorithm",            ASN1_EOC,          ASN1_RAW  }, /* 27 */
  { 1,   "signatureValue",          ASN1_BIT_STRING,   ASN1_BODY }  /* 28 */
 };

#define CRL_OBJ_CERTIFICATE_LIST           0
#define CRL_OBJ_TBS_CERT_LIST              1
#define CRL_OBJ_VERSION                    2
#define CRL_OBJ_SIG_ALG                    4
#define CRL_OBJ_ISSUER                     5
#define CRL_OBJ_THIS_UPDATE                6
#define CRL_OBJ_NEXT_UPDATE                7
#define CRL_OBJ_USER_CERTIFICATE          10
#define CRL_OBJ_REVOCATION_DATE                 11
#define CRL_OBJ_CRL_ENTRY_EXTN_ID         14
#define CRL_OBJ_CRL_ENTRY_CRITICAL        15
#define CRL_OBJ_CRL_ENTRY_EXTN_VALUE            16
#define CRL_OBJ_EXTN_ID                   22
#define CRL_OBJ_CRITICAL                  23
#define CRL_OBJ_EXTN_VALUE                24
#define CRL_OBJ_ALGORITHM                 27
#define CRL_OBJ_SIGNATURE                 28
#define CRL_OBJ_ROOF                      29


const x509crl_t empty_x509crl = {
      NULL        , /* *next */
    UNDEFINED_TIME, /* installed */
      NULL        , /* distributionPoints */
    { NULL, 0 }   , /* certificateList */
    { NULL, 0 }   , /*   tbsCertList */
            1     , /*     version */
    OID_UNKNOWN   , /*     sigAlg */
    { NULL, 0 }   , /*     issuer */
    UNDEFINED_TIME, /*     thisUpdate */
    UNDEFINED_TIME, /*     nextUpdate */
      NULL        , /*     revokedCertificates */
                    /*     crlExtensions */
                    /*       extension */
                    /*         extnID */
                    /*         critical */
                    /*         extnValue */
    { NULL, 0 }   , /*           authKeyID */
    { NULL, 0 }   , /*           authKeySerialNumber */
    OID_UNKNOWN   , /*   algorithm */
    { NULL, 0 }     /*   signature */
};

/*
 *  get the X.509 CRL with a given issuer
 */
static x509crl_t*
get_x509crl(chunk_t issuer, chunk_t serial, chunk_t keyid)
{
    x509crl_t *crl = x509crls;
    x509crl_t *prev_crl = NULL;

    while (crl != NULL)
    {
      if ((keyid.ptr != NULL && crl->authKeyID.ptr != NULL)
      ? same_keyid(keyid, crl->authKeyID)
      : (same_dn(crl->issuer, issuer) && same_serial(serial, crl->authKeySerialNumber)))
      {
          if (crl != x509crls)
          {
            /* bring the CRL up front */
            prev_crl->next = crl->next;
            crl->next = x509crls;
            x509crls = crl;
          }
          return crl;
      }
      prev_crl = crl;
      crl = crl->next;
    }
    return NULL;
}

/*
 *  free the dynamic memory used to store revoked certificates
 */
static void
free_revoked_certs(revokedCert_t* revokedCerts)
{
    while (revokedCerts != NULL)
    {
      revokedCert_t * revokedCert = revokedCerts;
      revokedCerts = revokedCert->next;
      pfree(revokedCert);
    }
}

/*
 *  free the dynamic memory used to store CRLs
 */
void
free_crl(x509crl_t *crl)
{
    free_revoked_certs(crl->revokedCertificates);
    free_generalNames(crl->distributionPoints, TRUE);
    pfree(crl->certificateList.ptr);
    pfree(crl);
}

static void
free_first_crl(void)
{
    x509crl_t *crl = x509crls;

    x509crls = crl->next;
    free_crl(crl);
}

void
free_crls(void)
{
    lock_crl_list("free_crls");

    while (x509crls != NULL)
      free_first_crl();

    unlock_crl_list("free_crls");
}

/*
 * Insert X.509 CRL into chained list
 */
bool
insert_crl(chunk_t blob, chunk_t crl_uri, bool cache_crl)
{
    x509crl_t *crl = alloc_thing(x509crl_t, "x509crl");

    *crl = empty_x509crl;

    if (parse_x509crl(blob, 0, crl))
    {
      x509cert_t *issuer_cert;
      x509crl_t *oldcrl;
      bool valid_sig;
      generalName_t *gn;

      /* add distribution point */
      gn = alloc_thing(generalName_t, "generalName");
      gn->kind = GN_URI;
      gn->name = crl_uri;
      gn->next = crl->distributionPoints;
      crl->distributionPoints = gn;

      lock_authcert_list("insert_crl");
      /* get the issuer cacert */
      issuer_cert = get_authcert(crl->issuer, crl->authKeySerialNumber,
          crl->authKeyID, AUTH_CA);
      if (issuer_cert == NULL)
      {
          plog("crl issuer cacert not found");
          free_crl(crl);
          unlock_authcert_list("insert_crl");
          return FALSE;
      }
      DBG(DBG_CONTROL,
          DBG_log("crl issuer cacert found")
      )

      /* check the issuer's signature of the crl */
      valid_sig = check_signature(crl->tbsCertList, crl->signature
                  , crl->algorithm, crl->algorithm, issuer_cert);
      unlock_authcert_list("insert_crl");

      if (!valid_sig)
      {
          free_crl(crl);
          return FALSE;
      }
      DBG(DBG_CONTROL,
          DBG_log("crl signature is valid")
      )

      lock_crl_list("insert_crl");
      oldcrl = get_x509crl(crl->issuer, crl->authKeySerialNumber
          , crl->authKeyID);

      if (oldcrl != NULL)
      {
          if (crl->thisUpdate > oldcrl->thisUpdate)
          {
            /* keep any known CRL distribution points */
            add_distribution_points(oldcrl->distributionPoints
                , &crl->distributionPoints);

            /* now delete the old CRL */
            free_first_crl();
            DBG(DBG_CONTROL,
                DBG_log("thisUpdate is newer - existing crl deleted")
            )
          }
          else
          {
            unlock_crl_list("insert_crls");
            DBG(DBG_CONTROL,
                DBG_log("thisUpdate is not newer - existing crl not replaced");
            )
            free_crl(crl);
            return oldcrl->nextUpdate - time(NULL) > 2*crl_check_interval;
          }
      }

      /* insert new CRL */
      crl->next = x509crls;
      x509crls = crl;

      unlock_crl_list("insert_crl");

      /* If crl caching is enabled then the crl is saved locally.
         * Only http or ldap URIs are cached but not local file URIs.
         * The issuer's subjectKeyID is used as a unique filename
         */
      if (cache_crl && strncasecmp(crl_uri.ptr, "file", 4) != 0)
      {
          char path[BUF_LEN];
          char buf[BUF_LEN];
          char digest_buf[SHA1_DIGEST_SIZE];
          chunk_t subjectKeyID = { digest_buf, SHA1_DIGEST_SIZE };

          if (issuer_cert->subjectKeyID.ptr == NULL)
            compute_subjectKeyID(issuer_cert, subjectKeyID);
          else
            subjectKeyID = issuer_cert->subjectKeyID;

          datatot(subjectKeyID.ptr, subjectKeyID.len, 16, buf, BUF_LEN);
          snprintf(path, BUF_LEN, "%s/%s.crl", CRL_PATH, buf);
          write_chunk(path, "crl", crl->certificateList, 0022, TRUE);
      }

      /* is the fetched crl valid? */
      return crl->nextUpdate - time(NULL) > 2*crl_check_interval;
    }
    else
    {
      plog("  error in X.509 crl");
      free_crl(crl);
      return FALSE;
    }
}

/*
 *  Loads CRLs
 */
void
load_crls(void)
{
    struct dirent **filelist;
    u_char buf[BUF_LEN];
    u_char *save_dir;
    int n;

    /* change directory to specified path */
    save_dir = getcwd(buf, BUF_LEN);
    if (chdir(CRL_PATH))
    {
      plog("Could not change to directory '%s'", CRL_PATH);
    }
    else
    {
      plog("Changing to directory '%s'", CRL_PATH);
      n = scandir(CRL_PATH, &filelist, file_select, alphasort);

      if (n < 0)
          plog("  scandir() error");
      else
      {
          while (n--)
          {
            bool pgp = FALSE;
            chunk_t blob = empty_chunk;
            char *filename = filelist[n]->d_name;

            if (load_coded_file(filename, NULL, "crl", &blob, &pgp))
            {
                chunk_t crl_uri;

                crl_uri.len = 7 + sizeof(CRL_PATH) + strlen(filename);
                crl_uri.ptr = alloc_bytes(crl_uri.len + 1, "crl uri");

                /* build CRL file URI */
                snprintf(crl_uri.ptr, crl_uri.len + 1, "file://%s/%s"
                    , CRL_PATH, filename);

                insert_crl(blob, crl_uri, FALSE);
            }
            free(filelist[n]);
          }
          free(filelist);
      }
    }
    /* restore directory path */
    chdir(save_dir);
}

/*
 * Parses a CRL revocation reason code
 */
static crl_reason_t
parse_crl_reasonCode(chunk_t object)
{
    crl_reason_t reason = REASON_UNSPECIFIED;

    if (*object.ptr == ASN1_ENUMERATED
    &&  asn1_length(&object) == 1)
    {
      reason = *object.ptr;
    }

    DBG(DBG_PARSING,
      DBG_log("  '%s'", enum_name(&crl_reason_names, reason))
    )
    return reason;
}

/*
 *  Parses an X.509 CRL
 */
bool
parse_x509crl(chunk_t blob, u_int level0, x509crl_t *crl)
{
    u_char buf[BUF_LEN];
    asn1_ctx_t ctx;
    bool critical;
    chunk_t extnID;
    chunk_t userCertificate;
    chunk_t object;
    u_int level;
    int objectID = 0;

    asn1_init(&ctx, blob, level0, FALSE, DBG_RAW);

    while (objectID < CRL_OBJ_ROOF)
    {
      if (!extract_object(crlObjects, &objectID, &object, &level, &ctx))
           return FALSE;

      /* those objects which will parsed further need the next higher level */
      level++;

      switch (objectID) {
      case CRL_OBJ_CERTIFICATE_LIST:
          crl->certificateList = object;
          break;
      case CRL_OBJ_TBS_CERT_LIST:
          crl->tbsCertList = object;
          break;
      case CRL_OBJ_VERSION:
          crl->version = (object.len) ? (1+(u_int)*object.ptr) : 1;
          DBG(DBG_PARSING,
            DBG_log("  v%d", crl->version);
          )
          break;
      case CRL_OBJ_SIG_ALG:
          crl->sigAlg = parse_algorithmIdentifier(object, level, NULL);
          break;
      case CRL_OBJ_ISSUER:
          crl->issuer = object;
          DBG(DBG_PARSING,
            dntoa(buf, BUF_LEN, object);
            DBG_log("  '%s'",buf)
          )
          break;
      case CRL_OBJ_THIS_UPDATE:
          crl->thisUpdate = parse_time(object, level);
          break;
      case CRL_OBJ_NEXT_UPDATE:
          crl->nextUpdate = parse_time(object, level);
          break;
      case CRL_OBJ_USER_CERTIFICATE:
          userCertificate = object;
          break;
      case CRL_OBJ_REVOCATION_DATE:
          {
            /* put all the serial numbers and the revocation date in a chained list
               with revocedCertificates pointing to the first revoked certificate */

            revokedCert_t *revokedCert = alloc_thing(revokedCert_t, "revokedCert");
            revokedCert->userCertificate = userCertificate;
            revokedCert->revocationDate = parse_time(object, level);
            revokedCert->revocationReason = REASON_UNSPECIFIED;
            revokedCert->next = crl->revokedCertificates;
            crl->revokedCertificates = revokedCert;
          }
          break;
      case CRL_OBJ_CRL_ENTRY_EXTN_ID:
      case CRL_OBJ_EXTN_ID:
          extnID = object;
          break;
      case CRL_OBJ_CRL_ENTRY_CRITICAL:
      case CRL_OBJ_CRITICAL:
          critical = object.len && *object.ptr;
          DBG(DBG_PARSING,
            DBG_log("  %s",(critical)?"TRUE":"FALSE");
          )
          break;
      case CRL_OBJ_CRL_ENTRY_EXTN_VALUE:
      case CRL_OBJ_EXTN_VALUE:
          {
            u_int extn_oid = known_oid(extnID);

            if (extn_oid == OID_CRL_REASON_CODE)
            {
                crl->revokedCertificates->revocationReason =
                  parse_crl_reasonCode(object);
            }
            else if (extn_oid == OID_AUTHORITY_KEY_ID)
            {
                parse_authorityKeyIdentifier(object, level
                  , &crl->authKeyID, &crl->authKeySerialNumber);
            }
          }
          break;
      case CRL_OBJ_ALGORITHM:
          crl->algorithm = parse_algorithmIdentifier(object, level, NULL);
          break;
      case CRL_OBJ_SIGNATURE:
          crl->signature = object;
          break;
      default:
          break;
      }
      objectID++;
    }
    time(&crl->installed);
    return TRUE;
}

/*  Checks if the current certificate is revoked. It goes through the
 *  list of revoked certificates of the corresponding crl. Either the
 *  status CERT_GOOD or CERT_REVOKED is returned
 */
static cert_status_t
check_revocation(const x509crl_t *crl, chunk_t serial
, time_t *revocationDate, crl_reason_t * revocationReason)
{
    revokedCert_t *revokedCert = crl->revokedCertificates;

    *revocationDate = UNDEFINED_TIME;
    *revocationReason = REASON_UNSPECIFIED;
    
    DBG(DBG_CONTROL,
      DBG_dump_chunk("serial number:", serial)
    )

    while(revokedCert != NULL)
    {
      /* compare serial numbers */
      if (revokedCert->userCertificate.len == serial.len &&
          memcmp(revokedCert->userCertificate.ptr, serial.ptr, serial.len) == 0)
      {
          *revocationDate = revokedCert->revocationDate;
          *revocationReason = revokedCert->revocationReason;
          return CERT_REVOKED;
      }
      revokedCert = revokedCert->next;
    }
    return CERT_GOOD;
}

/*
 * check if any crls are about to expire
 */
void
check_crls(void)
{
    x509crl_t *crl;

    lock_crl_list("check_crls");
    crl = x509crls;

    while (crl != NULL)
    {
      time_t time_left = crl->nextUpdate - time(NULL);
      u_char buf[BUF_LEN];

      DBG(DBG_CONTROL,
          dntoa(buf, BUF_LEN, crl->issuer);
          DBG_log("issuer: '%s'",buf);
          if (crl->authKeyID.ptr != NULL)
          {
            datatot(crl->authKeyID.ptr, crl->authKeyID.len, ':'
                , buf, BUF_LEN);
            DBG_log("authkey: %s", buf);
          }
          DBG_log("%ld seconds left", time_left)
      )
      if (time_left < 2*crl_check_interval)
      {
          fetch_req_t *req = build_crl_fetch_request(crl->issuer
            , crl->authKeySerialNumber
            , crl->authKeyID, crl->distributionPoints);
          add_crl_fetch_request(req);
      }
      crl = crl->next;
    }
    unlock_crl_list("check_crls");
}

/*
 * verify if a cert hasn't been revoked by a crl
 */
cert_status_t
verify_by_crl(const x509cert_t *cert, time_t *until, time_t *revocationDate
, crl_reason_t *revocationReason)
{
    x509crl_t *crl;

    ca_info_t *ca = get_ca_info(cert->issuer, cert->authKeySerialNumber
                        , cert->authKeyID);

    generalName_t *crluri = (ca == NULL)? NULL : ca->crluri;

    *revocationDate = UNDEFINED_TIME;
    *revocationReason = REASON_UNSPECIFIED;

    lock_crl_list("verify_by_crl");
    crl = get_x509crl(cert->issuer, cert->authKeySerialNumber, cert->authKeyID);

    if (crl == NULL)
    {
      unlock_crl_list("verify_by_crl");
      plog("crl not found");

      if (cert->crlDistributionPoints != NULL)
      {
          fetch_req_t *req = build_crl_fetch_request(cert->issuer
            , cert->authKeySerialNumber
            , cert->authKeyID, cert->crlDistributionPoints);
          add_crl_fetch_request(req);
      }

      if (crluri != NULL)
      {
          fetch_req_t *req = build_crl_fetch_request(cert->issuer
            , cert->authKeySerialNumber
            , cert->authKeyID, crluri);
          add_crl_fetch_request(req);
      }

      if (cert->crlDistributionPoints != 0 || crluri != NULL)
      {
          wake_fetch_thread("verify_by_crl");
          return CERT_UNKNOWN;
      }
      else
          return CERT_UNDEFINED;
    }
    else
    {
      x509cert_t *issuer_cert;
      bool valid;

      DBG(DBG_CONTROL,
          DBG_log("crl found")
      )

      add_distribution_points(cert->crlDistributionPoints
            , &crl->distributionPoints);

      add_distribution_points(crluri
            , &crl->distributionPoints);

      lock_authcert_list("verify_by_crl");

      issuer_cert = get_authcert(crl->issuer, crl->authKeySerialNumber
            , crl->authKeyID, AUTH_CA);
      valid = check_signature(crl->tbsCertList, crl->signature
            , crl->algorithm, crl->algorithm, issuer_cert);
      
      unlock_authcert_list("verify_by_crl");

      if (valid)
      {
          cert_status_t status;

          DBG(DBG_CONTROL,
            DBG_log("crl signature is valid")
          )
         /* return the expiration date */
          *until = crl->nextUpdate;

          /* has the certificate been revoked? */
          status = check_revocation(crl, cert->serialNumber, revocationDate
                        , revocationReason);

          if (*until < time(NULL))
          {
            fetch_req_t *req;

            plog("crl update is overdue since %s"
                , timetoa(until, TRUE));

            /* try to fetch a crl update */
            req = build_crl_fetch_request(crl->issuer
                        , crl->authKeySerialNumber
                        , crl->authKeyID, crl->distributionPoints);
            unlock_crl_list("verify_by_crl");

            add_crl_fetch_request(req);
            wake_fetch_thread("verify_by_crl");
          }
          else
          {
            unlock_crl_list("verify_by_crl");
            DBG(DBG_CONTROL,
                DBG_log("crl is valid")
            )
          }
          return status;
      }
      else
      {
          unlock_crl_list("verify_by_crl");
          plog("crl signature is invalid");
          return CERT_UNKNOWN;
      }
    }
}

/*
 *  list all X.509 crls in the chained list
 */
void
list_crls(bool utc, bool strict)
{
    x509crl_t *crl;

    lock_crl_list("list_crls");
    crl = x509crls;

    if (crl != NULL)
    {
      whack_log(RC_COMMENT, " ");
      whack_log(RC_COMMENT, "List of X.509 CRLs:");
      whack_log(RC_COMMENT, " ");
    }

    while (crl != NULL)
    {
      u_char buf[BUF_LEN];
      u_int revoked = 0;
      revokedCert_t *revokedCert = crl->revokedCertificates;

      /* count number of revoked certificates in CRL */
      while (revokedCert != NULL)
      {
          revoked++;
          revokedCert = revokedCert->next;
        }

      whack_log(RC_COMMENT, "%s, revoked certs: %d",
            timetoa(&crl->installed, utc), revoked);
      dntoa(buf, BUF_LEN, crl->issuer);
      whack_log(RC_COMMENT, "       issuer:   '%s'", buf);

      list_distribution_points(crl->distributionPoints);

      whack_log(RC_COMMENT, "       updates:   this %s",
            timetoa(&crl->thisUpdate, utc));
      whack_log(RC_COMMENT, "                  next %s %s",
            timetoa(&crl->nextUpdate, utc),
            check_expiry(crl->nextUpdate, CRL_WARNING_INTERVAL, strict));
      if (crl->authKeyID.ptr != NULL)
      {
          datatot(crl->authKeyID.ptr, crl->authKeyID.len, ':'
            , buf, BUF_LEN);
          whack_log(RC_COMMENT, "       authkey:   %s", buf);
      }
      if (crl->authKeySerialNumber.ptr != NULL)
      {
          datatot(crl->authKeySerialNumber.ptr, crl->authKeySerialNumber.len, ':'
            , buf, BUF_LEN);
          whack_log(RC_COMMENT, "       aserial:   %s", buf);
      }

      crl = crl->next;
    }
    unlock_crl_list("list_crls");
}


Generated by  Doxygen 1.6.0   Back to index