/* 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 <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");
}