/* card-p15.c - PKCS-15 based card access
 *	Copyright (C) 2002 Free Software Foundation, Inc.
 *
 * This file is part of GnuPG.
 *
 * GnuPG is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * GnuPG 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 copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

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

#ifdef HAVE_OPENSC
#include <opensc/pkcs15.h>
#include <ksba.h>

#include "scdaemon.h"
#include "card-common.h"


struct p15private_s {
  int n_prkey_rsa_objs;
  struct sc_pkcs15_object *prkey_rsa_objs[32];
  int n_cert_objs;
  struct sc_pkcs15_object *cert_objs[32];
};


/* Allocate private data. */
static int 
init_private_data (CARD card)
{
  struct p15private_s *priv;
  int rc;

  if (card->p15priv)
    return 0; /* already done. */

  priv = xtrycalloc (1, sizeof *priv);
  if (!priv)
    return out_of_core ();

  /* OpenSC (0.7.0) is a bit strange in that the get_objects functions
     tries to be a bit too clever and implicitly does an enumeration
     which eventually leads to the fact that every call to this
     fucntion returns one more macthing object.  The old code in
     p15_enum_keypairs assume that it would alwyas return the same
     numer of objects and used this to figure out what the last object
     enumerated is.  We now do an enum_objects just once and keep it
     in the private data. */
  rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_PRKEY_RSA, 
                              priv->prkey_rsa_objs,
                              DIM (priv->prkey_rsa_objs));
  if (rc < 0) 
    {
      log_error ("private keys enumeration failed: %s\n", sc_strerror (rc));
      xfree (priv);
      return gpg_error (GPG_ERR_CARD);
    }
  priv->n_prkey_rsa_objs = rc;

  /* Read all certificate objects. */
  rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_CERT_X509, 
                              priv->cert_objs,
                              DIM (priv->cert_objs));
  if (rc < 0) 
    {
      log_error ("private keys enumeration failed: %s\n", sc_strerror (rc));
      xfree (priv);
      return gpg_error (GPG_ERR_CARD);
    }
  priv->n_cert_objs = rc;

  card->p15priv = priv;
  return 0;
}


/* Release private data used in this module. */
void
p15_release_private_data (CARD card)
{
  if (!card->p15priv)
    return;
  xfree (card->p15priv);
  card->p15priv = NULL;
}



/* See card.c for interface description */
static int
p15_enum_keypairs (CARD card, int idx,
                   unsigned char *keygrip, char **keyid)
{
  int rc;
  KsbaError krc;
  struct p15private_s *priv;
  struct sc_pkcs15_object *tmpobj;
  int nobjs;
  struct sc_pkcs15_prkey_info *pinfo;
  struct sc_pkcs15_cert_info *certinfo;
  struct sc_pkcs15_cert      *certder;
  KsbaCert cert;

  rc = init_private_data (card);
  if (rc) 
      return rc;
  priv = card->p15priv;
  nobjs = priv->n_prkey_rsa_objs;
  rc = 0;
  if (idx >= nobjs)
    return -1;
  pinfo = priv->prkey_rsa_objs[idx]->data;
  
  /* now we need to read the certificate so that we can calculate the
     keygrip */
  rc = sc_pkcs15_find_cert_by_id (card->p15card, &pinfo->id, &tmpobj);
  if (rc)
    {
      log_info ("certificate for private key %d not found: %s\n",
                idx, sc_strerror (rc));
      /* note, that we return the ID anyway */
      rc = gpg_error (GPG_ERR_MISSING_CERTIFICATE);
      goto return_keyid;
    }
  certinfo = tmpobj->data;
  rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder);
  if (rc)
    {
      log_info ("failed to read certificate for private key %d: %s\n",
                idx, sc_strerror (rc));
      return gpg_error (GPG_ERR_CARD);
    }

  cert = ksba_cert_new ();
  if (!cert)
    {
      gpg_error_t tmperr = out_of_core ();
      sc_pkcs15_free_certificate (certder);
      return tmperr;
    }
  krc = ksba_cert_init_from_mem (cert, certder->data, certder->data_len);
  sc_pkcs15_free_certificate (certder);
  if (krc)
    {
      log_error ("failed to parse the certificate for private key %d: %s\n",
                 idx, ksba_strerror (krc));
      ksba_cert_release (cert);
      return gpg_error (GPG_ERR_CARD);
    }
  if (card_help_get_keygrip (cert, keygrip))
    {
      log_error ("failed to calculate the keygrip of private key %d\n", idx);
      ksba_cert_release (cert);
      return gpg_error (GPG_ERR_CARD);
    }      
  ksba_cert_release (cert);

  rc = 0;
 return_keyid:
  if (keyid)
    {
      char *p;
      int i;

      *keyid = p = xtrymalloc (9+pinfo->id.len*2+1);
      if (!*keyid)
        return out_of_core ();
      p = stpcpy (p, "P15-5015.");
      for (i=0; i < pinfo->id.len; i++, p += 2)
        sprintf (p, "%02X", pinfo->id.value[i]);
      *p = 0;
    }
  
  return rc;
}

