/* certreq.c - create pkcs-10 messages
 *      Copyright (C) 2002, 2011, 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 "cms.h"
#include "convert.h"
#include "keyinfo.h"
#include "der-encoder.h"
#include "ber-help.h"
#include "sexp-parse.h"
#include "certreq.h"

static const char oidstr_subjectAltName[] = "2.5.29.17";
static const char oidstr_extensionReq[] = "1.2.840.113549.1.9.14";

#if 0 /* Set to 1 to use this debug helper.  */
static void
log_sexp (const char *text, ksba_const_sexp_t p)
{
  int level = 0;

  gpgrt_log_debug ("%s: ", text);
  if (!p)
    gpgrt_log_printf ("[none]");
  else
    {
      for (;;)
        {
          if (*p == '(')
            {
              gpgrt_log_printf ("%c", *p);
              p++;
              level++;
            }
          else if (*p == ')')
            {
              gpgrt_log_printf ("%c", *p);
              p++;
              if (--level <= 0 )
                return;
            }
          else if (!digitp (p))
            {
              gpgrt_log_printf ("[invalid s-exp]");
              return;
            }
          else
            {
              char *endp;
              const unsigned char *s;
              unsigned long len, n;

              len = strtoul (p, &endp, 10);
              p = endp;
              if (*p != ':')
                {
                  gpgrt_log_printf ("[invalid s-exp]");
                  return;
                }
              p++;
              for (s=p,n=0; n < len; n++, s++)
                if ( !((*s >= 'a' && *s <= 'z')
                       || (*s >= 'A' && *s <= 'Z')
                       || (*s >= '0' && *s <= '9')
                       || *s == '-' || *s == '.'))
                  break;
              if (n < len)
                {
                  gpgrt_log_printf ("#");
                  for (n=0; n < len; n++, p++)
                    gpgrt_log_printf ("%02X", *p);
                  gpgrt_log_printf ("#");
                }
              else
                {
                  for (n=0; n < len; n++, p++)
                    gpgrt_log_printf ("%c", *p);
                }
            }
        }
    }
  gpgrt_log_printf ("\n");
}
#endif /* debug helper */


/**
 * ksba_cms_new:
 *
 * Create a new and empty CMS object
 *
 * Return value: A CMS object or an error code.
 **/
gpg_error_t
ksba_certreq_new (ksba_certreq_t *r_cr)
{
  *r_cr = xtrycalloc (1, sizeof **r_cr);
  if (!*r_cr)
    return gpg_error_from_errno (errno);

  return 0;
}

/**
 * ksba_certreq_release:
 * @cms: A Certreq object
 *
 * Release a Certreq object.
 **/
void
ksba_certreq_release (ksba_certreq_t cr)
{
  if (!cr)
    return;
  xfree (cr->x509.serial.der);
  xfree (cr->x509.issuer.der);
  xfree (cr->x509.siginfo.der);
  xfree (cr->subject.der);
  xfree (cr->key.der);
  xfree (cr->cri.der);
  xfree (cr->sig_val.algo);
  xfree (cr->sig_val.value);
  while (cr->subject_alt_names)
    {
      struct general_names_s *tmp = cr->subject_alt_names->next;
      xfree (cr->subject_alt_names);
      cr->subject_alt_names = tmp;
    }
  while (cr->extn_list)
    {
      struct extn_list_s *e = cr->extn_list->next;
      xfree (cr->extn_list);
      cr->extn_list = e;
    }

  xfree (cr);
}


gpg_error_t
ksba_certreq_set_writer (ksba_certreq_t cr, ksba_writer_t w)
{
  if (!cr || !w)
    return gpg_error (GPG_ERR_INV_VALUE);
  cr->writer = w;
  return 0;
}


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



/* Store the serial number.  If this function is used, a real X.509
   certificate will be built instead of a pkcs#10 certificate signing
   request.  SN must be a simple canonical encoded s-expression with
   the serial number as its only item.  Note that this function allows
   to set a negative serial number, which is not forbidden but
   probably not a good idea.  */
