/* app-dinsig.c - The DINSIG (DIN V 66291-1) card application.
 * Copyright (C) 2002, 2004, 2005, 2007, 2008 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 3 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, see <https://www.gnu.org/licenses/>.
 */


/* The German signature law and its bylaw (SigG and SigV) is currently
   used with an interface specification described in DIN V 66291-1.
   The AID to be used is: 'D27600006601'.

   The file IDs for certificates utilize the generic format:
        Cxyz
    C being the hex digit 'C' (12).
    x being the service indicator:
         '0' := SigG conform digital signature.
         '1' := entity authentication.
         '2' := key encipherment.
         '3' := data encipherment.
         '4' := key agreement.
         other values are reserved for future use.
    y being the security environment number using '0' for cards
      not supporting a SE number.
    z being the certificate type:
         '0'        := C.CH (base certificate of card holder) or C.ICC.
         '1' .. '7' := C.CH (business or professional certificate
                       of card holder.
         '8' .. 'D' := C.CA (certificate of a CA issue by the Root-CA).
         'E'        := C.RCA (self certified certificate of the Root-CA).
         'F'        := reserved.

   The file IDs used by default are:
   '1F00'  EF.SSD (security service descriptor). [o,o]
   '2F02'  EF.GDO (global data objects) [m,m]
   'A000'  EF.PROT (signature log).  Cyclic file with 20 records of 53 byte.
           Read and update after user authentication. [o,o]
   'B000'  EF.PK.RCA.DS (public keys of Root-CA).  Size is 512b or size
           of keys. [m (unless a 'C00E' is present),m]
   'B001'  EF.PK.CA.DS (public keys of CAs).  Size is 512b or size
           of keys. [o,o]
   'C00n'  EF.C.CH.DS (digital signature certificate of card holder)
           with n := 0 .. 7.  Size is 2k or size of cert.  Read and
           update allowed after user authentication. [m,m]
   'C00m'  EF.C.CA.DS (digital signature certificate of CA)
           with m := 8 .. E.  Size is 1k or size of cert.  Read always
           allowed, update after user authentication. [o,o]
   'C100'  EF.C.ICC.AUT (AUT certificate of ICC) [o,m]
   'C108'  EF.C.CA.AUT (AUT certificate of CA) [o,m]
   'D000'  EF.DM (display message) [-,m]

   The letters in brackets indicate optional or mandatory files: The
   first for card terminals under full control and the second for
   "business" card terminals.
*/




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

#include "scdaemon.h"

#include "../common/i18n.h"
#include "iso7816.h"
#include "../common/tlv.h"


static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
  gpg_error_t err;
  char ct_buf[100], id_buf[100];
  char hexkeygrip[41];
  size_t len, certoff;
  unsigned char *der;
  size_t derlen;
  ksba_cert_t cert;
  int fid;

  (void)flags;

  /* Return the certificate of the card holder. */
  fid = 0xC000;
  len = app_help_read_length_of_cert (app_get_slot (app), fid, &certoff);
  if (!len)
    return 0; /* Card has not been personalized. */

  sprintf (ct_buf, "%d", 101);
  sprintf (id_buf, "DINSIG.%04X", fid);
  send_status_info (ctrl, "CERTINFO",
                    ct_buf, strlen (ct_buf),
                    id_buf, strlen (id_buf),
                    NULL, (size_t)0);

  /* Now we need to read the certificate, so that we can get the
     public key out of it.  */
  err = iso7816_read_binary (app_get_slot (app), certoff, len-certoff,
                             &der, &derlen);
  if (err)
    {
      log_info ("error reading entire certificate from FID 0x%04X: %s\n",
                fid, gpg_strerror (err));
      return 0;
    }

  err = ksba_cert_new (&cert);
  if (err)
    {
      xfree (der);
      return err;
    }
  err = ksba_cert_init_from_mem (cert, der, derlen);
  xfree (der); der = NULL;
  if (err)
    {
      log_error ("failed to parse the certificate at FID 0x%04X: %s\n",
                 fid, gpg_strerror (err));
      ksba_cert_release (cert);
      return err;
    }
  err = app_help_get_keygrip_string (cert, hexkeygrip, NULL, NULL);
  if (err)
    {
      log_error ("failed to calculate the keygrip for FID 0x%04X\n", fid);
      ksba_cert_release (cert);
      return gpg_error (GPG_ERR_CARD);
    }
  ksba_cert_release (cert);

  sprintf (id_buf, "DINSIG.%04X", fid);
  send_status_info (ctrl, "KEYPAIRINFO",
                    hexkeygrip, 40,
                    id_buf, strlen (id_buf),
                    NULL, (size_t)0);
  return 0;
}




