/*	$OpenBSD: x509.c,v 1.85 2004/01/06 00:09:19 hshoexer Exp $	*/
/*	$EOM: x509.c,v 1.54 2001/01/16 18:42:16 ho Exp $	*/

/*
 * Copyright (c) 1998, 1999 Niels Provos.  All rights reserved.
 * Copyright (c) 1999, 2000, 2001 Niklas Hallqvist.  All rights reserved.
 * Copyright (c) 1999, 2000, 2001 Angelos D. Keromytis.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * This code was written under funding by Ericsson Radio Systems.
 */

#ifdef USE_X509

#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef USE_POLICY
#include <regex.h>
#include <keynote.h>
#endif /* USE_POLICY */

#include "sysdep.h"

#include "cert.h"
#include "conf.h"
#include "exchange.h"
#include "hash.h"
#include "ike_auth.h"
#include "ipsec.h"
#include "log.h"
#include "math_mp.h"
#include "policy.h"
#include "sa.h"
#include "util.h"
#include "x509.h"

static u_int16_t x509_hash (u_int8_t *, size_t);
static void x509_hash_init (void);
static X509 *x509_hash_find (u_int8_t *, size_t);
static int x509_hash_enter (X509 *);

/*
 * X509_STOREs do not support subjectAltNames, so we have to build
 * our own hash table.
 */

/*
 * XXX Actually this store is not really useful, we never use it as we have
 * our own hash table.  It also gets collisons if we have several certificates
 * only differing in subjectAltName.
 */
static X509_STORE *x509_certs = 0;
static X509_STORE *x509_cas = 0;

/* Initial number of bits used as hash.  */
#define INITIAL_BUCKET_BITS 6

struct x509_hash {
  LIST_ENTRY (x509_hash) link;

  X509 *cert;
};

static LIST_HEAD (x509_list, x509_hash) *x509_tab = 0;

/* Works both as a maximum index and a mask.  */
static int bucket_mask;

#ifdef USE_POLICY
/*
 * Given an X509 certificate, create a KeyNote assertion where
 * Issuer/Subject -> Authorizer/Licensees.
 * XXX RSA-specific.
 */