gpg_error_t
ksba_certreq_set_serial (ksba_certreq_t cr, ksba_const_sexp_t sn)
{
  const char *p = (const char *)sn;
  unsigned long n;
  char *endp;

  if (!cr || !sn || !p || *p != '(')
    return gpg_error (GPG_ERR_INV_VALUE);

  p++;
  n = strtoul (p, &endp, 10);
  p = endp;
  if (*p++ != ':' || !n)
    return gpg_error (GPG_ERR_INV_VALUE);

  /* Remove invalid leading zero bytes.  */
  for (; n > 1 && !*p && !(p[1] & 0x80); n--, p++)
    ;

  if (cr->x509.serial.der)
    return gpg_error (GPG_ERR_CONFLICT); /* Already set */
  cr->x509.serial.der = xtrymalloc (n);
  if (!cr->x509.serial.der)
    return gpg_error_from_syserror ();
  memcpy (cr->x509.serial.der, p, n);
  cr->x509.serial.derlen = n;

  return 0;
}


/* Store the issuer's name.  NAME must be a valid RFC-2253 encoded DN
   name.  Only used for building an X.509 certificate.  */
gpg_error_t
ksba_certreq_set_issuer (ksba_certreq_t cr, const char *name)
{
  if (!cr || !name)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (cr->x509.issuer.der)
    return gpg_error (GPG_ERR_CONFLICT); /* Already set */
  return _ksba_dn_from_str (name, &cr->x509.issuer.der,
                            &cr->x509.issuer.derlen);
}

/* Store validity information.  The time is in TIMEBUF.  A value of 0
   for WHAT stores the notBefore time, a value of 1 stores the
   notAfter time.  Only used for building an X.509 certificate.  */
gpg_error_t
ksba_certreq_set_validity (ksba_certreq_t cr, int what,
                           const ksba_isotime_t timebuf)
{
  if (!cr || what < 0 || what > 1
      || !timebuf || _ksba_assert_time_format (timebuf))
    return gpg_error (GPG_ERR_INV_VALUE);

  _ksba_copy_time (what?cr->x509.not_after:cr->x509.not_before, timebuf);
  return 0;
}


/* Store the signing key info.  This is used to extract the signing
   algorithm; the signing itself needs to be done by the caller as
   response to a stop code.  The expression SIGINFO is similar to a
   sig-val one, however most parameters are not required.  The
   expected structure of this canonical encoded s-expression is:

     (sig-val
       (<algo>
          (<param_name1> <value>)
          ...
          (<param_namen> <value>)))

*/
gpg_error_t
ksba_certreq_set_siginfo (ksba_certreq_t cr, ksba_const_sexp_t siginfo)
{
  if (!cr || !siginfo)
    return gpg_error (GPG_ERR_INV_VALUE);
  xfree (cr->x509.siginfo.der);
  cr->x509.siginfo.der = NULL;

  return _ksba_keyinfo_from_sexp (siginfo, 1, &cr->x509.siginfo.der,
                                  &cr->x509.siginfo.derlen);
}



/* Store the subject's name.  Does perform some syntactic checks on
   the name.  The first added subject is the real one, all subsequent
   calls add subjectAltNames.

   NAME must be a valid RFC-2253 encoded DN name for the first one or an
   email address enclosed in angle brackets for all further calls.
 */
gpg_error_t
ksba_certreq_add_subject (ksba_certreq_t cr, const char *name)
{
  unsigned long namelen;
  size_t n, n1;
  struct general_names_s *gn;
  unsigned char *der;
  int tag;
  const char *endp;

  if (!cr || !name)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (!cr->subject.der)
    return _ksba_dn_from_str (name, &cr->subject.der, &cr->subject.derlen);
  /* This is assumed to be an subjectAltName. */

  /* Note that the way we pass the name should match what
     ksba_cert_get_subject() returns.  In particular we expect that it
     is a real string and thus a canonical S-expression is
     additionally terminated by a 0. */
  namelen = strlen (name);
  if (*name == '<' && name[namelen-1] == '>'
      && namelen >= 4 && strchr (name, '@'))
    {
      name++;
      namelen -= 2;
      tag = 1;  /* rfc822Name */
    }
  else if (!strncmp (name, "(8:dns-name", 11))
    {
      tag = 2; /* dNSName */
      namelen = strtoul (name+11, (char**)&endp, 10);
      name = endp;
      if (!namelen || *name != ':')
        return gpg_error (GPG_ERR_INV_SEXP);
      name++;
    }
  else if (!strncmp (name, "(3:uri", 6))
    {
      tag = 6; /* uRI */
      namelen = strtoul (name+6, (char**)&endp, 10);
      name = endp;
      if (!namelen || *name != ':')
        return gpg_error (GPG_ERR_INV_SEXP);
      name++;
    }
  else
    return gpg_error (GPG_ERR_INV_VALUE);

  n1  = _ksba_ber_count_tl (tag, CLASS_CONTEXT, 0, namelen);
  n1 += namelen;

  gn = xtrymalloc (sizeof *gn + n1 - 1);
  if (!gn)
    return gpg_error_from_errno (errno);
  gn->tag = tag;
  gn->datalen = n1;
  der = (unsigned char *)gn->data;
  n = _ksba_ber_encode_tl (der, tag, CLASS_CONTEXT, 0, namelen);
  if (!n)
    return gpg_error (GPG_ERR_BUG);
  der += n;
  memcpy (der, name, namelen);
  assert (der + namelen - (unsigned char*)gn->data == n1);

  gn->next = cr->subject_alt_names;
  cr->subject_alt_names = gn;

  return 0;
}


