/* crl.c - CRL parser
 * Copyright (C) 2002, 2004, 2005, 2012 g10 Code GmbH
 *
 * This file is part of KSBA.
 *
 * KSBA is free software; you can redistribute it and/or modify
 * it under the terms of either
 *
 *   - the GNU Lesser General Public License as published by the Free
 *     Software Foundation; either version 3 of the License, or (at
 *     your option) any later version.
 *
 * or
 *
 *   - 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.
 *
 * or both in parallel, as here.
 *
 * KSBA 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.
 *
 * You should have received a copies of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#include "util.h"

#include "convert.h"
#include "keyinfo.h"
#include "der-encoder.h"
#include "ber-help.h"
#include "ber-decoder.h"
#include "crl.h"
#include "stringbuf.h"


static const char oidstr_crlNumber[] = "2.5.29.20";
static const char oidstr_crlReason[] = "2.5.29.21";
#if 0
static const char oidstr_issuingDistributionPoint[] = "2.5.29.28";
#endif
static const char oidstr_certificateIssuer[] = "2.5.29.29";
static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35";

/* We better buffer the hashing. */
static inline void
do_hash (ksba_crl_t crl, const void *buffer, size_t length)
{
  while (length)
    {
      size_t n = length;

      if (crl->hashbuf.used + n > sizeof crl->hashbuf.buffer)
        n = sizeof crl->hashbuf.buffer - crl->hashbuf.used;
      memcpy (crl->hashbuf.buffer+crl->hashbuf.used, buffer, n);
      crl->hashbuf.used += n;
      if (crl->hashbuf.used == sizeof crl->hashbuf.buffer)
        {
          if (crl->hash_fnc)
            crl->hash_fnc (crl->hash_fnc_arg,
                           crl->hashbuf.buffer, crl->hashbuf.used);
          crl->hashbuf.used = 0;
        }
      buffer = (const char *)buffer + n;
      length -= n;
    }
}

#define HASH(a,b) do_hash (crl, (a), (b))



/**
 * ksba_crl_new:
 *
 * Create a new and empty CRL object
 *
 * Return value: A CRL object or an error code.
 **/
gpg_error_t
ksba_crl_new (ksba_crl_t *r_crl)
{
  *r_crl = xtrycalloc (1, sizeof **r_crl);
  if (!*r_crl)
    return gpg_error_from_errno (errno);
  return 0;
}


/**
 * ksba_crl_release:
 * @crl: A CRL object
 *
 * Release a CRL object.
 **/
void
ksba_crl_release (ksba_crl_t crl)
{
  if (!crl)
    return;
  xfree (crl->algo.oid);
  xfree (crl->algo.parm);

  _ksba_asn_release_nodes (crl->issuer.root);
  xfree (crl->issuer.image);

  xfree (crl->item.serial);

  xfree (crl->sigval);
  while (crl->extension_list)
    {
      crl_extn_t tmp = crl->extension_list->next;
      xfree (crl->extension_list->oid);
      xfree (crl->extension_list);
      crl->extension_list = tmp;
    }

  xfree (crl);
}


gpg_error_t
ksba_crl_set_reader (ksba_crl_t crl, ksba_reader_t r)
{
  if (!crl || !r)
    return gpg_error (GPG_ERR_INV_VALUE);

  crl->reader = r;
  return 0;
}

/* Provide a hash function so that we are able to hash the data */
void
ksba_crl_set_hash_function (ksba_crl_t crl,
                            void (*hash_fnc)(void *, const void *, size_t),
                            void *hash_fnc_arg)
{
  if (crl)
    {
      crl->hash_fnc = hash_fnc;
      crl->hash_fnc_arg = hash_fnc_arg;
    }
}



/*
   access functions
*/


/**
 * ksba_crl_get_digest_algo:
 * @cms: CMS object
 *
 * Figure out the the digest algorithm used for the signature and return
 * its OID.
 *
 * Return value: NULL if the signature algorithm is not yet available
 * or there is a mismatched between "tbsCertList.signature" and
 * "signatureAlgorithm"; on success the OID is returned which is valid
 * as long as the CRL object is valid.
 **/
const char *
ksba_crl_get_digest_algo (ksba_crl_t crl)
{
  if (!crl)
    return NULL;

  /* fixme: implement the described check */

  return crl->algo.oid;
}


/**
 * ksba_crl_get_issuer:
 * @cms: CMS object
 * @r_issuer: returns the issuer
 *
 * This functions returns the issuer of the CRL.  The caller must
 * release the returned object.
 *
 * Return value: 0 on success or an error code
 **/
gpg_error_t
ksba_crl_get_issuer (ksba_crl_t crl, char **r_issuer)
{
  gpg_error_t err;
  AsnNode n;
  const unsigned char *image;

  if (!crl || !r_issuer)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (!crl->issuer.root)
    return gpg_error (GPG_ERR_NO_DATA);

  n = crl->issuer.root;
  image = crl->issuer.image;

  if (!n || !n->down)
    return gpg_error (GPG_ERR_NO_VALUE);
  n = n->down; /* dereference the choice node */

  if (n->off == -1)
    {
/*        fputs ("get_issuer problem at node:\n", stderr); */
/*        _ksba_asn_node_dump_all (n, stderr); */
      return gpg_error (GPG_ERR_GENERAL);
    }
  err = _ksba_dn_to_str (image, n, r_issuer);

  return err;
}


/* Return the CRL extension in OID, CRITICAL, DER and DERLEN.  The
   caller should iterate IDX from 0 upwards until GPG_ERR_EOF is
   returned. Note, that the returned values are valid as long as the
   context is valid and no new parsing has been started. */