int
x509_generate_kn (int id, X509 *cert)
{
  char *fmt = "Authorizer: \"rsa-hex:%s\"\nLicensees: \"rsa-hex:%s\"\n"
    "Conditions: %s >= \"%s\" && %s <= \"%s\";\n";
  char *ikey, *skey, *buf, isname[256], subname[256];
  char *fmt2 = "Authorizer: \"DN:%s\"\nLicensees: \"DN:%s\"\n"
    "Conditions: %s >= \"%s\" && %s <= \"%s\";\n";
  X509_NAME *issuer, *subject;
  struct keynote_deckey dc;
  X509_STORE_CTX csc;
  X509_OBJECT obj;
  X509 *icert;
  RSA *key;
  time_t tt;
  char before[15], after[15];
  ASN1_TIME *tm;
  char *timecomp, *timecomp2;
  int i, buf_len;

  LOG_DBG ((LOG_POLICY, 90,
	    "x509_generate_kn: generating KeyNote policy for certificate %p",
	    cert));

  issuer = X509_get_issuer_name (cert);
  subject = X509_get_subject_name (cert);

  /* Missing or self-signed, ignore cert but don't report failure.  */
  if (!issuer || !subject || !X509_name_cmp (issuer, subject))
    return 1;

  if (!x509_cert_get_key (cert, &key))
    {
      LOG_DBG ((LOG_POLICY, 30,
		"x509_generate_kn: failed to get public key from cert"));
      return 0;
    }

  dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
  dc.dec_key = key;
  ikey = kn_encode_key (&dc, INTERNAL_ENC_PKCS1, ENCODING_HEX,
			KEYNOTE_PUBLIC_KEY);
  if (keynote_errno == ERROR_MEMORY)
    {
      log_print ("x509_generate_kn: failed to get memory for public key");
      RSA_free (key);
      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: cannot get subject key"));
      return 0;
    }
  if (!ikey)
    {
      RSA_free (key);
      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: cannot get subject key"));
      return 0;
    }
  RSA_free (key);

  /* Now find issuer's certificate so we can get the public key.  */
  X509_STORE_CTX_init (&csc, x509_cas, cert, NULL);
  if (X509_STORE_get_by_subject (&csc, X509_LU_X509, issuer, &obj) !=
      X509_LU_X509)
    {
      X509_STORE_CTX_cleanup (&csc);
      X509_STORE_CTX_init (&csc, x509_certs, cert, NULL);
      if (X509_STORE_get_by_subject (&csc, X509_LU_X509, issuer, &obj) !=
          X509_LU_X509)
	{
	  X509_STORE_CTX_cleanup (&csc);
	  LOG_DBG ((LOG_POLICY, 30,
		    "x509_generate_kn: no certificate found for issuer"));
	  return 0;
	}
    }

  X509_STORE_CTX_cleanup (&csc);
  icert = obj.data.x509;

  if (icert == NULL)
    {
      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: "
		"missing certificates, cannot construct X509 chain"));
      free (ikey);
      return 0;
    }

  if (!x509_cert_get_key (icert, &key))
    {
      LOG_DBG ((LOG_POLICY, 30,
		"x509_generate_kn: failed to get public key from cert"));
      free (ikey);
      return 0;
    }

  X509_OBJECT_free_contents (&obj);

  dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
  dc.dec_key = key;
  skey = kn_encode_key (&dc, INTERNAL_ENC_PKCS1, ENCODING_HEX,
			KEYNOTE_PUBLIC_KEY);
  if (keynote_errno == ERROR_MEMORY)
    {
      log_error ("x509_generate_kn: failed to get memory for public key");
      free (ikey);
      RSA_free (key);
      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: cannot get issuer key"));
      return 0;
    }

  if (!skey)
    {
      free (ikey);
      RSA_free (key);
      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: cannot get issuer key"));
      return 0;
    }
  RSA_free (key);

  buf_len = strlen (fmt) + strlen (ikey) + strlen (skey) + 56;
  buf = calloc (buf_len, sizeof (char));
  buf_len *= sizeof (char);
  if (!buf)
    {
      log_error ("x509_generate_kn: "
		 "failed to allocate memory for KeyNote credential");
      free (ikey);
      free (skey);
      return 0;
    }

  if (((tm = X509_get_notBefore (cert)) == NULL) ||
      (tm->type != V_ASN1_UTCTIME && tm->type != V_ASN1_GENERALIZEDTIME))
    {
      tt = time (0);
      strftime (before, 14, "%Y%m%d%H%M%S", localtime (&tt));
      timecomp = "LocalTimeOfDay";
    }
  else
    {
      if (tm->data[tm->length - 1] == 'Z')
	{
	  timecomp = "GMTTimeOfDay";
	  i = tm->length - 2;
	}
      else
        {
	  timecomp = "LocalTimeOfDay";
	  i = tm->length - 1;
	}

      for (; i >= 0; i--)
        {
	  if (tm->data[i] < '0' || tm->data[i] > '9')
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid data in "
			"NotValidBefore time field"));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }
	}

      if (tm->type == V_ASN1_UTCTIME)
	{
	  if ((tm->length < 10) || (tm->length > 13))
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid length "
			"of NotValidBefore time field (%d)", tm->length));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }

	  /* Validity checks.  */
	  if ((tm->data[2] != '0' && tm->data[2] != '1')
	      || (tm->data[2] == '0' && tm->data[3] == '0')
	      || (tm->data[2] == '1' && tm->data[3] > '2')
	      || (tm->data[4] > '3')
	      || (tm->data[4] == '0' && tm->data[5] == '0')
	      || (tm->data[4] == '3' && tm->data[5] > '1')
	      || (tm->data[6] > '2')
	      || (tm->data[6] == '2' && tm->data[7] > '3')
	      || (tm->data[8] > '5'))
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid value in "
			"NotValidBefore time field"));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }

	  /* Stupid UTC tricks.  */
	  if (tm->data[0] < '5')
	    snprintf (before, sizeof before, "20%s", tm->data);
	  else
	    snprintf (before, sizeof before, "19%s", tm->data);
	}
      else
        { /* V_ASN1_GENERICTIME */
	  if ((tm->length < 12) || (tm->length > 15))
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid length of "
			"NotValidBefore time field (%d)", tm->length));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }

	  /* Validity checks.  */
	  if ((tm->data[4] != '0' && tm->data[4] != '1')
	      || (tm->data[4] == '0' && tm->data[5] == '0')
	      || (tm->data[4] == '1' && tm->data[5] > '2')
	      || (tm->data[6] > '3')
	      || (tm->data[6] == '0' && tm->data[7] == '0')
	      || (tm->data[6] == '3' && tm->data[7] > '1')
	      || (tm->data[8] > '2')
	      || (tm->data[8] == '2' && tm->data[9] > '3')
	      || (tm->data[10] > '5'))
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid value in "
			"NotValidBefore time field"));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }

	  snprintf (before, sizeof before, "%s", tm->data);
	}

      /* Fix missing seconds.  */
      if (tm->length < 12)
        {
	  before[12] = '0';
	  before[13] = '0';
	}

      /* This will overwrite trailing 'Z'.  */
      before[14] = '\0';
    }

  tm = X509_get_notAfter (cert);
  if (tm == NULL
      && (tm->type != V_ASN1_UTCTIME && tm->type != V_ASN1_GENERALIZEDTIME))
    {
      tt = time (0);
      strftime (after, 14, "%Y%m%d%H%M%S", localtime (&tt));
      timecomp2 = "LocalTimeOfDay";
    }
  else
    {
      if (tm->data[tm->length - 1] == 'Z')
	{
	  timecomp2 = "GMTTimeOfDay";
	  i = tm->length - 2;
	}
      else
        {
	  timecomp2 = "LocalTimeOfDay";
	  i = tm->length - 1;
	}

      for (; i >= 0; i--)
        {
	  if (tm->data[i] < '0' || tm->data[i] > '9')
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid data in "
			"NotValidAfter time field"));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }
	}

      if (tm->type == V_ASN1_UTCTIME)
	{
	  if ((tm->length < 10) || (tm->length > 13))
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid length of "
			"NotValidAfter time field (%d)", tm->length));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }

	  /* Validity checks. */
	  if ((tm->data[2] != '0' && tm->data[2] != '1')
	      || (tm->data[2] == '0' && tm->data[3] == '0')
	      || (tm->data[2] == '1' && tm->data[3] > '2')
	      || (tm->data[4] > '3')
	      || (tm->data[4] == '0' && tm->data[5] == '0')
	      || (tm->data[4] == '3' && tm->data[5] > '1')
	      || (tm->data[6] > '2')
	      || (tm->data[6] == '2' && tm->data[7] > '3')
	      || (tm->data[8] > '5'))
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid value in "
			"NotValidAfter time field"));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }

	  /* Stupid UTC tricks.  */
	  if (tm->data[0] < '5')
	    snprintf (after, sizeof after, "20%s", tm->data);
	  else
	    snprintf (after, sizeof after, "19%s", tm->data);
	}
      else
        { /* V_ASN1_GENERICTIME */
	  if ((tm->length < 12) || (tm->length > 15))
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid length of "
			"NotValidAfter time field (%d)", tm->length));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }

	  /* Validity checks.  */
	  if ((tm->data[4] != '0' && tm->data[4] != '1')
	      || (tm->data[4] == '0' && tm->data[5] == '0')
	      || (tm->data[4] == '1' && tm->data[5] > '2')
	      || (tm->data[6] > '3')
	      || (tm->data[6] == '0' && tm->data[7] == '0')
	      || (tm->data[6] == '3' && tm->data[7] > '1')
	      || (tm->data[8] > '2')
	      || (tm->data[8] == '2' && tm->data[9] > '3')
	      || (tm->data[10] > '5'))
	    {
	      LOG_DBG ((LOG_POLICY, 30, "x509_generate_kn: invalid value in "
			"NotValidAfter time field"));
	      free (ikey);
	      free (skey);
	      free (buf);
	      return 0;
	    }

	  snprintf (after, sizeof after, "%s", tm->data);
        }

      /* Fix missing seconds.  */
      if (tm->length < 12)
        {
	  after[12] = '0';
	  after[13] = '0';
	}

      after[14] = '\0'; /* This will overwrite trailing 'Z' */
    }

  snprintf (buf, buf_len, fmt, skey, ikey, timecomp, before, timecomp2, after);
  free (ikey);
  free (skey);

  if (kn_add_assertion (id, buf, strlen (buf), ASSERT_FLAG_LOCAL) == -1)
    {
      LOG_DBG ((LOG_POLICY, 30,
		"x509_generate_kn: failed to add new KeyNote credential"));
      free (buf);
      return 0;
    }

  /* We could print the assertion here, but log_print() truncates...  */
  LOG_DBG ((LOG_POLICY, 60, "x509_generate_kn: added credential"));

  free (buf);

  if (!X509_NAME_oneline (issuer, isname, 256))
    {
      LOG_DBG ((LOG_POLICY, 50,
		"x509_generate_kn: X509_NAME_oneline (issuer, ...) failed"));
      return 0;
    }

  if (!X509_NAME_oneline (subject, subname, 256))
    {
      LOG_DBG ((LOG_POLICY, 50,
		"x509_generate_kn: X509_NAME_oneline (subject, ...) failed"));
      return 0;
    }

  buf_len = strlen (fmt2) + strlen (isname) + strlen (subname) + 56;
  buf = malloc (buf_len);
  if (!buf)
    {
      log_error ("x509_generate_kn: malloc (%d) failed", buf_len);
      return 0;
    }

  snprintf (buf, buf_len, fmt2, isname, subname, timecomp, before, timecomp2,
	    after);

  if (kn_add_assertion (id, buf, strlen (buf), ASSERT_FLAG_LOCAL) == -1)
    {
      LOG_DBG ((LOG_POLICY, 30,
		"x509_generate_kn: failed to add new KeyNote credential"));
      free (buf);
      return 0;
    }

  LOG_DBG ((LOG_POLICY, 80, "x509_generate_kn: added credential:\n%s", buf));

  free (buf);
  return 1;
}
#endif /* USE_POLICY */