/* See card.c for interface description */
static int
p15_enum_certs (CARD card, int idx, char **certid, int *type)
{
  int rc;
  struct p15private_s *priv;
  struct sc_pkcs15_object *obj;
  struct sc_pkcs15_cert_info *cinfo;
  int nobjs;

  rc = init_private_data (card);
  if (rc) 
      return rc;
  priv = card->p15priv;
  nobjs = priv->n_cert_objs;
  rc = 0;
  if (idx >= nobjs)
    return -1;
  obj =  priv->cert_objs[idx];
  cinfo = obj->data;
  
  if (certid)
    {
      char *p;
      int i;

      *certid = p = xtrymalloc (9+cinfo->id.len*2+1);
      if (!*certid)
        return out_of_core ();
      p = stpcpy (p, "P15-5015.");
      for (i=0; i < cinfo->id.len; i++, p += 2)
        sprintf (p, "%02X", cinfo->id.value[i]);
      *p = 0;
    }
  if (type)
    {
      if (!obj->df)
        *type = 0; /* unknown */
      else if (obj->df->type == SC_PKCS15_CDF)
        *type = 100;
      else if (obj->df->type == SC_PKCS15_CDF_TRUSTED)
        *type = 101;
      else if (obj->df->type == SC_PKCS15_CDF_USEFUL)
        *type = 102;
      else 
        *type = 0; /* error -> unknown */
    }
  
  return rc;
}



static int
idstr_to_id (const char *idstr, struct sc_pkcs15_id *id)
{
  const char *s;
  int n;

  /* For now we only support the standard DF */
  if (strncmp (idstr, "P15-5015.", 9) ) 
    return gpg_error (GPG_ERR_INV_ID);
  for (s=idstr+9, n=0; hexdigitp (s); s++, n++)
    ;
  if (*s || (n&1))
    return gpg_error (GPG_ERR_INV_ID); /*invalid or odd number of digits*/
  n /= 2;
  if (!n || n > SC_PKCS15_MAX_ID_SIZE)
    return gpg_error (GPG_ERR_INV_ID); /* empty or too large */
  for (s=idstr+9, n=0; *s; s += 2, n++)
    id->value[n] = xtoi_2 (s);
  id->len = n;
  return 0;
}


/* See card.c for interface description */
static int
p15_read_cert (CARD card, const char *certidstr,
               unsigned char **cert, size_t *ncert)
{
  struct sc_pkcs15_object *tmpobj;
  struct sc_pkcs15_id certid;
  struct sc_pkcs15_cert_info *certinfo;
  struct sc_pkcs15_cert      *certder;
  int rc;

  if (!card || !certidstr || !cert || !ncert)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (!card->p15card)
    return gpg_error (GPG_ERR_NO_PKCS15_APP);

  rc = idstr_to_id (certidstr, &certid);
  if (rc)
    return rc;

  rc = sc_pkcs15_find_cert_by_id (card->p15card, &certid, &tmpobj);
  if (rc)
    {
      log_info ("certificate '%s' not found: %s\n", 
                certidstr, sc_strerror (rc));
      return -1;
    }
  certinfo = tmpobj->data;
  rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder);
  if (rc)
    {
      log_info ("failed to read certificate '%s': %s\n",
                certidstr, sc_strerror (rc));
      return gpg_error (GPG_ERR_CARD);
    }

  *cert = xtrymalloc (certder->data_len);
  if (!*cert)
    {
      gpg_error_t tmperr = out_of_core ();
      sc_pkcs15_free_certificate (certder);
      return tmperr;
    }
  memcpy (*cert, certder->data, certder->data_len);
  *ncert = certder->data_len;
  sc_pkcs15_free_certificate (certder);
  return 0;
}