/* Add the GeneralNames object GNAMES to the list of extensions in CR.
   Use OID as object identifier for the extensions. */
static gpg_error_t
add_general_names_to_extn (ksba_certreq_t cr, struct general_names_s *gnames,
                           const char *oid)
{
  struct general_names_s *g;
  size_t n, n1, n2;
  struct extn_list_s *e;
  unsigned char *der;

  /* Calculate the required size. */
  n1 = 0;
  for (g=gnames; g; g = g->next)
    n1 += g->datalen;

  n2  = _ksba_ber_count_tl (TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, n1);
  n2 += n1;

  /* Allocate memory and encode all. */
  e = xtrymalloc (sizeof *e + n2 - 1);
  if (!e)
    return gpg_error_from_errno (errno);
  e->oid = oid;
  e->critical = 0;
  e->derlen = n2;
  der = e->der;
  n = _ksba_ber_encode_tl (der, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, n1);
  if (!n)
    return gpg_error (GPG_ERR_BUG); /* (no need to cleanup after a bug) */
  der += n;

  for (g=gnames; g; g = g->next)
    {
      memcpy (der, g->data, g->datalen);
      der += g->datalen;
    }
  assert (der - e->der == n2);

  e->next = cr->extn_list;
  cr->extn_list = e;
  return 0;
}

/* Store the subject's publickey. */
gpg_error_t
ksba_certreq_set_public_key (ksba_certreq_t cr, ksba_const_sexp_t key)
{
  if (!cr)
    return gpg_error (GPG_ERR_INV_VALUE);

  xfree (cr->key.der);
  cr->key.der = NULL;
  return _ksba_keyinfo_from_sexp (key, 0, &cr->key.der, &cr->key.derlen);
}


/* Generic function to add an extension to a certificate request.  The
   extension must be provided readily encoded in the buffer DER of
   length DERLEN bytes; the OID is to be given in OID and IS_CRIT
   should be set to true if that extension shall be marked
   critical. */
gpg_error_t
ksba_certreq_add_extension (ksba_certreq_t cr,
                            const char *oid, int is_crit,
                            const void *der, size_t derlen)
{
  size_t oidlen;
  struct extn_list_s *e;

  if (!cr || !oid|| !*oid || !der || !derlen)
    return gpg_error (GPG_ERR_INV_VALUE);

  oidlen = strlen (oid);
  e = xtrymalloc (sizeof *e + derlen + oidlen);
  if (!e)
    return gpg_error_from_errno (errno);
  e->critical = is_crit;
  e->derlen = derlen;
  memcpy (e->der, der, derlen);
  strcpy (e->der+derlen, oid);
  e->oid = e->der + derlen;

  e->next = cr->extn_list;
  cr->extn_list = e;

  return 0;
}