static u_int16_t
x509_hash (u_int8_t *id, size_t len)
{
  int i;
  u_int16_t bucket = 0;

  /* XXX We might resize if we are crossing a certain threshold.  */
  for (i = 4; i < (len & ~1); i += 2)
    {
      /* Doing it this way avoids alignment problems.  */
      bucket ^= (id[i] + 1) * (id[i + 1] + 257);
    }
  /* Hash in the last character of odd length IDs too.  */
  if (i < len)
    bucket ^= (id[i] + 1) * (id[i] + 257);

  bucket &= bucket_mask;

  return bucket;
}

static void
x509_hash_init (void)
{
  struct x509_hash *certh;
  int i;

  bucket_mask = (1 << INITIAL_BUCKET_BITS) - 1;

  /* If reinitializing, free existing entries.  */
  if (x509_tab)
    {
      for (i = 0; i <= bucket_mask; i++)
        for (certh = LIST_FIRST (&x509_tab[i]); certh;
             certh = LIST_FIRST (&x509_tab[i]))
	  {
	    LIST_REMOVE (certh, link);
	    free (certh);
	  }

      free (x509_tab);
    }

  x509_tab = malloc ((bucket_mask + 1) * sizeof (struct x509_list));
  if (!x509_tab)
    log_fatal ("x509_hash_init: malloc (%lu) failed",
	       (bucket_mask + 1) * (unsigned long)sizeof (struct x509_list));
  for (i = 0; i <= bucket_mask; i++)
    {
      LIST_INIT (&x509_tab[i]);
    }
}