gpg_error_t
ksba_crl_get_extension (ksba_crl_t crl, int idx,
                        char const **oid, int *critical,
                        unsigned char const **der, size_t *derlen)
{
  crl_extn_t e;

  if (!crl)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (idx < 0)
    return gpg_error (GPG_ERR_INV_INDEX);

  for (e=crl->extension_list; e && idx; e = e->next, idx-- )
    ;
  if (!e)
    return gpg_error (GPG_ERR_EOF);

  if (oid)
    *oid = e->oid;
  if (critical)
    *critical = e->critical;
  if (der)
    *der = e->der;
  if (derlen)
    *derlen = e->derlen;

  return 0;
}


/* Return the authorityKeyIdentifier in r_name and r_serial or in
   r_keyID.  GPG_ERR_NO_DATA is returned if no authorityKeyIdentifier
   or only one using the keyIdentifier method is available and R_KEYID
   is NULL.

   FIXME: This function shares a lot of code with the one in cert.c
*/
gpg_error_t
ksba_crl_get_auth_key_id (ksba_crl_t crl,
                          ksba_sexp_t *r_keyid,
                          ksba_name_t *r_name,
                          ksba_sexp_t *r_serial)
{
  gpg_error_t err;
  size_t derlen;
  const unsigned char *der;
  const unsigned char *keyid_der = NULL;
  size_t keyid_derlen = 0;
  struct tag_info ti;
  char numbuf[30];
  size_t numbuflen;
  crl_extn_t e;

  if (r_keyid)
    *r_keyid = NULL;
  if (!crl || !r_name || !r_serial)
    return gpg_error (GPG_ERR_INV_VALUE);
  *r_name = NULL;
  *r_serial = NULL;

  for (e=crl->extension_list; e; e = e->next)
    if (!strcmp (e->oid, oidstr_authorityKeyIdentifier))
      break;
  if (!e)
    return gpg_error (GPG_ERR_NO_DATA); /* not available */

  /* Check that there is only one */
  {
    crl_extn_t e2;

    for (e2 = e->next; e2; e2 = e2->next)
      if (!strcmp (e2->oid, oidstr_authorityKeyIdentifier))
        return gpg_error (GPG_ERR_DUP_VALUE);
  }

  der = e->der;
  derlen = e->derlen;

  err = _ksba_ber_parse_tl (&der, &derlen, &ti);
  if (err)
    return err;
  if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE
         && ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  if (ti.ndef)
    return gpg_error (GPG_ERR_NOT_DER_ENCODED);
  if (ti.length > derlen)
    return gpg_error (GPG_ERR_BAD_BER);

  err = _ksba_ber_parse_tl (&der, &derlen, &ti);
  if (err)
    return err;
  if (ti.class != CLASS_CONTEXT)
    return gpg_error (GPG_ERR_INV_CRL_OBJ); /* We expected a tag. */
  if (ti.ndef)
    return gpg_error (GPG_ERR_NOT_DER_ENCODED);
  if (derlen < ti.length)
    return gpg_error (GPG_ERR_BAD_BER);

  if (ti.tag == 0)
    { /* keyIdentifier:  Just save it away for later use. */
      keyid_der = der;
      keyid_derlen = ti.length;

      der += ti.length;
      derlen -= ti.length;
      /* If the keyid has been requested but no other data follows, we
         directly jump to the end. */
      if (r_keyid && !derlen)
        goto build_keyid;
      if (!derlen)
        return gpg_error (GPG_ERR_NO_DATA); /* not available */

      err = _ksba_ber_parse_tl (&der, &derlen, &ti);
      if (err)
        return err;
      if (ti.class != CLASS_CONTEXT)
        return gpg_error (GPG_ERR_INV_CRL_OBJ); /* we expected a tag */
      if (ti.ndef)
        return gpg_error (GPG_ERR_NOT_DER_ENCODED);
      if (derlen < ti.length)
        return gpg_error (GPG_ERR_BAD_BER);
    }

  if (ti.tag != 1 || !derlen)
    return gpg_error (GPG_ERR_INV_CRL_OBJ);

  err = _ksba_name_new_from_der (r_name, der, ti.length);
  if (err)
    return err;

  der += ti.length;
  derlen -= ti.length;

  /* Fixme: we should release r_name before returning on error */
  err = _ksba_ber_parse_tl (&der, &derlen, &ti);
  if (err)
    return err;
  if (ti.class != CLASS_CONTEXT)
    return gpg_error (GPG_ERR_INV_CRL_OBJ); /* we expected a tag */
  if (ti.ndef)
    return gpg_error (GPG_ERR_NOT_DER_ENCODED);
  if (derlen < ti.length)
    return gpg_error (GPG_ERR_BAD_BER);

  if (ti.tag != 2 || !derlen)
    return gpg_error (GPG_ERR_INV_CRL_OBJ);

  sprintf (numbuf,"(%u:", (unsigned int)ti.length);
  numbuflen = strlen (numbuf);
  *r_serial = xtrymalloc (numbuflen + ti.length + 2);
  if (!*r_serial)
    return gpg_error_from_errno (errno);
  strcpy (*r_serial, numbuf);
  memcpy (*r_serial+numbuflen, der, ti.length);
  (*r_serial)[numbuflen + ti.length] = ')';
  (*r_serial)[numbuflen + ti.length + 1] = 0;

 build_keyid:
  if (r_keyid && keyid_der && keyid_derlen)
    {
      sprintf (numbuf,"(%u:", (unsigned int)keyid_derlen);
      numbuflen = strlen (numbuf);
      *r_keyid = xtrymalloc (numbuflen + keyid_derlen + 2);
      if (!*r_keyid)
        return gpg_error (GPG_ERR_ENOMEM);
      strcpy (*r_keyid, numbuf);
      memcpy (*r_keyid+numbuflen, keyid_der, keyid_derlen);
      (*r_keyid)[numbuflen + keyid_derlen] = ')';
      (*r_keyid)[numbuflen + keyid_derlen + 1] = 0;
    }
  return 0;
}