/*
 * r_sig  = (sig-val
 *	      (<algo>
 *		(<param_name1> <mpi>)
 *		...
 *		(<param_namen> <mpi>)
 *	      ))
 * The sexp must be in canonical form.
 * Fixme:  The code is mostly duplicated from cms.c
 * Note, that <algo> must be given as a stringified OID or the special
 * string "rsa" which is translated to sha1WithRSAEncryption
*/
gpg_error_t
ksba_certreq_set_sig_val (ksba_certreq_t cr, ksba_const_sexp_t sigval)
{
  const unsigned char *s, *saved;
  char *buf = NULL;
  unsigned long n, len;
  int pass, nparam;

  if (!cr)
    return gpg_error (GPG_ERR_INV_VALUE);

  s = sigval;
  if (*s != '(')
    return gpg_error (GPG_ERR_INV_SEXP);
  s++;

  if (!(n = snext (&s)))
    return gpg_error (GPG_ERR_INV_SEXP);
  if (!smatch (&s, 7, "sig-val"))
    return gpg_error (GPG_ERR_UNKNOWN_SEXP);
  if (*s != '(')
    return gpg_error (digitp (s)? GPG_ERR_UNKNOWN_SEXP : GPG_ERR_INV_SEXP);
  s++;

  /* break out the algorithm ID */
  if (!(n = snext (&s)))
    return gpg_error (GPG_ERR_INV_SEXP);
  xfree (cr->sig_val.algo);
  if (n==3 && s[0] == 'r' && s[1] == 's' && s[2] == 'a')
    { /* kludge to allow "rsa" to be passed as algorithm name */
      cr->sig_val.algo = xtrystrdup ("1.2.840.113549.1.1.5");
      if (!cr->sig_val.algo)
        return gpg_error (GPG_ERR_ENOMEM);
    }
  else
    {
      cr->sig_val.algo = xtrymalloc (n+1);
      if (!cr->sig_val.algo)
        return gpg_error (GPG_ERR_ENOMEM);
      memcpy (cr->sig_val.algo, s, n);
      cr->sig_val.algo[n] = 0;
      if (!memcmp (s, "eddsa", 5))
        cr->sig_val.is_ecc = 2;
    }
  s += n;

  if (cr->sig_val.is_ecc == 2
      || !strcmp (cr->sig_val.algo, "1.3.101.112")           /* Ed25519 */
      || !strcmp (cr->sig_val.algo, "1.3.101.113"))          /* Ed448   */
    cr->sig_val.is_ecc = 2;
  else if (!strcmp (cr->sig_val.algo, "1.2.840.10045.4.1")   /* with-sha1   */
        || !strcmp (cr->sig_val.algo, "1.2.840.10045.4.3.1") /* with-sha224 */
        || !strcmp (cr->sig_val.algo, "1.2.840.10045.4.3.2") /* with-sha256 */
        || !strcmp (cr->sig_val.algo, "1.2.840.10045.4.3.3") /* with-sha384 */
        || !strcmp (cr->sig_val.algo, "1.2.840.10045.4.3.4"))/* with-sha512 */
    cr->sig_val.is_ecc = 1;
  else
    cr->sig_val.is_ecc = 0;

  /* And now the values.
   *
   * If there is only one value, the signature is simply
   * that value. Otherwise, the signature is a DER-encoded
   * SEQUENCE of INTEGERs representing the different values.
   *
   * We need three passes over the values:
   * - first pass is to get the number of values (nparam);
   * - second pass is to compute the total length (len);
   * - third pass is to build the final signature. */
  for (pass = 1, nparam = len = 0, saved = s; pass < 4; pass++)
    {
      s = saved;

      if (pass == 3)
        {
          size_t needed = len;
          if (cr->sig_val.is_ecc != 2 && nparam > 1)
            needed += _ksba_ber_count_tl (TYPE_SEQUENCE, CLASS_UNIVERSAL,
                                          1, len);

          xfree (cr->sig_val.value);
          cr->sig_val.value = xtrymalloc (needed);
          if (!cr->sig_val.value)
            return gpg_error (GPG_ERR_ENOMEM);
          cr->sig_val.valuelen = needed;
          buf = cr->sig_val.value;

          if (cr->sig_val.is_ecc != 2 && nparam > 1)
            buf += _ksba_ber_encode_tl (buf, TYPE_SEQUENCE,
                                        CLASS_UNIVERSAL, 1, len);
        }

      while (*s != ')')
        {
          if (*s != '(')
            return gpg_error (digitp (s)? GPG_ERR_UNKNOWN_SEXP : GPG_ERR_INV_SEXP);
          s++;
          if (!(n = snext (&s)))
            return gpg_error (GPG_ERR_INV_SEXP);
          s += n; /* Ignore the name of the parameter. */

          if (!digitp (s))
            return gpg_error (GPG_ERR_UNKNOWN_SEXP);
          if (!(n = snext (&s)))
            return gpg_error (GPG_ERR_INV_SEXP);

          if (pass == 1)
            nparam++;
          else if (pass == 2)
            {
              if (cr->sig_val.is_ecc == 2 || nparam == 1)
                len += n;
              else
                len += _ksba_ber_count_tl (TYPE_INTEGER, CLASS_UNIVERSAL, 0,
                                           *s >= 0x80? n + 1 : n)
                       + (*s >= 0x80? n + 1 : n);
            }
          else if (pass == 3)
            {
              if (cr->sig_val.is_ecc == 2 || nparam == 1)
                {
                  memcpy (buf, s, n);
                  buf += n;
                }
              else
                {
                  if (*s >= 0x80)
                   { /* Add leading zero byte.  */
                     buf += _ksba_ber_encode_tl (buf, TYPE_INTEGER,
                                                 CLASS_UNIVERSAL, 0, n + 1);
                     *buf++ = 0;
                   }
                 else
                   buf += _ksba_ber_encode_tl (buf, TYPE_INTEGER,
                                               CLASS_UNIVERSAL, 0, n);
                  memcpy (buf, s, n);
                  buf += n;
                }
            }

          s += n;
          if (*s != ')')
            return gpg_error (GPG_ERR_UNKNOWN_SEXP);
          s++;
        }
    }

  /* we need 2 closing parenthesis */
  if ( *s != ')' || s[1] != ')')
    return gpg_error (GPG_ERR_INV_SEXP);

  return 0;
}