/* Lookup a certificate by an ID blob.  */
static X509 *
x509_hash_find (u_int8_t *id, size_t len)
{
  struct x509_hash *cert;
  u_int8_t **cid;
  u_int32_t *clen;
  int n, i, id_found;

  for (cert = LIST_FIRST (&x509_tab[x509_hash (id, len)]); cert;
       cert = LIST_NEXT (cert, link))
    {
      if (!x509_cert_get_subjects (cert->cert, &n, &cid, &clen))
	continue;

      id_found = 0;
      for (i = 0; i < n; i++)
	{
	  LOG_DBG_BUF ((LOG_CRYPTO, 70, "cert_cmp", id, len));
	  LOG_DBG_BUF ((LOG_CRYPTO, 70, "cert_cmp", cid[i], clen[i]));
	  /* XXX This identity predicate needs to be understood.  */
	  if (clen[i] == len && id[0] == cid[i][0]
	      && memcmp (id + 4, cid[i] + 4, len - 4) == 0)
	    {
	      id_found++;
	      break;
	    }
	}
      cert_free_subjects (n, cid, clen);
      if (!id_found)
	continue;

      LOG_DBG ((LOG_CRYPTO, 70, "x509_hash_find: return X509 %p",
		cert->cert));
      return cert->cert;
    }

  LOG_DBG ((LOG_CRYPTO, 70, "x509_hash_find: no certificate matched query"));
  return 0;
}

static int
x509_hash_enter (X509 *cert)
{
  u_int16_t bucket = 0;
  u_int8_t **id;
  u_int32_t *len;
  struct x509_hash *certh;
  int n, i;

  if (!x509_cert_get_subjects (cert, &n, &id, &len))
    {
      log_print ("x509_hash_enter: cannot retrieve subjects");
      return 0;
    }

  for (i = 0; i < n; i++)
    {
      certh = calloc (1, sizeof *certh);
      if (!certh)
        {
          cert_free_subjects (n, id, len);
          log_error ("x509_hash_enter: calloc (1, %lu) failed",
		(unsigned long)sizeof *certh);
          return 0;
        }

      certh->cert = cert;

      bucket = x509_hash (id[i], len[i]);

      LIST_INSERT_HEAD (&x509_tab[bucket], certh, link);
      LOG_DBG ((LOG_CRYPTO, 70, "x509_hash_enter: cert %p added to bucket %d",
		cert, bucket));
    }
  cert_free_subjects (n, id, len);

  return 1;
}

/* X509 Certificate Handling functions.  */

int
x509_read_from_dir (X509_STORE *ctx, char *name, int hash)
{
  DIR *dir;
  struct dirent *file;
  BIO *certh;
  X509 *cert;
  char fullname[PATH_MAX];
  int off, size;

  if (strlen (name) >= sizeof fullname - 1)
    {
      log_print ("x509_read_from_dir: directory name too long");
      return 0;
    }

  LOG_DBG ((LOG_CRYPTO, 40, "x509_read_from_dir: reading certs from %s",
	    name));

  dir = opendir (name);
  if (!dir)
    {
      LOG_DBG ((LOG_CRYPTO, 10, "x509_read_from_dir: opendir (\"%s\") failed: "
		"%s", name, strerror (errno)));
      return 0;
    }

  strlcpy (fullname, name, sizeof fullname);
  off = strlen (fullname);
  size = sizeof fullname - off;

  while ((file = readdir (dir)) != NULL)
    {
      strlcpy (fullname + off, file->d_name, size);

      if (file->d_type != DT_UNKNOWN)
      {
	 if (file->d_type != DT_REG && file->d_type != DT_LNK)
	   continue;
      }
      else
      {
	struct stat sb;

	if (stat (fullname, &sb) == -1 || !(sb.st_mode & S_IFREG))
          continue;
      }

      LOG_DBG ((LOG_CRYPTO, 60, "x509_read_from_dir: reading certificate %s",
		file->d_name));

      certh = BIO_new (BIO_s_file ());
      if (!certh)
	{
	  log_error ("x509_read_from_dir: BIO_new (BIO_s_file ()) failed");
	  continue;
	}

      if (BIO_read_filename (certh, fullname) == -1)
	{
	  BIO_free (certh);
	  log_error ("x509_read_from_dir: "
		     "BIO_read_filename (certh, \"%s\") failed",
		     fullname);
	  continue;
	}

#if SSLEAY_VERSION_NUMBER >= 0x00904100L
      cert = PEM_read_bio_X509 (certh, NULL, NULL, NULL);
#else
      cert = PEM_read_bio_X509 (certh, NULL, NULL);
#endif
      BIO_free (certh);
      if (cert == NULL)
	{
	  log_print ("x509_read_from_dir: PEM_read_bio_X509 failed for %s",
		     file->d_name);
	  continue;
	}

      if (!X509_STORE_add_cert (ctx, cert))
	{
	  /*
	   * This is actually expected if we have several certificates only
	   * differing in subjectAltName, which is not an something that is
	   * strange.  Consider multi-homed machines.
	   */
	  LOG_DBG ((LOG_CRYPTO, 50,
		    "x509_read_from_dir: X509_STORE_add_cert failed for %s",
		    file->d_name));
	}

      if (hash)
	if (!x509_hash_enter (cert))
	  log_print ("x509_read_from_dir: x509_hash_enter (%s) failed",
		     file->d_name);
    }

  closedir (dir);

  return 1;
}