/* Return the optional crlNumber in NUMBER or GPG_ERR_NO_DATA if it is
   not available.  Caller must release NUMBER if the fuction retruned
   with success. */
gpg_error_t
ksba_crl_get_crl_number (ksba_crl_t crl, ksba_sexp_t *number)
{
  gpg_error_t err;
  size_t derlen;
  const unsigned char *der;
  struct tag_info ti;
  char numbuf[30];
  size_t numbuflen;
  crl_extn_t e;

  if (!crl || !number)
    return gpg_error (GPG_ERR_INV_VALUE);
  *number = NULL;

  for (e=crl->extension_list; e; e = e->next)
    if (!strcmp (e->oid, oidstr_crlNumber))
      break;
  if (!e)
    return gpg_error (GPG_ERR_NO_DATA); /* not available */

  /* Check that there is only one. */
  {
    crl_extn_t e2;

    for (e2 = e->next; e2; e2 = e2->next)
      if (!strcmp (e2->oid, oidstr_crlNumber))
        return gpg_error (GPG_ERR_DUP_VALUE);
  }

  der = e->der;
  derlen = e->derlen;

  err = parse_integer (&der, &derlen, &ti);
  if (err)
    return err;

  sprintf (numbuf,"(%u:", (unsigned int)ti.length);
  numbuflen = strlen (numbuf);
  *number = xtrymalloc (numbuflen + ti.length + 2);
  if (!*number)
    return gpg_error_from_errno (errno);
  strcpy (*number, numbuf);
  memcpy (*number+numbuflen, der, ti.length);
  (*number)[numbuflen + ti.length] = ')';
  (*number)[numbuflen + ti.length + 1] = 0;

  return 0;
}




/**
 * ksba_crl_get_update_times:
 * @crl: CRL object
 * @this: Returns the thisUpdate value
 * @next: Returns the nextUpdate value.
 *
 * THIS and NEXT may be given as NULL if the value is not required.
 * Return value: 0 on success or an error code
 **/
gpg_error_t
ksba_crl_get_update_times (ksba_crl_t crl,
                           ksba_isotime_t this,
                           ksba_isotime_t next)
{
  if (this)
    *this = 0;
  if (next)
    *next = 0;
  if (!crl)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (!*crl->this_update)
    return gpg_error (GPG_ERR_INV_TIME);
  if (this)
    _ksba_copy_time (this, crl->this_update);
  if (next)
    _ksba_copy_time (next, crl->next_update);
  return 0;
}

/**
 * ksba_crl_get_item:
 * @crl: CRL object
 * @r_serial: Returns a S-exp with the serial number; caller must free.
 * @r_revocation_date: Returns the recocation date
 * @r_reason: Return the reason for revocation
 *
 * Return the serial number, revocation time and reason of the current
 * item.  Any of these arguments may be passed as %NULL if the value
 * is not of interest.  This function should be used after the parse
 * function came back with %KSBA_SR_GOT_ITEM.  For efficiency reasons
 * the function should be called only once, the implementation may
 * return an error for the second call.
 *
 * Return value: 0 in success or an error code.
 **/
gpg_error_t
ksba_crl_get_item (ksba_crl_t crl, ksba_sexp_t *r_serial,
                   ksba_isotime_t r_revocation_date,
                   ksba_crl_reason_t *r_reason)
{
  if (r_revocation_date)
    *r_revocation_date = 0;

  if (!crl)
    return gpg_error (GPG_ERR_INV_VALUE);

  if (r_serial)
    {
      if (!crl->item.serial)
        return gpg_error (GPG_ERR_NO_DATA);
      *r_serial = crl->item.serial;
      crl->item.serial = NULL;
    }
   if (r_revocation_date)
    _ksba_copy_time (r_revocation_date, crl->item.revocation_date);
  if (r_reason)
    *r_reason = crl->item.reason;
  return 0;
}



/**
 * ksba_crl_get_sig_val:
 * @crl: CRL object
 *
 * Return the actual signature in a format suitable to be used as
 * input to Libgcrypt's verification function.  The caller must free
 * the returned string.  For a rsaPSS signed CRLs this function may
 * also be called right after rsaPSS has been detected using
 * ksba_crl_get_digest_algo and before the the signature value can be
 * retrieved.  In this case an S-expression of the form
 *
 *   (sig-val (hash-algo OID)(salt-length N))
 *
 * is returned.  The caller should extract the actual to be used hash
 * algorithm from that S-expression.  Note that after the actual
 * signature as been seen, a similar S-expression is returned but in
 * this case also with the (rsa(s XXX)) list.
 *
 * Return value: NULL or a string with an S-Exp.
 **/
