/*
 * NAME:
 *	crl - check for revoked x509 certs
 *
 * DESCRIPTION:
 *	SSL Verify callbacks can call crl_check(ctx,xs), which will
 *	indicate which of the following cases is true.
 *
 *	1	we cannot find a CRL for issuer, return 0
 *	2	we have a CRL which revokes xs, return -2
 *	3	we have an expired CRL for issuer which does not revoke xs,
 *		return -1
 *	4	we have a valid CRL for issuer which does not revoke xs,
 *		return 1
 *
 * SEE ALSO:
 *	sslfd(3)
 *
 * AUTHOR:
 *	Simon J. Gerraty <sjg@quick.com.au>
 */
/*
 *      @(#)Copyright (c) 1996 Simon J. Gerraty.
 *      
 *      This is free software.  It comes with NO WARRANTY.
 *      Permission to use, modify and distribute this source code 
 *      is granted subject to the following conditions.
 *      1/ that the above copyright notice and this notice 
 *      are preserved in all copies and that due credit be given 
 *      to the author.  
 *      2/ that any changes to this code are clearly commented 
 *      as such so that the author does not get blamed for bugs 
 *      other than his own.
 *      
 *      Please send copies of changes and bug-fixes to:
 *      sjg@quick.com.au
 */
#ifndef lint
static char RCSid[] = "$Id: crl.c,v 1.8 1998/11/12 04:00:35 sjg Exp $";
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <syslog.h>
#include <string.h>
#include <unistd.h>
#ifndef R_OK
#include <sys/file.h>
#endif

#include "buffer.h"
#include "x509.h"
#include "pem.h"
#include "ssl.h"

#ifdef libsslfd
#include "sslfd.h"
#else
/*
 * Make this more stand alone so folk can use it with Apache
 */
# ifndef SSLEAY_VERSION_NUMBER
#   include "crypto.h"
# endif
# if SSLEAY_VERSION_NUMBER >= 0x0800
#   define SSLEAY8
#   define X509_cert_verify_error_string X509_verify_cert_error_string
#   define X509_NAME_ONELINE(x) (char *)X509_NAME_oneline((x), 0, 0)
# endif
#endif
#ifdef SSLEAY8
# include "x509_vfy.h"
#endif

#ifndef SSLEAY8
/*
 * This stuff has been added to libcrypto - see crl-*.patch
 */
/*
 * The BIO stuff is derrived from apps/crl.c
 */
static X509_CRL *
load_crl(issuer, infile)
	X509_NAME *issuer;
	char *infile;
{
	X509_CRL *crl = NULL;
	BIO *in = NULL;

	in=BIO_new(BIO_s_file());
	if (in == NULL) {
		syslog(LOG_ERR, "cannot create BIO for %s", infile);
		goto end;
	}
	if (BIO_read_filename(in,infile) <= 0) {
		syslog(LOG_ERR, "%s: %m", infile);
		goto end;
	}

	/*
	 * assume PEM, and if that fails, try DER
	 */
	if ((crl = PEM_read_bio_X509_CRL(in,NULL,NULL)) == NULL &&
	    (crl = d2i_X509_CRL_bio(in,NULL)) == NULL) {
		syslog(LOG_ERR, "unable to load CRL %s", infile);
		goto end;
	}
	
	/*
	 * we have loaded a CRL, check that issuer is correct
	 */
	if (X509_name_cmp(issuer, crl->crl->issuer) != 0) {
		syslog(LOG_DEBUG, "loaded wrong CRL");
		X509_CRL_free(crl);
		crl = NULL;
	}
end:
	if (in != NULL)
		BIO_free(in);
	return(crl);
}
#endif

X509_CRL *
find_crl(ctx, xs)
#ifdef SSLEAY8
	X509_STORE_CTX *ctx;
#else
	CERTIFICATE_CTX *ctx;
#endif
	X509 *xs;
{
#ifdef SSLEAY8
	X509_OBJECT obj;
#endif
	X509_NAME *issuer;
	X509_CRL *crl = 0;
	u_long h;
	int i, k;
	char buf[256];
	
	if (!ctx)
		return 0;		/* should/can not happen */

	issuer = X509_get_issuer_name(xs);
	/*
	 * REVISIT: is there any point checking a CRL for a
	 * self-signed cert?  In any case we need to do this for 0.9
	 * so that we do get the CRL ok for the subject cert.
	 */
	if (X509_NAME_cmp(X509_get_subject_name(xs),issuer) == 0)
		return 0;
	
#ifdef SSLEAY8
	memset((char *) &obj, 0, sizeof (obj));	/* initialize it! */
	if (X509_STORE_get_by_subject(ctx,X509_LU_CRL,issuer,&obj) > 0) 
		return (obj.data.crl);
	
	X509_OBJECT_free_contents(&obj);
#else
	/*
	 * The search logic is lifted from eay's
	 * crypto/x509/x509_crt.c I've just made it look for crl's
	 */
	h = X509_issuer_name_hash(xs);
	for (i = 0; i < ctx->num_dirs; i++) {
		sprintf(buf, "%.200s/%08lx.crl", ctx->dirs[i], h);
		if (access(buf, R_OK) == 0)
			crl = load_crl(issuer, buf);
		if (crl)		/* could have been the wrong one */
			return crl;
		for (k = 0; k < 8; k++) {
			sprintf(buf, "%.200s/%08lx.%d.crl", ctx->dirs[i], h, k);
			if (access(buf, R_OK) == 0)
				crl = load_crl(issuer, buf);
			if (crl)		/* still the wrong one? */
				return crl;
		}
	}	
#endif
	return 0;
}

int
asn1cmp(a, b)
	ASN1_INTEGER *a, *b;
{
	int ret;

	if ((ret = a->length - b->length) == 0) {
		if ((ret = memcmp(a->data, b->data, a->length)) == 0) {
			ret = a->type - b->type;
		}
	}
	return ret;
}
	
/*
 * The possible cases are:
 * 1	we cannot find a CRL for issuer, return 0
 * 2	we have a CRL which revokes xs, return -2
 * 3	we have an expired CRL for issuer which does not revoke xs,
 * 	return -1
 * 4	we have a valid CRL for issuer which does not revoke xs,
 * 	return 1
 */

int
check_crl(ctx, xs)
#ifdef SSLEAY8
	X509_STORE_CTX *ctx;
#else
	CERTIFICATE_CTX *ctx;
#endif
	X509 *xs;
{
	X509_CRL *crl = find_crl(ctx, xs);
	X509_REVOKED *r;
	ASN1_UTCTIME *utc;
	int ret = 1;				/* OK */
	
	if (!crl)
		return 0;			/* no CRL */
	
	while (ret > 0 &&
	       (r = (X509_REVOKED *) sk_pop(crl->crl->revoked)) != NULL) {
		if (asn1cmp(xs->cert_info->serialNumber, r->serialNumber) == 0)
			ret = -2;		/* revoked */
	}
	/*
	 * not revoked, need to check if crl has expired
	 */
	if (ret > 0 && crl->crl->nextUpdate != 0 && /* its optional */
	    X509_cmp_current_time(crl->crl->nextUpdate) < 0)
		ret = -1;		/* expired */

#if SSLEAY_VERSION_NUMBER < 0x0900
	X509_CRL_free(crl);
#endif
	return ret;
}