/* XXX share code with x509_read_from_dir() ?  */
int
x509_read_crls_from_dir (X509_STORE *ctx, char *name)
{
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
  DIR *dir;
  struct dirent *file;
  BIO *crlh;
  X509_CRL *crl;
  char fullname[PATH_MAX];
  int off, size;

  if (strlen (name) >= sizeof fullname - 1)
    {
      log_print ("x509_read_crls_from_dir: directory name too long");
      return 0;
    }

  LOG_DBG ((LOG_CRYPTO, 40, "x509_read_crls_from_dir: reading CRLs from %s",
	    name));

  dir = opendir (name);
  if (!dir)
    {
      LOG_DBG ((LOG_CRYPTO, 10, "x509_read_crls_from_dir: opendir (\"%s\") "
		"failed: %s", name, strerror (errno)));
      return 0;
    }

  strlcpy (fullname, name, sizeof fullname);
  off = strlen (fullname);
  size = sizeof fullname - off;

  while ((file = readdir (dir)) != NULL)
    {
      strlcpy (fullname + off, file->d_name, size);

      if (file->d_type != DT_UNKNOWN)
      {
	  if (file->d_type != DT_REG && file->d_type != DT_LNK)
	    continue;
      }
      else
      {
	struct stat sb;

	if (stat (fullname, &sb) == -1 || !(sb.st_mode & S_IFREG))
	  continue;
      }

      LOG_DBG ((LOG_CRYPTO, 60, "x509_read_crls_from_dir: reading CRL %s",
		file->d_name));

      crlh = BIO_new (BIO_s_file ());
      if (!crlh)
	{
	  log_error ("x509_read_crls_from_dir: "
		     "BIO_new (BIO_s_file ()) failed");
	  continue;
	}

      if (BIO_read_filename (crlh, fullname) == -1)
	{
	  BIO_free (crlh);
	  log_error ("x509_read_crls_from_dir: "
		     "BIO_read_filename (crlh, \"%s\") failed", fullname);
	  continue;
	}

      crl = PEM_read_bio_X509_CRL (crlh, NULL, NULL, NULL);

      BIO_free (crlh);
      if (crl == NULL)
	{
	  log_print ("x509_read_crls_from_dir: "
		     "PEM_read_bio_X509_CRL failed for %s", file->d_name);
	  continue;
	}

      if (!X509_STORE_add_crl (ctx, crl))
	{
	  LOG_DBG ((LOG_CRYPTO, 50, "x509_read_crls_from_dir: "
		    "X509_STORE_add_crl failed for %s", file->d_name));
	  continue;
	}

      /*
       * XXX This is to make x509_cert_validate set this (and another) flag
       * XXX when validating certificates. Currently, OpenSSL defaults to
       * XXX reject an otherwise valid certificate (chain) if these flags
       * XXX are set but there are no CRLs to check. The current workaround
       * XXX is to only set the flags if we actually loaded some CRL data.
       */
      X509_STORE_set_flags (ctx, X509_V_FLAG_CRL_CHECK);
    }

  closedir (dir);
#endif /* OPENSSL_VERSION_NUMBER >= 0x00907000L */

  return 1;
}

/* Initialize our databases and load our own certificates.  */
int
x509_cert_init (void)
{
  char *dirname;

  x509_hash_init ();

  /* Process CA certificates we will trust.  */
  dirname = conf_get_str ("X509-certificates", "CA-directory");
  if (!dirname)
    {
      log_print ("x509_cert_init: no CA-directory");
      return 0;
    }

  /* Free if already initialized.  */
  if (x509_cas)
    X509_STORE_free (x509_cas);

  x509_cas = X509_STORE_new ();
  if (!x509_cas)
    {
      log_print ("x509_cert_init: creating new X509_STORE failed");
      return 0;
    }

  if (!x509_read_from_dir (x509_cas, dirname, 0))
    {
      log_print ("x509_cert_init: x509_read_from_dir failed");
      return 0;
    }

  /* Process client certificates we will accept.  */
  dirname = conf_get_str ("X509-certificates", "Cert-directory");
  if (!dirname)
    {
      log_print ("x509_cert_init: no Cert-directory");
      return 0;
    }

  /* Free if already initialized.  */
  if (x509_certs)
    X509_STORE_free (x509_certs);

  x509_certs = X509_STORE_new ();
  if (!x509_certs)
    {
      log_print ("x509_cert_init: creating new X509_STORE failed");
      return 0;
    }

  if (!x509_read_from_dir (x509_certs, dirname, 1))
    {
      log_print ("x509_cert_init: x509_read_from_dir failed");
      return 0;
    }

  return 1;
}

int
x509_crl_init (void)
{
  /*
   * XXX I'm not sure if the method to use CRLs in certificate validation
   * is valid for OpenSSL versions prior to 0.9.7. For now, simply do not
   * support it.
   */
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
  char *dirname;
  dirname = conf_get_str ("X509-certificates", "CRL-directory");
  if (!dirname)
    {
      log_print ("x509_crl_init: no CRL-directory");
      return 0;
    }

  if (!x509_read_crls_from_dir (x509_cas, dirname))
    {
      LOG_DBG ((LOG_MISC, 10, "x509_crl_init: x509_read_crls_from_dir failed"));
      return 0;
    }
#else
  LOG_DBG ((LOG_CRYPTO, 10, "x509_crl_init: CRL support only "
	    "with OpenSSL v0.9.7 or later"));
#endif

  return 1;
}