ksba_sexp_t
ksba_crl_get_sig_val (ksba_crl_t crl)
{
  ksba_sexp_t p;

  if (!crl)
    return NULL;

  if (!crl->sigval
      && crl->algo.oid && !strcmp (crl->algo.oid, "1.2.840.113549.1.1.10")
      && crl->algo.parm && crl->algo.parmlen)
    {
      char *pss_hash;
      unsigned int salt_length;
      struct stringbuf sb;

      if (_ksba_keyinfo_get_pss_info (crl->algo.parm, crl->algo.parmlen,
                                      &pss_hash, &salt_length))
        return NULL;

      init_stringbuf (&sb, 100);
      put_stringbuf (&sb,"(7:sig-val(5:flags3:pss)(9:hash-algo");
      put_stringbuf_sexp (&sb, pss_hash);
      put_stringbuf (&sb, ")(11:salt-length");
      put_stringbuf_uint (&sb, salt_length);
      put_stringbuf (&sb, "))");

      return get_stringbuf (&sb);
    }

  if (!crl->sigval)
    return NULL;

  p = crl->sigval;
  crl->sigval = NULL;
  return p;
}



/*
  Parser functions
*/

/* read one byte */
static int
read_byte (ksba_reader_t reader)
{
  unsigned char buf;
  size_t nread;
  int rc;

  do
    rc = ksba_reader_read (reader, &buf, 1, &nread);
  while (!rc && !nread);
  return rc? -1: buf;
}

/* read COUNT bytes into buffer.  Return 0 on success */
static int
read_buffer (ksba_reader_t reader, char *buffer, size_t count)
{
  size_t nread;

  while (count)
    {
      if (ksba_reader_read (reader, buffer, count, &nread))
        return -1;
      buffer += nread;
      count -= nread;
    }
  return 0;
}

/* Create a new decoder and run it for the given element */
/* Fixme: this code is duplicated from cms-parser.c */
static gpg_error_t
create_and_run_decoder (ksba_reader_t reader, const char *elem_name,
                        AsnNode *r_root,
                        unsigned char **r_image, size_t *r_imagelen)
{
  gpg_error_t err;
  ksba_asn_tree_t crl_tree;
  BerDecoder decoder;

  err = ksba_asn_create_tree ("tmttv2", &crl_tree);
  if (err)
    return err;

  decoder = _ksba_ber_decoder_new ();
  if (!decoder)
    {
      ksba_asn_tree_release (crl_tree);
      return gpg_error (GPG_ERR_ENOMEM);
    }

  err = _ksba_ber_decoder_set_reader (decoder, reader);
  if (err)
    {
      ksba_asn_tree_release (crl_tree);
      _ksba_ber_decoder_release (decoder);
      return err;
    }

  err = _ksba_ber_decoder_set_module (decoder, crl_tree);
  if (err)
    {
      ksba_asn_tree_release (crl_tree);
      _ksba_ber_decoder_release (decoder);
      return err;
    }

  err = _ksba_ber_decoder_decode (decoder, elem_name, 0,
                                  r_root, r_image, r_imagelen);

  _ksba_ber_decoder_release (decoder);
  ksba_asn_tree_release (crl_tree);
  return err;
}


/* Parse the extension in the buffer DER or length DERLEN and return
   the result in OID, CRITICAL, OFF and LEN. */
static gpg_error_t
parse_one_extension (const unsigned char *der, size_t derlen,
                     char **oid, int *critical, size_t *off, size_t *len)
{
  gpg_error_t err;
  struct tag_info ti;
  const unsigned char *start = der;

  *oid = NULL;
  *critical = 0;
  *off = *len = 0;

  /*
     Extension  ::=  SEQUENCE {
         extnID      OBJECT IDENTIFIER,
         critical    BOOLEAN DEFAULT FALSE,
         extnValue   OCTET STRING }
  */
  err = parse_sequence (&der, &derlen, &ti);
  if (err)
    goto failure;

  err = parse_object_id_into_str (&der, &derlen, oid);
  if (err)
    goto failure;

  err = _ksba_ber_parse_tl (&der, &derlen, &ti);
  if (err)
    goto failure;
  if (ti.length > derlen)
    return gpg_error (GPG_ERR_BAD_BER);
  if (ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_BOOLEAN
           && !ti.is_constructed)
    {
      if (ti.length != 1)
        goto bad_ber;
      *critical = !!*der;
      parse_skip (&der, &derlen, &ti);
    }
  else
    { /* Undo that read. */
      der -= ti.nhdr;
      derlen += ti.nhdr;
    }

  err = parse_octet_string (&der, &derlen, &ti);
  if (err)
    goto failure;
  *off = der - start;
  *len = ti.length;

  return 0;

 bad_ber:
  err = gpg_error (GPG_ERR_BAD_BER);
 failure:
  xfree (*oid);
  *oid = NULL;
  return err;
}


/* Store an extension into the context. */
static gpg_error_t
store_one_extension (ksba_crl_t crl, const unsigned char *der, size_t derlen)
{
  gpg_error_t err;
  char *oid;
  int critical;
  size_t off, len;
  crl_extn_t e;

  err = parse_one_extension (der, derlen, &oid, &critical, &off, &len);
  if (err)
    return err;
  e = xtrymalloc (sizeof *e + len - 1);
  if (!e)
    {
      err = gpg_error_from_errno (errno);
      xfree (oid);
      return err;
    }
  e->oid = oid;
  e->critical = critical;
  e->derlen = len;
  memcpy (e->der, der + off, len);
  e->next = crl->extension_list;
  crl->extension_list = e;

  return 0;
}



/* Parse the fixed block at the beginning.  We use a custom parser
   here because our BER decoder is not yet able to stop at certain
   points */