static int
p15_prepare_key (CARD card, const char *keyidstr,
                 int (pincb)(void*, const char *, char **),
                 void *pincb_arg, struct sc_pkcs15_object **r_keyobj)
{
  struct sc_pkcs15_id keyid;
  struct sc_pkcs15_pin_info *pin;
  struct sc_pkcs15_object *keyobj, *pinobj;
  char *pinvalue;
  int rc;

  rc = idstr_to_id (keyidstr, &keyid);
  if (rc)
    return rc;

  rc = sc_pkcs15_find_prkey_by_id (card->p15card, &keyid, &keyobj);
  if (rc < 0)
    {
      log_error ("private key not found: %s\n", sc_strerror(rc));
      return gpg_error (GPG_ERR_NO_SECRET_KEY);
    }

  rc = sc_pkcs15_find_pin_by_auth_id (card->p15card,
                                      &keyobj->auth_id, &pinobj);
  if (rc)
    {
      log_error ("failed to find PIN by auth ID: %s\n", sc_strerror (rc));
      return gpg_error (GPG_ERR_BAD_PIN_METHOD);
    }
  pin = pinobj->data;

  /* Fixme: pack this into a verification loop */
  /* Fixme: we might want to pass pin->min_length and 
     pin->stored_length */
  rc = pincb (pincb_arg, pinobj->label, &pinvalue);
  if (rc)
    {
      log_info ("PIN callback returned error: %s\n", gnupg_strerror (rc));
      return rc;
    }

  rc = sc_pkcs15_verify_pin (card->p15card, pin,
                             pinvalue, strlen (pinvalue));
  xfree (pinvalue);
  if (rc)
    {
      log_info ("PIN verification failed: %s\n", sc_strerror (rc));
      return gpg_error (GPG_ERR_BAD_PIN);
    }

  /* fixme: check wheter we need to release KEYOBJ in case of an error */
  *r_keyobj = keyobj;
  return 0;
}


/* See card.c for interface description */
static int 
p15_sign (CARD card, const char *keyidstr, int hashalgo,
          int (pincb)(void*, const char *, char **),
          void *pincb_arg,
          const void *indata, size_t indatalen,
          unsigned char **outdata, size_t *outdatalen )
{
  unsigned int cryptflags;
  struct sc_pkcs15_object *keyobj;
  int rc;
  unsigned char *outbuf = NULL;
  size_t outbuflen;

  if (hashalgo != GCRY_MD_SHA1)
    return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);

  rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj);
  if (rc)
    return rc;

  cryptflags = SC_ALGORITHM_RSA_PAD_PKCS1;

  outbuflen = 1024; 
  outbuf = xtrymalloc (outbuflen);
  if (!outbuf)
    return out_of_core ();
  
  rc = sc_pkcs15_compute_signature (card->p15card, keyobj,
                                    cryptflags,
                                    indata, indatalen,
                                    outbuf, outbuflen );
  if (rc < 0)
    {
      log_error ("failed to create signature: %s\n", sc_strerror (rc));
      rc = gpg_error (GPG_ERR_CARD);
    }
  else
    {
      *outdatalen = rc;
      *outdata = outbuf;
      outbuf = NULL;
      rc = 0;
    }

  xfree (outbuf);
  return rc;
}


/* See card.c for description */
static int 
p15_decipher (CARD card, const char *keyidstr,
              int (pincb)(void*, const char *, char **),
              void *pincb_arg,
              const void *indata, size_t indatalen,
              unsigned char **outdata, size_t *outdatalen )
{
  struct sc_pkcs15_object *keyobj;
  int rc;
  unsigned char *outbuf = NULL;
  size_t outbuflen;

  rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj);
  if (rc)
    return rc;

  if (card && card->scard && card->scard->driver
      && !strcasecmp (card->scard->driver->short_name, "tcos"))
    {
      /* very ugly hack to force the use of a local key.  We need this
         until we have fixed the initialization code for TCOS cards */
      struct sc_pkcs15_prkey_info *prkey = keyobj->data;
      if ( !(prkey->key_reference & 0x80))
        {
          prkey->key_reference |= 0x80;
          log_debug ("using TCOS hack to force the use of local keys\n");
        }
      if (*keyidstr && keyidstr[strlen(keyidstr)-1] == '6')
        {
          prkey->key_reference |= 1;
          log_debug ("warning: using even more TCOS hacks\n");
        }
    }

  outbuflen = indatalen < 256? 256 : indatalen; 
  outbuf = xtrymalloc (outbuflen);
  if (!outbuf)
    return out_of_core ();

  rc = sc_pkcs15_decipher (card->p15card, keyobj, 
                           0,
                           indata, indatalen, 
                           outbuf, outbuflen); 
  if (rc < 0)
    {
      log_error ("failed to decipher the data: %s\n", sc_strerror (rc));
      rc = gpg_error (GPG_ERR_CARD);
    }
  else
    {
      *outdatalen = rc;
      *outdata = outbuf;
      outbuf = NULL;
      rc = 0;
    }

  xfree (outbuf);
  return rc;
}



/* Bind our operations to the card */
void
card_p15_bind (CARD card)
{
  card->fnc.enum_keypairs = p15_enum_keypairs;
  card->fnc.enum_certs    = p15_enum_certs;
  card->fnc.read_cert     = p15_read_cert;
  card->fnc.sign          = p15_sign;
  card->fnc.decipher      = p15_decipher;
}
#endif /*HAVE_OPENSC*/