/* Build the extension block and return it in R_DER and R_DERLEN.  IF
   CERTMODE is true build X.509 certificate extension instead.  */
static gpg_error_t
build_extensions (ksba_certreq_t cr, int certmode,
                  void **r_der, size_t *r_derlen)
{
  gpg_error_t err;
  ksba_writer_t writer, w=NULL;
  struct extn_list_s *e;
  unsigned char *value = NULL;
  size_t valuelen;
  unsigned char *p;
  size_t n;

  *r_der = NULL;
  *r_derlen = 0;
  err = ksba_writer_new (&writer);
  if (err)
    goto leave;
  err = ksba_writer_set_mem (writer, 2048);
  if (err)
    goto leave;
  err = ksba_writer_new (&w);
  if (err)
    goto leave;

  for (e=cr->extn_list; e; e = e->next)
    {
      err = ksba_writer_set_mem (w, e->derlen + 100);
      if (err)
        goto leave;

      err = ksba_oid_from_str (e->oid, &p, &n);
      if(err)
        goto leave;
      err = _ksba_ber_write_tl (w, TYPE_OBJECT_ID, CLASS_UNIVERSAL, 0, n);
      if (!err)
        err = ksba_writer_write (w, p, n);
      xfree (p);

      if (e->critical)
        {
          err = _ksba_ber_write_tl (w, TYPE_BOOLEAN, CLASS_UNIVERSAL, 0, 1);
          if (!err)
            err = ksba_writer_write (w, "\xff", 1);
          if(err)
            goto leave;
        }

      err = _ksba_ber_write_tl (w, TYPE_OCTET_STRING, CLASS_UNIVERSAL,
                                0, e->derlen);
      if (!err)
        err = ksba_writer_write (w, e->der, e->derlen);
      if(err)
        goto leave;

      p = ksba_writer_snatch_mem (w, &n);
      if (!p)
        {
          err = gpg_error (GPG_ERR_ENOMEM);
          goto leave;
        }
      err = _ksba_ber_write_tl (writer, TYPE_SEQUENCE, CLASS_UNIVERSAL,
                                1, n);
      if (!err)
        err = ksba_writer_write (writer, p, n);
      xfree (p); p = NULL;
      if (err)
        goto leave;
    }

  /* Embed all the sequences into another sequence */
  value = ksba_writer_snatch_mem (writer, &valuelen);
  if (!value)
    {
      err = gpg_error (GPG_ERR_ENOMEM);
      goto leave;
    }
  err = ksba_writer_set_mem (writer, valuelen+10);
  if (err)
    goto leave;
  err = _ksba_ber_write_tl (writer, TYPE_SEQUENCE, CLASS_UNIVERSAL,
                            1, valuelen);
  if (!err)
    err = ksba_writer_write (writer, value, valuelen);
  if (err)
    goto leave;

  xfree (value);
  value = ksba_writer_snatch_mem (writer, &valuelen);
  if (!value)
    {
      err = gpg_error (GPG_ERR_ENOMEM);
      goto leave;
    }

  if (!certmode)
    {
      /* Now create the extension request sequence content */
      err = ksba_writer_set_mem (writer, valuelen+100);
      if (err)
        goto leave;
      err = ksba_oid_from_str (oidstr_extensionReq, &p, &n);
      if(err)
        goto leave;
      err = _ksba_ber_write_tl (writer, TYPE_OBJECT_ID, CLASS_UNIVERSAL, 0, n);
      if (!err)
        err = ksba_writer_write (writer, p, n);
      xfree (p); p = NULL;
      if (err)
        return err;
      err = _ksba_ber_write_tl (writer, TYPE_SET, CLASS_UNIVERSAL, 1, valuelen);
      if (!err)
        err = ksba_writer_write (writer, value, valuelen);

      /* Put this all into a SEQUENCE */
      xfree (value);
      value = ksba_writer_snatch_mem (writer, &valuelen);
      if (!value)
        {
          err = gpg_error (GPG_ERR_ENOMEM);
          goto leave;
        }
      err = ksba_writer_set_mem (writer, valuelen+10);
      if (err)
        goto leave;
      err = _ksba_ber_write_tl (writer, TYPE_SEQUENCE, CLASS_UNIVERSAL,
                                1, valuelen);
      if (!err)
        err = ksba_writer_write (writer, value, valuelen);
      if (err)
        goto leave;

      xfree (value);
      value = ksba_writer_snatch_mem (writer, &valuelen);
      if (!value)
        {
          err = gpg_error (GPG_ERR_ENOMEM);
          goto leave;
        }
    }

  *r_der = value;
  *r_derlen = valuelen;
  value = NULL;


 leave:
  ksba_writer_release (writer);
  ksba_writer_release (w);
  xfree (value);
  return err;
}