static gpg_error_t
parse_to_next_update (ksba_crl_t crl)
{
  gpg_error_t err;
  struct tag_info ti;
  unsigned long outer_len, tbs_len;
  int outer_ndef, tbs_ndef;
  int c;
  unsigned char tmpbuf[500]; /* for OID or algorithmIdentifier */
  size_t nread;

  /* read the outer sequence */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE
         && ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  outer_len = ti.length;
  outer_ndef = ti.ndef;
  if (!outer_ndef && outer_len < 10)
    return gpg_error (GPG_ERR_TOO_SHORT);

  /* read the tbs sequence */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE
         && ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  HASH (ti.buf, ti.nhdr);
  if (!outer_ndef)
    {
      if (outer_len < ti.nhdr)
        return gpg_error (GPG_ERR_BAD_BER); /* Triplet header larger
                                               than outer sequence */
      outer_len -= ti.nhdr;
      if (!ti.ndef && outer_len < ti.length)
        return gpg_error (GPG_ERR_BAD_BER); /* Triplet larger than
                                               outer sequence */
      outer_len -= ti.length;
    }
  tbs_len = ti.length;
  tbs_ndef = ti.ndef;
  if (!tbs_ndef && tbs_len < 10)
    return gpg_error (GPG_ERR_TOO_SHORT);

  /* read the optional version integer */
  crl->crl_version = -1;
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_INTEGER)
    {
      if ( ti.is_constructed || !ti.length )
        return gpg_error (GPG_ERR_INV_CRL_OBJ);
      HASH (ti.buf, ti.nhdr);
      if (!tbs_ndef)
        {
          if (tbs_len < ti.nhdr)
            return gpg_error (GPG_ERR_BAD_BER);
          tbs_len -= ti.nhdr;
          if (tbs_len < ti.length)
            return gpg_error (GPG_ERR_BAD_BER);
          tbs_len -= ti.length;
        }
      /* fixme: we should also check the outer data length here and in
         the follwing code.  It might however be easier to to thsi at
         the end of this sequence */
      if (ti.length != 1)
        return gpg_error (GPG_ERR_UNSUPPORTED_CRL_VERSION);
      if ( (c=read_byte (crl->reader)) == -1)
        {
          err = ksba_reader_error (crl->reader);
          return err? err : gpg_error (GPG_ERR_GENERAL);
        }
      if ( !(c == 0 || c == 1) )
        return gpg_error (GPG_ERR_UNSUPPORTED_CRL_VERSION);
      {
        unsigned char tmp = c;
        HASH (&tmp, 1);
      }
      crl->crl_version = c;
      err = _ksba_ber_read_tl (crl->reader, &ti);
      if (err)
        return err;
    }

  /* read the algorithm identifier */
  if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE
         && ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  if (!tbs_ndef)
    {
      if (tbs_len < ti.nhdr)
        return gpg_error (GPG_ERR_BAD_BER);
      tbs_len -= ti.nhdr;
      if (!ti.ndef && tbs_len < ti.length)
        return gpg_error (GPG_ERR_BAD_BER);
      tbs_len -= ti.length;
    }
  if (ti.nhdr + ti.length >= DIM(tmpbuf))
    return gpg_error (GPG_ERR_TOO_LARGE);
  memcpy (tmpbuf, ti.buf, ti.nhdr);
  err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length);
  if (err)
    return err;
  HASH (tmpbuf, ti.nhdr+ti.length);

  xfree (crl->algo.oid); crl->algo.oid = NULL;
  xfree (crl->algo.parm); crl->algo.parm = NULL;
  err = _ksba_parse_algorithm_identifier2 (tmpbuf, ti.nhdr+ti.length, &nread,
                                           &crl->algo.oid,
                                           &crl->algo.parm,
                                           &crl->algo.parmlen);
  if (err)
    return err;
  assert (nread <= ti.nhdr + ti.length);
  if (nread < ti.nhdr + ti.length)
    return gpg_error (GPG_ERR_TOO_SHORT);


  /* read the name */
  {
    unsigned long n = ksba_reader_tell (crl->reader);
    err = create_and_run_decoder (crl->reader,
                                  "TMTTv2.CertificateList.tbsCertList.issuer",
                                  &crl->issuer.root,
                                  &crl->issuer.image,
                                  &crl->issuer.imagelen);
    if (err)
      return err;
    /* imagelen might be larger than the valid data (due to read ahead).
       So we need to get the count from the reader */
    n = ksba_reader_tell (crl->reader) - n;
    if (n > crl->issuer.imagelen)
      return gpg_error (GPG_ERR_BUG);
    HASH (crl->issuer.image, n);

    if (!tbs_ndef)
      {
        if (tbs_len < n)
          return gpg_error (GPG_ERR_BAD_BER);
        tbs_len -= n;
      }
  }



  /* read the thisUpdate time */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( !(ti.class == CLASS_UNIVERSAL
         && (ti.tag == TYPE_UTC_TIME || ti.tag == TYPE_GENERALIZED_TIME)
         && !ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  if (!tbs_ndef)
    {
      if (tbs_len < ti.nhdr)
        return gpg_error (GPG_ERR_BAD_BER);
      tbs_len -= ti.nhdr;
      if (!ti.ndef && tbs_len < ti.length)
        return gpg_error (GPG_ERR_BAD_BER);
      tbs_len -= ti.length;
    }
  if (ti.nhdr + ti.length >= DIM(tmpbuf))
    return gpg_error (GPG_ERR_TOO_LARGE);
  memcpy (tmpbuf, ti.buf, ti.nhdr);
  err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length);
  if (err)
    return err;
  HASH (tmpbuf, ti.nhdr+ti.length);
  _ksba_asntime_to_iso (tmpbuf+ti.nhdr, ti.length,
                        ti.tag == TYPE_UTC_TIME, crl->this_update);

  /* Read the optional nextUpdate time. */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( ti.class == CLASS_UNIVERSAL
       && (ti.tag == TYPE_UTC_TIME || ti.tag == TYPE_GENERALIZED_TIME)
         && !ti.is_constructed )
    {
      if (!tbs_ndef)
        {
          if (tbs_len < ti.nhdr)
            return gpg_error (GPG_ERR_BAD_BER);
          tbs_len -= ti.nhdr;
          if (!ti.ndef && tbs_len < ti.length)
            return gpg_error (GPG_ERR_BAD_BER);
          tbs_len -= ti.length;
        }
      if (ti.nhdr + ti.length >= DIM(tmpbuf))
        return gpg_error (GPG_ERR_TOO_LARGE);
      memcpy (tmpbuf, ti.buf, ti.nhdr);
      err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length);
      if (err)
        return err;
      HASH (tmpbuf, ti.nhdr+ti.length);
      _ksba_asntime_to_iso (tmpbuf+ti.nhdr, ti.length,
                            ti.tag == TYPE_UTC_TIME, crl->next_update);
      err = _ksba_ber_read_tl (crl->reader, &ti);
      if (err)
        return err;
    }

  /* Read the first sequence tag of the optional SEQ of SEQ. */
  if (tbs_ndef || tbs_len)
    {
      if (ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE
          && ti.is_constructed )
        { /* yes, there is one */
          HASH (ti.buf, ti.nhdr);
          if (!tbs_ndef)
            {
              if (tbs_len < ti.nhdr)
            return gpg_error (GPG_ERR_BAD_BER);
              tbs_len -= ti.nhdr;
              if (!ti.ndef && tbs_len < ti.length)
                return gpg_error (GPG_ERR_BAD_BER);
              tbs_len -= ti.length;
            }
          crl->state.have_seqseq = 1;
          crl->state.seqseq_ndef = ti.ndef;
          crl->state.seqseq_len  = ti.length;
          /* and read the next */
          err = _ksba_ber_read_tl (crl->reader, &ti);
          if (err)
            return err;
        }
    }

  /* We need to save some stuff for the next round. */
  crl->state.ti = ti;
  crl->state.outer_ndef = outer_ndef;
  crl->state.outer_len = outer_len;
  crl->state.tbs_ndef = tbs_ndef;
  crl->state.tbs_len = tbs_len;

  return 0;
}