/* Read the certificate with id CERTID (as returned by learn_status in
   the CERTINFO status lines) and return it in the freshly allocated
   buffer put into CERT and the length of the certificate put into
   CERTLEN.

   FIXME: This needs some cleanups and caching with do_learn_status.
*/
static gpg_error_t
do_readcert (app_t app, const char *certid,
             unsigned char **cert, size_t *certlen)
{
  int fid;
  gpg_error_t err;
  unsigned char *buffer;
  const unsigned char *p;
  size_t buflen, n;
  int class, tag, constructed, ndef;
  size_t totobjlen, objlen, hdrlen;
  int rootca = 0;

  *cert = NULL;
  *certlen = 0;
  if (strncmp (certid, "DINSIG.", 7) )
    return gpg_error (GPG_ERR_INV_ID);
  certid += 7;
  if (!hexdigitp (certid) || !hexdigitp (certid+1)
      || !hexdigitp (certid+2) || !hexdigitp (certid+3)
      || certid[4])
    return gpg_error (GPG_ERR_INV_ID);
  fid = xtoi_4 (certid);
  if (fid != 0xC000 )
    return gpg_error (GPG_ERR_NOT_FOUND);

  /* Read the entire file.  fixme: This could be optimized by first
     reading the header to figure out how long the certificate
     actually is. */
  err = iso7816_select_file (app_get_slot (app), fid, 0);
  if (err)
    {
      log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
      return err;
    }

  err = iso7816_read_binary (app_get_slot (app), 0, 0, &buffer, &buflen);
  if (err)
    {
      log_error ("error reading certificate from FID 0x%04X: %s\n",
                 fid, gpg_strerror (err));
      return err;
    }

  if (!buflen || *buffer == 0xff)
    {
      log_info ("no certificate contained in FID 0x%04X\n", fid);
      err = gpg_error (GPG_ERR_NOT_FOUND);
      goto leave;
    }

  /* Now figure something out about the object. */
  p = buffer;
  n = buflen;
  err = parse_ber_header (&p, &n, &class, &tag, &constructed,
                          &ndef, &objlen, &hdrlen);
  if (err)
    goto leave;
  if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed )
    ;
  else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
    rootca = 1;
  else
    {
      err = gpg_error (GPG_ERR_INV_OBJ);
      goto leave;
    }
  totobjlen = objlen + hdrlen;
  log_assert (totobjlen <= buflen);

  err = parse_ber_header (&p, &n, &class, &tag, &constructed,
                          &ndef, &objlen, &hdrlen);
  if (err)
    goto leave;

  if (rootca)
    ;
  else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
    {
      const unsigned char *save_p;

      /* The certificate seems to be contained in a userCertificate
         container.  Skip this and assume the following sequence is
         the certificate. */
      if (n < objlen)
        {
          err = gpg_error (GPG_ERR_INV_OBJ);
          goto leave;
        }
      p += objlen;
      n -= objlen;
      save_p = p;
      err = parse_ber_header (&p, &n, &class, &tag, &constructed,
                              &ndef, &objlen, &hdrlen);
      if (err)
        goto leave;
      if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
        return gpg_error (GPG_ERR_INV_OBJ);
      totobjlen = objlen + hdrlen;
      log_assert (save_p + totobjlen <= buffer + buflen);
      memmove (buffer, save_p, totobjlen);
    }

  *cert = buffer;
  buffer = NULL;
  *certlen = totobjlen;

 leave:
  xfree (buffer);
  return err;
}