/* Build a value tree from the already stored values. */
static gpg_error_t
build_cri (ksba_certreq_t cr)
{
  gpg_error_t err;
  ksba_writer_t writer;
  void *value = NULL;
  size_t valuelen;
  int certmode;

  /* If a serial number has been set, we don't create a CSR but a
     proper certificate.  */
  certmode = !!cr->x509.serial.der;

  err = ksba_writer_new (&writer);
  if (err)
    goto leave;
  err = ksba_writer_set_mem (writer, 2048);
  if (err)
    goto leave;

  if (!cr->key.der)
    {
      err = gpg_error (GPG_ERR_MISSING_VALUE);
      goto leave;
    }

  /* We write all stuff out to a temporary writer object, then use
     this object to create the cri and store the cri image */

  if (certmode)
    {
      /* Store the version structure; version is 3 (encoded as 2):
         [0] { INTEGER 2 }  */
      err = ksba_writer_write (writer, "\xa0\x03\x02\x01\x02", 5);
    }
  else
    {
      /* Store version v1 (which is a 0).  */
      err = _ksba_ber_write_tl (writer, TYPE_INTEGER, CLASS_UNIVERSAL, 0, 1);
      if (!err)
        err = ksba_writer_write (writer, "", 1);
    }
  if (err)
    goto leave;

  /* For a certificate we need to store the s/n, the signature
     algorithm identifier, the issuer DN and the validity.  */
  if (certmode)
    {
      /* Store the serial number. */
      err = _ksba_ber_write_tl (writer, TYPE_INTEGER, CLASS_UNIVERSAL, 0,
                                cr->x509.serial.derlen);
      if (!err)
        err = ksba_writer_write (writer,
                                 cr->x509.serial.der, cr->x509.serial.derlen);
      if (err)
        goto leave;

      /* Store the signature algorithm identifier.  */
      if (!cr->x509.siginfo.der)
        err = gpg_error (GPG_ERR_MISSING_VALUE);
      else
        err = ksba_writer_write (writer,
                                 cr->x509.siginfo.der, cr->x509.siginfo.derlen);
      if (err)
        goto leave;


      /* Store the issuer DN.  If no issuer DN has been set we use the
         subject DN.  */
      if (cr->x509.issuer.der)
        err = ksba_writer_write (writer,
                                 cr->x509.issuer.der, cr->x509.issuer.derlen);
      else if (cr->subject.der)
        err = ksba_writer_write (writer, cr->subject.der, cr->subject.derlen);
      else
        err = gpg_error (GPG_ERR_MISSING_VALUE);
      if (err)
        goto leave;

      /* Store the Validity.  */
      {
        unsigned char templ[36];
        unsigned char *tp;

        tp = templ;
        *tp++ = 0x30;
        *tp++ = 0x22;

        *tp++ = TYPE_GENERALIZED_TIME;
        *tp++ = 15;
        if (cr->x509.not_before[0])
          {
            if (_ksba_cmp_time (cr->x509.not_before, "20500101T000000") >= 0)
              {
                memcpy (tp, cr->x509.not_before, 8);
                tp += 8;
                memcpy (tp, cr->x509.not_before+9, 6);
                tp += 6;
              }
            else
              {
                tp[-2] = TYPE_UTC_TIME;
                tp[-1] = 13;
                memcpy (tp, cr->x509.not_before+2, 6);
                tp += 6;
                memcpy (tp, cr->x509.not_before+9, 6);
                tp += 6;
              }
          }
        else
          {
            tp[-2] = TYPE_UTC_TIME;
            tp[-1] = 13;
            memcpy (tp, "110101000000", 12);
            tp += 12;
          }
        *tp++ = 'Z';

        *tp++ = TYPE_GENERALIZED_TIME;
        *tp++ = 15;
        if (cr->x509.not_after[0])
          {
            if (_ksba_cmp_time (cr->x509.not_after, "20500101T000000") >= 0)
              {
                memcpy (tp, cr->x509.not_after, 8);
                tp += 8;
                memcpy (tp, cr->x509.not_after+9, 6);
                tp += 6;
              }
            else
              {
                tp[-2] = TYPE_UTC_TIME;
                tp[-1] = 13;
                memcpy (tp, cr->x509.not_after+2, 6);
                tp += 6;
                memcpy (tp, cr->x509.not_after+9, 6);
                tp += 6;
              }
          }
        else
          {
            memcpy (tp,"20630405170000", 14);
            tp += 14;
          }
        *tp++ = 'Z';
        assert (tp - templ <= 36);
        templ[1] = tp - templ - 2;  /* Fixup the sequence length.  */

        err = ksba_writer_write (writer, templ, tp - templ);
        if (err)
          goto leave;
      }
    }

  /* store the subject */
  if (!cr->subject.der)
    {
      err = gpg_error (GPG_ERR_MISSING_VALUE);
      goto leave;
    }
  err = ksba_writer_write (writer, cr->subject.der, cr->subject.derlen);
  if (err)
    goto leave;

  /* store the public key info */
  err = ksba_writer_write (writer, cr->key.der, cr->key.derlen);
  if (err)
    goto leave;

  /* Copy generalNames objects to the extension list. */
  if (cr->subject_alt_names)
    {
      err = add_general_names_to_extn (cr, cr->subject_alt_names,
                                       oidstr_subjectAltName);
      if (err)
        goto leave;
      while (cr->subject_alt_names)
        {
          struct general_names_s *tmp = cr->subject_alt_names->next;
          xfree (cr->subject_alt_names);
          cr->subject_alt_names = tmp;
        }
      cr->subject_alt_names = NULL;
    }


  /* Write the extensions.  Note that the implicit SET OF is REQUIRED */
  xfree (value); value = NULL;
  valuelen = 0;
  if (cr->extn_list)
    {
      err = build_extensions (cr, certmode, &value, &valuelen);
      if (err)
        goto leave;
      err = _ksba_ber_write_tl (writer, certmode? 3:0,
                                CLASS_CONTEXT, 1, valuelen);
      if (!err)
        err = ksba_writer_write (writer, value, valuelen);
      if (err)
        goto leave;
    }
  else
    { /* We can't write an object of length zero using our ber_write
         function.  So we must open encode it. */
      err = ksba_writer_write (writer,
                               certmode? "\xa3\x02\x30":"\xa0\x02\x30", 4);
      if (err)
        goto leave;
    }


  /* pack it into the sequence */
  xfree (value);
  value = ksba_writer_snatch_mem (writer, &valuelen);
  if (!value)
    {
      err = gpg_error (GPG_ERR_ENOMEM);
      goto leave;
    }
  /* reinitialize the buffer to create the outer sequence */
  err = ksba_writer_set_mem (writer, valuelen+10);
  if (err)
    goto leave;
  /* write outer sequence */
  err = _ksba_ber_write_tl (writer, TYPE_SEQUENCE, CLASS_UNIVERSAL,
                            1, valuelen);
  if (!err)
    err = ksba_writer_write (writer, value, valuelen);
  if (err)
    goto leave;

  /* and store the final result */
  cr->cri.der = ksba_writer_snatch_mem (writer, &cr->cri.derlen);
  if (!cr->cri.der)
    err = gpg_error (GPG_ERR_ENOMEM);

 leave:
  ksba_writer_release (writer);
  xfree (value);
  return err;
}