/* Store an entry extension into the current item. */
static gpg_error_t
store_one_entry_extension (ksba_crl_t crl,
                           const unsigned char *der, size_t derlen)
{
  gpg_error_t err;
  char *oid;
  int critical;
  size_t off, len;

  err = parse_one_extension (der, derlen, &oid, &critical, &off, &len);
  if (err)
    return err;
  if (!strcmp (oid, oidstr_crlReason))
    {
      struct tag_info ti;
      const unsigned char *buf = der+off;
      size_t mylen = len;

      err = parse_enumerated (&buf, &mylen, &ti, 1);
      if (err)
        return err;
      /* Note that we OR the values so that in case this extension is
         repeated we can track all reason codes. */
      switch (*buf)
        {
        case  0: crl->item.reason |= KSBA_CRLREASON_UNSPECIFIED; break;
        case  1: crl->item.reason |= KSBA_CRLREASON_KEY_COMPROMISE; break;
        case  2: crl->item.reason |= KSBA_CRLREASON_CA_COMPROMISE; break;
        case  3: crl->item.reason |= KSBA_CRLREASON_AFFILIATION_CHANGED; break;
        case  4: crl->item.reason |= KSBA_CRLREASON_SUPERSEDED; break;
        case  5: crl->item.reason |= KSBA_CRLREASON_CESSATION_OF_OPERATION;
          break;
        case  6: crl->item.reason |= KSBA_CRLREASON_CERTIFICATE_HOLD; break;
        case  8: crl->item.reason |= KSBA_CRLREASON_REMOVE_FROM_CRL; break;
        case  9: crl->item.reason |= KSBA_CRLREASON_PRIVILEGE_WITHDRAWN; break;
        case 10: crl->item.reason |= KSBA_CRLREASON_AA_COMPROMISE; break;
        default: crl->item.reason |= KSBA_CRLREASON_OTHER; break;
        }
    }
  if (!strcmp (oid, oidstr_certificateIssuer))
    {
      /* FIXME: We need to implement this. */
    }
  else if (critical)
    err = gpg_error (GPG_ERR_UNKNOWN_CRIT_EXTN);

  xfree (oid);

  return err;
}

/* Parse the revokedCertificates SEQUENCE of SEQUENCE using a custom
   parser for efficiency and return after each entry */