/* Verify the PIN if required.  */
static gpg_error_t
verify_pin (app_t app,
            gpg_error_t (*pincb)(void*, const char *, char **),
            void *pincb_arg)
{
  const char *s;
  int rc;
  pininfo_t pininfo;

  if ( app->did_chv1 && !app->force_chv1 )
    return 0;  /* No need to verify it again.  */

  memset (&pininfo, 0, sizeof pininfo);
  pininfo.fixedlen = -1;
  pininfo.minlen = 6;
  pininfo.maxlen = 8;

  if (!opt.disable_pinpad
      && !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo) )
    {
      rc = pincb (pincb_arg,
                  _("||Please enter your PIN at the reader's pinpad"),
                  NULL);
      if (rc)
        {
          log_info (_("PIN callback returned error: %s\n"),
                    gpg_strerror (rc));
          return rc;
        }
      rc = iso7816_verify_kp (app_get_slot (app), 0x81, &pininfo);
      /* Dismiss the prompt. */
      pincb (pincb_arg, NULL, NULL);
    }
  else  /* No Pinpad.  */
    {
      char *pinvalue;

      rc = pincb (pincb_arg, "PIN", &pinvalue);
      if (rc)
        {
          log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
          return rc;
        }

      /* We require the PIN to be at least 6 and at max 8 bytes.
         According to the specs, this should all be ASCII.  */
      for (s=pinvalue; digitp (s); s++)
        ;
      if (*s)
        {
          log_error ("Non-numeric digits found in PIN\n");
          xfree (pinvalue);
          return gpg_error (GPG_ERR_BAD_PIN);
        }

      if (strlen (pinvalue) < pininfo.minlen)
        {
          log_error ("PIN is too short; minimum length is %d\n",
                     pininfo.minlen);
          xfree (pinvalue);
          return gpg_error (GPG_ERR_BAD_PIN);
        }
      else if (strlen (pinvalue) > pininfo.maxlen)
        {
          log_error ("PIN is too large; maximum length is %d\n",
                     pininfo.maxlen);
          xfree (pinvalue);
          return gpg_error (GPG_ERR_BAD_PIN);
        }

      rc = iso7816_verify (app_get_slot (app), 0x81,
                           pinvalue, strlen (pinvalue));
      if (gpg_err_code (rc) == GPG_ERR_INV_VALUE)
        {
          /* We assume that ISO 9564-1 encoding is used and we failed
             because the first nibble we passed was 3 and not 2.  DIN
             says something about looking up such an encoding in the
             SSD but I was not able to find any tag relevant to
             this. */
          char paddedpin[8];
          int i, ndigits;

          for (ndigits=0, s=pinvalue; *s; ndigits++, s++)
            ;
          i = 0;
          paddedpin[i++] = 0x20 | (ndigits & 0x0f);
          for (s=pinvalue; i < sizeof paddedpin && *s && s[1]; s = s+2 )
            paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f));
          if (i < sizeof paddedpin && *s)
            paddedpin[i++] = (((*s - '0') << 4) | 0x0f);
          while (i < sizeof paddedpin)
            paddedpin[i++] = 0xff;
          rc = iso7816_verify (app_get_slot (app), 0x81,
                               paddedpin, sizeof paddedpin);
        }
      xfree (pinvalue);
    }

  if (rc)
    {
      log_error ("verify PIN failed\n");
      return rc;
    }
  app->did_chv1 = 1;
  return 0;
}



/* Create the signature and return the allocated result in OUTDATA.
   If a PIN is required the PINCB will be used to ask for the PIN;
   that callback should return the PIN in an allocated buffer and
   store that in the 3rd argument.  */