void *
x509_cert_get (u_int8_t *asn, u_int32_t len)
{
  return x509_from_asn (asn, len);
}

int
x509_cert_validate (void *scert)
{
  X509_STORE_CTX csc;
  X509_NAME *issuer, *subject;
  X509 *cert = (X509 *)scert;
  EVP_PKEY *key;
  int res, err;

  /*
   * Validate the peer certificate by checking with the CA certificates we
   * trust.
   */
  X509_STORE_CTX_init (&csc, x509_cas, cert, NULL);
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
  /* XXX See comment in x509_read_crls_from_dir.  */
  if (x509_cas->flags & X509_V_FLAG_CRL_CHECK)
    {
      X509_STORE_CTX_set_flags (&csc, X509_V_FLAG_CRL_CHECK);
      X509_STORE_CTX_set_flags (&csc, X509_V_FLAG_CRL_CHECK_ALL);
    }
#endif
  res = X509_verify_cert (&csc);
  err = csc.error;
  X509_STORE_CTX_cleanup (&csc);

  /* Return if validation succeeded or self-signed certs are not accepted.  */
  if (res)
    return 1;
  else if (!conf_get_str ("X509-certificates", "Accept-self-signed"))
    {
      if (err)
	log_print ("x509_cert_validate: %.100s",
		   X509_verify_cert_error_string (err));
      return res;
    }

  issuer = X509_get_issuer_name (cert);
  subject = X509_get_subject_name (cert);

  if (!issuer || !subject || X509_name_cmp (issuer, subject))
    return 0;

  key = X509_get_pubkey (cert);
  if (!key)
    {
      log_print ("x509_cert_validate: could not get public key from "
		 "self-signed cert");
      return 0;
    }

  if (X509_verify (cert, key) == -1)
    {
      log_print ("x509_cert_validate: self-signed cert is bad");
      return 0;
    }

  return 1;
}

int
x509_cert_insert (int id, void *scert)
{
  X509 *cert;
  int res;

  cert = X509_dup ((X509 *)scert);
  if (!cert)
    {
      log_print ("x509_cert_insert: X509_dup failed");
      return 0;
    }

#ifdef USE_POLICY
  if (x509_generate_kn (id, cert) == 0)
    {
      LOG_DBG ((LOG_POLICY, 50,
		"x509_cert_insert: x509_generate_kn failed"));
      X509_free (cert);
      return 0;
    }
#endif /* USE_POLICY */

  res = x509_hash_enter (cert);
  if (!res)
    X509_free (cert);

  return res;
}

static struct x509_hash *
x509_hash_lookup (X509 *cert)
{
  int i;
  struct x509_hash *certh;

  for (i = 0; i <= bucket_mask; i++)
    for (certh = LIST_FIRST (&x509_tab[i]); certh;
	 certh = LIST_NEXT (certh, link))
      if (certh->cert == cert)
	return certh;
  return 0;
}

void
x509_cert_free (void *cert)
{
  struct x509_hash *certh = x509_hash_lookup ((X509 *)cert);

  if (certh)
    LIST_REMOVE (certh, link);
  X509_free ((X509 *)cert);
}

/* Validate the BER Encoding of a RDNSequence in the CERT_REQ payload.  */
int
x509_certreq_validate (u_int8_t *asn, u_int32_t len)
{
  int res = 1;
#if 0
  struct norm_type name = SEQOF ("issuer", RDNSequence);

  if (!asn_template_clone (&name, 1)
      || (asn = asn_decode_sequence (asn, len, &name)) == 0)
    {
      log_print ("x509_certreq_validate: can not decode 'acceptable CA' info");
      res = 0;
    }
  asn_free (&name);
#endif

  /* XXX - not supported directly in SSL - later.  */

  return res;
}

/* Decode the BER Encoding of a RDNSequence in the CERT_REQ payload.  */
void *
x509_certreq_decode (u_int8_t *asn, u_int32_t len)
{
#if 0
  /* XXX This needs to be done later.  */
  struct norm_type aca = SEQOF ("aca", RDNSequence);
  struct norm_type *tmp;
  struct x509_aca naca, *ret;

  if (!asn_template_clone (&aca, 1)
      || (asn = asn_decode_sequence (asn, len, &aca)) == 0)
    {
      log_print ("x509_certreq_decode: can not decode 'acceptable CA' info");
      goto fail;
    }
  memset (&naca, 0, sizeof (naca));

  tmp = asn_decompose ("aca.RelativeDistinguishedName.AttributeValueAssertion",
		       &aca);
  if (!tmp)
    goto fail;
  x509_get_attribval (tmp, &naca.name1);

  tmp = asn_decompose ("aca.RelativeDistinguishedName[1]"
		       ".AttributeValueAssertion", &aca);
  if (tmp)
    x509_get_attribval (tmp, &naca.name2);

  asn_free (&aca);

  ret = malloc (sizeof (struct x509_aca));
  if (ret)
    memcpy (ret, &naca, sizeof (struct x509_aca));
  else
    {
      log_error ("x509_certreq_decode: malloc (%lu) failed",
		 (unsigned long)sizeof (struct x509_aca));
      x509_free_aca (&aca);
    }

  return ret;

 fail:
  asn_free (&aca);
#endif
  return 0;
}