static gpg_error_t
parse_crl_entry (ksba_crl_t crl, int *got_entry)
{
  gpg_error_t err;
  struct tag_info ti = crl->state.ti;
  unsigned long seqseq_len= crl->state.seqseq_len;
  int seqseq_ndef         = crl->state.seqseq_ndef;
  unsigned long len;
  int ndef;
  unsigned char tmpbuf[4096]; /* for time, serial number and extensions */
  char numbuf[22];
  int numbuflen;

  /* Check the length to see whether we are at the end of the seq but do
     this only when we know that we have this optional seq of seq. */
  if (!crl->state.have_seqseq)
    return 0; /* ready (no entries at all) */

  if (!seqseq_ndef && !seqseq_len)
    return 0; /* ready */

  /* if this is not a SEQUENCE the CRL is invalid */
  if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE
         && ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  HASH (ti.buf, ti.nhdr);
  if (!seqseq_ndef)
    {
      if (seqseq_len < ti.nhdr)
        return gpg_error (GPG_ERR_BAD_BER);
      seqseq_len -= ti.nhdr;
      if (!ti.ndef && seqseq_len < ti.length)
        return gpg_error (GPG_ERR_BAD_BER);
      seqseq_len -= ti.length;
    }
  ndef = ti.ndef;
  len  = ti.length;

  /* get the serial number */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_INTEGER
         && !ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  if (!ndef)
    {
      if (len < ti.nhdr)
        return gpg_error (GPG_ERR_BAD_BER);
      len -= ti.nhdr;
      if (!ti.ndef && len < ti.length)
        return gpg_error (GPG_ERR_BAD_BER);
      len -= ti.length;
    }
  if (ti.nhdr + ti.length >= DIM(tmpbuf))
    return gpg_error (GPG_ERR_TOO_LARGE);
  memcpy (tmpbuf, ti.buf, ti.nhdr);
  err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length);
  if (err)
    return err;
  HASH (tmpbuf, ti.nhdr+ti.length);

  xfree (crl->item.serial);
  sprintf (numbuf,"(%u:", (unsigned int)ti.length);
  numbuflen = strlen (numbuf);
  crl->item.serial = xtrymalloc (numbuflen + ti.length + 2);
  if (!crl->item.serial)
    return gpg_error (GPG_ERR_ENOMEM);
  strcpy (crl->item.serial, numbuf);
  memcpy (crl->item.serial+numbuflen, tmpbuf+ti.nhdr, ti.length);
  crl->item.serial[numbuflen + ti.length] = ')';
  crl->item.serial[numbuflen + ti.length + 1] = 0;
  crl->item.reason = 0;

  /* get the revocation time */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( !(ti.class == CLASS_UNIVERSAL
         && (ti.tag == TYPE_UTC_TIME || ti.tag == TYPE_GENERALIZED_TIME)
         && !ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  if (!ndef)
    {
      if (len < ti.nhdr)
        return gpg_error (GPG_ERR_BAD_BER);
      len -= ti.nhdr;
      if (!ti.ndef && len < ti.length)
        return gpg_error (GPG_ERR_BAD_BER);
      len -= ti.length;
    }
  if (ti.nhdr + ti.length >= DIM(tmpbuf))
    return gpg_error (GPG_ERR_TOO_LARGE);
  memcpy (tmpbuf, ti.buf, ti.nhdr);
  err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length);
  if (err)
    return err;
  HASH (tmpbuf, ti.nhdr+ti.length);

  _ksba_asntime_to_iso (tmpbuf+ti.nhdr, ti.length,
                        ti.tag == TYPE_UTC_TIME, crl->item.revocation_date);

  /* if there is still space we must parse the optional entryExtensions */
  if (ndef)
    return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
  else if (len)
    {
      /* read the outer sequence */
      err = _ksba_ber_read_tl (crl->reader, &ti);
      if (err)
        return err;
      if ( !(ti.class == CLASS_UNIVERSAL
             && ti.tag == TYPE_SEQUENCE && ti.is_constructed) )
        return gpg_error (GPG_ERR_INV_CRL_OBJ);
      if (ti.ndef)
        return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
      HASH (ti.buf, ti.nhdr);
      if (len < ti.nhdr)
        return gpg_error (GPG_ERR_BAD_BER);
      len -= ti.nhdr;
      if (len < ti.length)
        return gpg_error (GPG_ERR_BAD_BER);

      /* now loop over the extensions */
      while (len)
        {
          err = _ksba_ber_read_tl (crl->reader, &ti);
          if (err)
            return err;
          if ( !(ti.class == CLASS_UNIVERSAL
                 && ti.tag == TYPE_SEQUENCE && ti.is_constructed) )
            return gpg_error (GPG_ERR_INV_CRL_OBJ);
          if (ti.ndef)
            return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
          if (len < ti.nhdr)
            return gpg_error (GPG_ERR_BAD_BER);
          len -= ti.nhdr;
          if (len < ti.length)
            return gpg_error (GPG_ERR_BAD_BER);
          len -= ti.length;
          if (ti.nhdr + ti.length >= DIM(tmpbuf))
            return gpg_error (GPG_ERR_TOO_LARGE);
          memcpy (tmpbuf, ti.buf, ti.nhdr);
          err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length);
          if (err)
            return err;
          HASH (tmpbuf, ti.nhdr+ti.length);
          err = store_one_entry_extension (crl, tmpbuf, ti.nhdr+ti.length);
          if (err)
            return err;
        }
    }

  /* read ahead */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;

  *got_entry = 1;

  /* Fixme: the seqseq length is not correct if any element was ndef'd */
  crl->state.ti = ti;
  crl->state.seqseq_ndef = seqseq_ndef;
  crl->state.seqseq_len  = seqseq_len;

  return 0;
}


/* This function is used when a [0] tag was encountered to read the
   crlExtensions */