static gpg_error_t
do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
         gpg_error_t (*pincb)(void*, const char *, char **),
         void *pincb_arg,
         const void *indata, size_t indatalen,
         unsigned char **outdata, size_t *outdatalen )
{
  static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
    { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
      0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
  static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
    { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
      0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
  static unsigned char sha256_prefix[19] = /* OID is 2.16.840.1.101.3.4.2.1 */
    { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
      0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
      0x00, 0x04, 0x20 };
  int rc;
  int fid;
  unsigned char data[19+32]; /* Must be large enough for a SHA-256 digest
                                + the largest OID _prefix above. */
  int datalen;

  (void)ctrl;

  if (!keyidstr || !*keyidstr)
    return gpg_error (GPG_ERR_INV_VALUE);
  if (indatalen != 20 && indatalen != 16 && indatalen != 32
      && indatalen != (15+20) && indatalen != (19+32))
    return gpg_error (GPG_ERR_INV_VALUE);

  /* Check that the provided ID is valid.  This is not really needed
     but we do it to enforce correct usage by the caller. */
  if (strncmp (keyidstr, "DINSIG.", 7) )
    return gpg_error (GPG_ERR_INV_ID);
  keyidstr += 7;
  if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
      || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
      || keyidstr[4])
    return gpg_error (GPG_ERR_INV_ID);
  fid = xtoi_4 (keyidstr);
  if (fid != 0xC000)
    return gpg_error (GPG_ERR_NOT_FOUND);

  /* Prepare the DER object from INDATA. */
  datalen = 35;
  if (indatalen == 15+20)
    {
      /* Alright, the caller was so kind to send us an already
         prepared DER object.  Check that it is what we want and that
         it matches the hash algorithm. */
      if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
        ;
      else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix,15))
        ;
      else
        return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
      memcpy (data, indata, indatalen);
    }
  else if (indatalen == 19+32)
    {
      /* Alright, the caller was so kind to send us an already
         prepared DER object.  Check that it is what we want and that
         it matches the hash algorithm. */
      datalen = indatalen;
      if (hashalgo == GCRY_MD_SHA256 && !memcmp (indata, sha256_prefix, 19))
        ;
      else if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha256_prefix, 19))
        {
          /* Fixme: This is a kludge.  A better solution is not to use
             SHA1 as default but use an autodetection.  However this
             needs changes in all app-*.c */
          datalen  = indatalen;
        }
      else
        return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
      memcpy (data, indata, indatalen);
    }
  else
    {
      int len = 15;
      if (hashalgo == GCRY_MD_SHA1)
        memcpy (data, sha1_prefix, len);
      else if (hashalgo == GCRY_MD_RMD160)
        memcpy (data, rmd160_prefix, len);
      else if (hashalgo == GCRY_MD_SHA256)
        {
          len = 19;
          datalen = len + indatalen;
          memcpy (data, sha256_prefix, len);
        }
      else
        return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
      memcpy (data+len, indata, indatalen);
    }

  rc = verify_pin (app, pincb, pincb_arg);
  if (!rc)
    rc = iso7816_compute_ds (app_get_slot (app), 0, data, datalen, 0,
                             outdata, outdatalen);
  return rc;
}


#if 0
#warning test function - works but may brick your card
/* Handle the PASSWD command.  CHVNOSTR is currently ignored; we
   always use VHV0.  RESET_MODE is not yet implemented.  */
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl,  const char *chvnostr,
               unsigned int flags,
               gpg_error_t (*pincb)(void*, const char *, char **),
               void *pincb_arg)
{
  gpg_error_t err;
  char *pinvalue;
  const char *oldpin;
  size_t oldpinlen;

  if ((flags & APP_CHANGE_FLAG_RESET))
    return gpg_error (GPG_ERR_NOT_IMPLEMENTED);

  if ((flags & APP_CHANGE_FLAG_NULLPIN))
    {
      /* With the nullpin flag, we do not verify the PIN - it would fail
         if the Nullpin is still set.  */
      oldpin = "\0\0\0\0\0";
      oldpinlen = 6;
    }
  else
    {
      err = verify_pin (app, pincb, pincb_arg);
      if (err)
        return err;
      oldpin = NULL;
      oldpinlen = 0;
    }

  /* TRANSLATORS: Do not translate the "|*|" prefixes but
     keep it at the start of the string.  We need this elsewhere
     to get some infos on the string. */
  err = pincb (pincb_arg, _("|N|Initial New PIN"), &pinvalue);
  if (err)
    {
      log_error (_("error getting new PIN: %s\n"), gpg_strerror (err));
      return err;
    }

  err = iso7816_change_reference_data (app_get_slot (app), 0x81,
                                       oldpin, oldpinlen,
                                       pinvalue, strlen (pinvalue));
  xfree (pinvalue);
  return err;
}
#endif /*0*/


/* Select the DINSIG application on the card in SLOT.  This function
   must be used before any other DINSIG application functions. */
gpg_error_t
app_select_dinsig (app_t app)
{
  static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 };
  int slot = app_get_slot (app);
  int rc;

  rc = iso7816_select_application (slot, aid, sizeof aid, 0);
  if (!rc)
    {
      app->apptype = APPTYPE_DINSIG;

      app->fnc.prep_reselect = NULL;
      app->fnc.reselect = NULL;
      app->fnc.learn_status = do_learn_status;
      app->fnc.readcert = do_readcert;
      app->fnc.getattr = NULL;
      app->fnc.setattr = NULL;
      app->fnc.genkey = NULL;
      app->fnc.sign = do_sign;
      app->fnc.auth = NULL;
      app->fnc.decipher = NULL;
      app->fnc.change_pin = NULL /*do_change_pin*/;
      app->fnc.check_pin = NULL;

      app->force_chv1 = 1;
   }

  return rc;
}