void
x509_free_aca (void *blob)
{
  struct x509_aca *aca = blob;

  if (aca->name1.type)
    free (aca->name1.type);
  if (aca->name1.val)
    free (aca->name1.val);

  if (aca->name2.type)
    free (aca->name2.type);
  if (aca->name2.val)
    free (aca->name2.val);
}

X509 *
x509_from_asn (u_char *asn, u_int len)
{
  BIO *certh;
  X509 *scert = 0;

  certh = BIO_new (BIO_s_mem ());
  if (!certh)
    {
      log_error ("x509_from_asn: BIO_new (BIO_s_mem ()) failed");
      return 0;
    }

  if (BIO_write (certh, asn, len) == -1)
    {
      log_error ("x509_from_asn: BIO_write failed\n");
      goto end;
    }

  scert = d2i_X509_bio (certh, NULL);
  if (!scert)
    {
      log_print ("x509_from_asn: d2i_X509_bio failed\n");
      goto end;
    }

 end:
  BIO_free (certh);
  return scert;
}

/*
 * Obtain a certificate from an acceptable CA.
 * XXX We don't check if the certificate we find is from an accepted CA.
 */
int
x509_cert_obtain (u_int8_t *id, size_t id_len, void *data, u_int8_t **cert,
		  u_int32_t *certlen)
{
  struct x509_aca *aca = data;
  X509 *scert;

  if (aca)
    LOG_DBG ((LOG_CRYPTO, 60,
	      "x509_cert_obtain: acceptable certificate authorities here"));

  /* We need our ID to find a certificate.  */
  if (!id)
    {
      log_print ("x509_cert_obtain: ID is missing");
      return 0;
    }

  scert = x509_hash_find (id, id_len);
  if (!scert)
    return 0;

  x509_serialize (scert, cert, certlen);
  if (!*cert)
    return 0;
  return 1;
}

/* Returns a pointer to the subjectAltName information of X509 certificate.  */
int
x509_cert_subjectaltname (X509 *scert, u_int8_t **altname, u_int32_t *len)
{
  X509_EXTENSION *subjectaltname;
  u_int8_t *sandata;
  int extpos;
  int santype, sanlen;

  extpos = X509_get_ext_by_NID (scert, NID_subject_alt_name, -1);
  if (extpos == -1)
    {
      log_print ("x509_cert_subjectaltname: "
		 "certificate does not contain subjectAltName");
      return 0;
    }

  subjectaltname = X509_get_ext (scert, extpos);

  if (!subjectaltname || !subjectaltname->value
      || !subjectaltname->value->data || subjectaltname->value->length < 4)
    {
      log_print ("x509_cert_subjectaltname: invalid subjectaltname extension");
      return 0;
    }

  /* SSL does not handle unknown ASN stuff well, do it by hand.  */
  sandata = subjectaltname->value->data;
  santype = sandata[2] & 0x3f;
  sanlen = sandata[3];
  sandata += 4;

  if (sanlen + 4 != subjectaltname->value->length)
    {
      log_print ("x509_cert_subjectaltname: subjectaltname invalid length");
      return 0;
    }

  *len = sanlen;
  *altname = sandata;

  return santype;
}