static gpg_error_t
parse_crl_extensions (ksba_crl_t crl)
{
  gpg_error_t err;
  struct tag_info ti = crl->state.ti;
  unsigned long ext_len, len;
  unsigned char tmpbuf[4096]; /* for extensions */

  /* if we do not have a tag [0] we are done with this */
  if (!(ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed))
    return 0;
  if (ti.ndef)
    return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
  HASH (ti.buf, ti.nhdr);
  ext_len = ti.length;

  /* read the outer sequence */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( !(ti.class == CLASS_UNIVERSAL
         && ti.tag == TYPE_SEQUENCE && ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  if (ti.ndef)
    return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
  HASH (ti.buf, ti.nhdr);
  if (ext_len < ti.nhdr)
    return gpg_error (GPG_ERR_BAD_BER);
  ext_len -= ti.nhdr;
  if (ext_len < ti.length)
    return gpg_error (GPG_ERR_BAD_BER);
  len = ti.length;

  /* now loop over the extensions */
  while (len)
    {
      err = _ksba_ber_read_tl (crl->reader, &ti);
      if (err)
        return err;
      if ( !(ti.class == CLASS_UNIVERSAL
             && ti.tag == TYPE_SEQUENCE && ti.is_constructed) )
        return gpg_error (GPG_ERR_INV_CRL_OBJ);
      if (ti.ndef)
        return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
      if (len < ti.nhdr)
        return gpg_error (GPG_ERR_BAD_BER);
      len -= ti.nhdr;
      if (len < ti.length)
        return gpg_error (GPG_ERR_BAD_BER);
      len -= ti.length;
      if (ti.nhdr + ti.length >= DIM(tmpbuf))
        return gpg_error (GPG_ERR_TOO_LARGE);
      /* fixme use a larger buffer if the extension does not fit into tmpbuf */
      memcpy (tmpbuf, ti.buf, ti.nhdr);
      err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length);
      if (err)
        return err;
      HASH (tmpbuf, ti.nhdr+ti.length);
      err = store_one_extension (crl, tmpbuf, ti.nhdr+ti.length);
      if (err)
        return err;
    }

  /* read ahead */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;

  crl->state.ti = ti;
  return 0;
}

/* Parse the signatureAlgorithm and the signature */
static gpg_error_t
parse_signature (ksba_crl_t crl)
{
  gpg_error_t err;
  struct tag_info ti = crl->state.ti;
  unsigned char tmpbuf[2048]; /* for the sig algo and bitstr */
  size_t n, n2;

  /* We do read the stuff into a temporary buffer so that we can apply
     our parsing function for this structure */

  /* read the algorithmIdentifier sequence */
  if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE
         && ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  if (ti.ndef)
    return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
  n = ti.nhdr + ti.length;
  if (n >= DIM(tmpbuf))
    return gpg_error (GPG_ERR_TOO_LARGE);
  memcpy (tmpbuf, ti.buf, ti.nhdr);
  err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length);
  if (err)
    return err;

  /* and append the bit string */
  err = _ksba_ber_read_tl (crl->reader, &ti);
  if (err)
    return err;
  if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_BIT_STRING
         && !ti.is_constructed) )
    return gpg_error (GPG_ERR_INV_CRL_OBJ);
  n2 = ti.nhdr + ti.length;
  if (n + n2 >= DIM(tmpbuf) || (n + n2) < n)
    return gpg_error (GPG_ERR_TOO_LARGE);
  memcpy (tmpbuf+n, ti.buf, ti.nhdr);
  err = read_buffer (crl->reader, tmpbuf+n+ti.nhdr, ti.length);
  if (err)
    return err;

  /* now parse it */
  xfree (crl->sigval); crl->sigval = NULL;
  return _ksba_sigval_to_sexp (tmpbuf, n + n2, &crl->sigval);
}


/* The actual parser which should be used with a new CRL object and
   run in a loop until the the KSBA_SR_READY is encountered */
gpg_error_t
ksba_crl_parse (ksba_crl_t crl, ksba_stop_reason_t *r_stopreason)
{
  enum {
    sSTART,
    sCRLENTRY,
    sCRLEXT,
    sERROR
  } state = sERROR;
  ksba_stop_reason_t stop_reason;
  gpg_error_t err = 0;
  int got_entry = 0;

  if (!crl || !r_stopreason)
    return gpg_error (GPG_ERR_INV_VALUE);

  if (!crl->any_parse_done)
    { /* first time initialization of the stop reason */
      *r_stopreason = 0;
      crl->any_parse_done = 1;
    }

  /* Calculate state from last reason */
  stop_reason = *r_stopreason;
  *r_stopreason = KSBA_SR_RUNNING;
  switch (stop_reason)
    {
    case 0:
      state = sSTART;
      break;
    case KSBA_SR_BEGIN_ITEMS:
    case KSBA_SR_GOT_ITEM:
      state = sCRLENTRY;
      break;
    case KSBA_SR_END_ITEMS:
      state = sCRLEXT;
      break;
    case KSBA_SR_RUNNING:
      err = gpg_error (GPG_ERR_INV_STATE);
      break;
    default:
      err = gpg_error (GPG_ERR_BUG);
      break;
    }
  if (err)
    return err;

  /* Do the action */
  switch (state)
    {
    case sSTART:
      err = parse_to_next_update (crl);
      break;
    case sCRLENTRY:
      err = parse_crl_entry (crl, &got_entry);
      break;
    case sCRLEXT:
      err = parse_crl_extensions (crl);
      if (!err)
        {
          if (crl->hash_fnc && crl->hashbuf.used)
            crl->hash_fnc (crl->hash_fnc_arg,
                           crl->hashbuf.buffer, crl->hashbuf.used);
          crl->hashbuf.used = 0;
          err = parse_signature (crl);
        }
      break;
    default:
      err = gpg_error (GPG_ERR_INV_STATE);
      break;
    }
  if (err)
    return err;

  /* Calculate new stop reason */
  switch (state)
    {
    case sSTART:
      stop_reason = KSBA_SR_BEGIN_ITEMS;
      break;
    case sCRLENTRY:
      stop_reason = got_entry? KSBA_SR_GOT_ITEM : KSBA_SR_END_ITEMS;
      break;
    case sCRLEXT:
      stop_reason = KSBA_SR_READY;
      break;
    default:
      break;
    }

  *r_stopreason = stop_reason;
  return 0;
}