static gpg_error_t
hash_cri (ksba_certreq_t cr)
{
  if (!cr->hash_fnc)
    return gpg_error (GPG_ERR_MISSING_ACTION);
  if (!cr->cri.der)
    return gpg_error (GPG_ERR_INV_STATE);
  cr->hash_fnc (cr->hash_fnc_arg, cr->cri.der, cr->cri.derlen);
  return 0;
}


/* The user has calculated the signatures and we can now write
   the signature */
static gpg_error_t
sign_and_write (ksba_certreq_t cr)
{
  gpg_error_t err;
  ksba_der_t dbld;
  unsigned char *value = NULL;
  size_t valuelen;

  dbld = _ksba_der_builder_new (0);
  if (!dbld)
    {
      err = gpg_error_from_syserror ();
      goto leave;
    }

  /* Start outer sequence.  */
  _ksba_der_add_tag (dbld, 0, TYPE_SEQUENCE);

  /* Store the cri */
  if (!cr->cri.der)
    {
      err = gpg_error (GPG_ERR_MISSING_VALUE);
      goto leave;
    }
  _ksba_der_add_der (dbld, cr->cri.der, cr->cri.derlen);

  /* Store the signatureAlgorithm */
  if (!cr->sig_val.algo)
    return gpg_error (GPG_ERR_MISSING_VALUE);
  _ksba_der_add_tag (dbld, 0, TYPE_SEQUENCE);
  _ksba_der_add_oid (dbld, cr->sig_val.algo);
  if (!cr->sig_val.is_ecc)
    _ksba_der_add_ptr (dbld, 0, TYPE_NULL, NULL, 0);
  _ksba_der_add_end (dbld);

  /* Write the signature */
  _ksba_der_add_bts (dbld, cr->sig_val.value, cr->sig_val.valuelen, 0);

  /* End outer sequence.  */
  _ksba_der_add_end (dbld);

  /* and finally write the result */
  err = _ksba_der_builder_get (dbld, &value, &valuelen);
  if (err)
    goto leave;

  if (!cr->writer)
    err = gpg_error (GPG_ERR_MISSING_ACTION);
  else
    err = ksba_writer_write (cr->writer, value, valuelen);

 leave:
  ksba_der_release (dbld);
  xfree (value);
  return err;
}