int
x509_cert_get_subjects (void *scert, int *cnt, u_int8_t ***id,
			u_int32_t **id_len)
{
  X509 *cert = scert;
  X509_NAME *subject;
  int type;
  u_int8_t *altname;
  u_int32_t altlen;
  u_int8_t *buf = 0;
  unsigned char *ubuf;
  int i;

  *id = 0;
  *id_len = 0;

  /*
   * XXX There can be a collection of subjectAltNames, but for now
   * I only return the subjectName and a single subjectAltName, if present.
   */
  type = x509_cert_subjectaltname (cert, &altname, &altlen);
  if (!type)
    {
      *cnt = 1;
      altlen = 0;
    }
  else
    *cnt = 2;

  *id = calloc (*cnt, sizeof **id);
  if (!*id)
    {
      log_print ("x509_cert_get_subject: malloc (%lu) failed",
		 *cnt * (unsigned long)sizeof **id);
      goto fail;
    }

  *id_len = malloc (*cnt * sizeof **id_len);
  if (!*id_len)
    {
      log_print ("x509_cert_get_subject: malloc (%lu) failed",
		 *cnt * (unsigned long)sizeof **id_len);
      goto fail;
    }

  /* Stash the subjectName into the first slot.  */
  subject = X509_get_subject_name (cert);
  if (!subject)
    goto fail;

  (*id_len)[0] =
    ISAKMP_ID_DATA_OFF + i2d_X509_NAME (subject, NULL) - ISAKMP_GEN_SZ;
  (*id)[0] = malloc ((*id_len)[0]);
  if (!(*id)[0])
    {
      log_print ("x509_cert_get_subject: malloc (%d) failed", (*id_len)[0]);
      goto fail;
    }
  SET_ISAKMP_ID_TYPE ((*id)[0] - ISAKMP_GEN_SZ, IPSEC_ID_DER_ASN1_DN);
  ubuf = (*id)[0] + ISAKMP_ID_DATA_OFF - ISAKMP_GEN_SZ;
  i2d_X509_NAME (subject, &ubuf);

  if (altlen)
    {
      /* Stash the subjectAltName into the second slot.  */
      buf = malloc (altlen + ISAKMP_ID_DATA_OFF);
      if (!buf)
	{
	  log_print ("x509_cert_get_subject: malloc (%d) failed",
		     altlen + ISAKMP_ID_DATA_OFF);
	  goto fail;
	}

      switch (type)
	{
	case X509v3_DNS_NAME:
	  SET_ISAKMP_ID_TYPE (buf, IPSEC_ID_FQDN);
	  break;

	case X509v3_RFC_NAME:
	  SET_ISAKMP_ID_TYPE (buf, IPSEC_ID_USER_FQDN);
	  break;

	case X509v3_IP_ADDR:
	  /*
	   * XXX I dislike the numeric constants, but I don't know what we
	   * should use otherwise.
	   */
	  switch (altlen)
	    {
	    case 4:
	      SET_ISAKMP_ID_TYPE (buf, IPSEC_ID_IPV4_ADDR);
	      break;

	    case 16:
	      SET_ISAKMP_ID_TYPE (buf, IPSEC_ID_IPV6_ADDR);
	      break;

	    default:
	      log_print ("x509_cert_get_subject: "
			 "invalid subjectAltName IPaddress length %d ", altlen);
	      goto fail;
	    }
	  break;
	}

      SET_IPSEC_ID_PROTO (buf + ISAKMP_ID_DOI_DATA_OFF, 0);
      SET_IPSEC_ID_PORT (buf + ISAKMP_ID_DOI_DATA_OFF, 0);
      memcpy (buf + ISAKMP_ID_DATA_OFF, altname, altlen);

      (*id_len)[1] = ISAKMP_ID_DATA_OFF + altlen - ISAKMP_GEN_SZ;
      (*id)[1] = malloc ((*id_len)[1]);
      if (!(*id)[1])
	{
	  log_print ("x509_cert_get_subject: malloc (%d) failed",
		     (*id_len)[1]);
	  goto fail;
	}
      memcpy ((*id)[1], buf + ISAKMP_GEN_SZ, (*id_len)[1]);

      free (buf);
      buf = 0;
    }

  return 1;

 fail:
  for (i = 0; i < *cnt; i++)
    if ((*id)[i])
      free ((*id)[i]);
  if (*id)
    free (*id);
  if (*id_len)
    free (*id_len);
  if (buf)
    free (buf);
  return 0;
}

int
x509_cert_get_key (void *scert, void *keyp)
{
  X509 *cert = scert;
  EVP_PKEY *key;

  key = X509_get_pubkey (cert);

  /* Check if we got the right key type.  */
  if (key->type != EVP_PKEY_RSA)
    {
      log_print ("x509_cert_get_key: public key is not a RSA key");
      X509_free (cert);
      return 0;
    }

  *(RSA **)keyp = RSAPublicKey_dup (key->pkey.rsa);

  return *(RSA **)keyp == NULL ? 0 : 1;
}

void *
x509_cert_dup (void *scert)
{
  return X509_dup (scert);
}

void
x509_serialize (void *scert, u_int8_t **data, u_int32_t *datalen)
{
  u_int8_t *p;

  *datalen = i2d_X509 ((X509 *) scert, NULL);
  *data = p = malloc (*datalen);
  if (!p)
    {
      log_error ("x509_serialize: malloc (%d) failed", *datalen);
      return;
    }

  *datalen = i2d_X509 ((X509 *)scert, &p);
}

/* From cert to printable */
char *
x509_printable (void *cert)
{
  char *s;
  u_int8_t *data;
  u_int32_t datalen;
  int i;

  x509_serialize (cert, &data, &datalen);
  if (!data)
    return 0;

  s = malloc (datalen * 2 + 1);
  if (!s)
    {
      free (data);
      log_error ("x509_printable: malloc (%d) failed", datalen * 2 + 1);
      return 0;
    }

  for (i = 0; i < datalen; i++)
    snprintf (s + (2 * i), 2 * (datalen - i) + 1, "%02x", data[i]);
  free (data);
  return s;
}

/* From printable to cert */
void *
x509_from_printable (char *cert)
{
  u_int8_t *buf;
  int plen, ret;
  void *foo;

  plen = (strlen (cert) + 1) / 2;
  buf = malloc (plen);
  if (!buf)
    {
      log_error ("x509_from_printable: malloc (%d) failed", plen);
      return 0;
    }

  ret = hex2raw (cert, buf, plen);
  if (ret == -1)
    {
      free (buf);
      log_print ("x509_from_printable: badly formatted cert");
      return 0;
    }

  foo = x509_cert_get (buf, plen);
  free (buf);
  if (!foo)
    log_print ("x509_from_printable: could not retrieve certificate");
  return foo;
}

char *
x509_DN_string (u_int8_t *asn1, size_t sz)
{
  X509_NAME *name;
  u_int8_t *p = asn1;
  /* XXX Just a guess at a maximum length.  */
  char buf[256];

  name = d2i_X509_NAME (NULL, &p, sz);
  if (!name)
    {
      log_print ("x509_DN_string: d2i_X509_NAME failed");
      return 0;
    }
  if (!X509_NAME_oneline (name, buf, sizeof buf - 1))
    {
      log_print ("x509_DN_string: X509_NAME_oneline failed");
      X509_NAME_free (name);
      return 0;
    }
  X509_NAME_free (name);
  buf[sizeof buf - 1] = '\0';
  return strdup (buf);
}
#endif /* USE_X509 */