/* The main function to build a certificate request.  It is used in a
 * loop to allow for interaction between the function and the caller */
gpg_error_t
ksba_certreq_build (ksba_certreq_t cr, ksba_stop_reason_t *r_stopreason)
{
  enum {
    sSTART,
    sHASHING,
    sGOTSIG,
    sERROR
  } state = sERROR;
  gpg_error_t err = 0;
  ksba_stop_reason_t stop_reason;

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

  if (!cr->any_build_done)
    { /* first time initialization of the stop reason */
      *r_stopreason = 0;
      cr->any_build_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_NEED_HASH:
      state = sHASHING;
      break;
    case KSBA_SR_NEED_SIG:
      if (!cr->sig_val.algo)
        err = gpg_error (GPG_ERR_MISSING_ACTION);
      else
        state = sGOTSIG;
      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 = build_cri (cr);
      break;
    case sHASHING:
      err = hash_cri (cr);
      break;
    case sGOTSIG:
      err = sign_and_write (cr);
      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_NEED_HASH; /* caller should set the hash function*/
      break;
    case sHASHING:
      stop_reason = KSBA_SR_NEED_SIG;
      break;
    case sGOTSIG:
      stop_reason = KSBA_SR_READY;
      break;
    default:
      break;
    }

  *r_stopreason = stop_reason;
  return 0;
}
