docker: source checking container
[gnuk/gnuk.git] / src / openpgp.c
index eec46ee..5b788b0 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * openpgp.c -- OpenPGP card protocol support
  *
- * Copyright (C) 2010, 2011 Free Software Initiative of Japan
+ * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
+ *               Free Software Initiative of Japan
  * Author: NIIBE Yutaka <gniibe@fsij.org>
  *
  * This file is a part of Gnuk, a GnuPG USB Token implementation.
  *
  */
 
+#include <stdint.h>
+#include <string.h>
+#include <chopstx.h>
+#include <eventflag.h>
+
 #include "config.h"
-#include "ch.h"
-#include "hal.h"
+
 #include "gnuk.h"
-#include "openpgp.h"
-#include "polarssl/config.h"
-#include "polarssl/sha1.h"
+#include "sys.h"
+#include "status-code.h"
+#include "sha256.h"
+#include "random.h"
+
+static struct eventflag *openpgp_comm;
+
+#define ADMIN_PASSWD_MINLEN 8
 
 #define CLS(a) a.cmd_apdu_head[0]
 #define INS(a) a.cmd_apdu_head[1]
 #define INS_CHANGE_REFERENCE_DATA              0x24
 #define INS_PSO                                        0x2a
 #define INS_RESET_RETRY_COUNTER                        0x2c
+#define INS_ACTIVATE_FILE                      0x44
 #define INS_PGP_GENERATE_ASYMMETRIC_KEY_PAIR   0x47
+#define INS_EXTERNAL_AUTHENTICATE              0x82
+#define INS_GET_CHALLENGE                      0x84
 #define INS_INTERNAL_AUTHENTICATE              0x88
 #define INS_SELECT_FILE                                0xa4
 #define INS_READ_BINARY                                0xb0
@@ -47,6 +60,9 @@
 #define INS_UPDATE_BINARY                      0xd6
 #define INS_PUT_DATA                           0xda
 #define INS_PUT_DATA_ODD                       0xdb    /* For key import */
+#define INS_TERMINATE_DF                       0xe6
+
+static const uint8_t *challenge; /* Random bytes */
 
 static const uint8_t
 select_file_TOP_result[] __attribute__ ((aligned (1))) = {
@@ -76,20 +92,32 @@ set_res_sw (uint8_t sw1, uint8_t sw2)
 #define FILE_DF_OPENPGP        1
 #define FILE_MF                2
 #define FILE_EF_DIR    3
-#define FILE_EF_SERIAL 4
-#define FILE_EF_CH_CERTIFICATE  5
-#define FILE_EF_RANDOM         6
+#define FILE_EF_SERIAL_NO      4
+#define FILE_EF_UPDATE_KEY_0   5
+#define FILE_EF_UPDATE_KEY_1   6
+#define FILE_EF_UPDATE_KEY_2   7
+#define FILE_EF_UPDATE_KEY_3   8
+#define FILE_EF_CH_CERTIFICATE 9
+#define FILE_CARD_TERMINATED_OPENPGP   254
+#define FILE_CARD_TERMINATED   255
 
-static uint8_t file_selection;
+uint8_t file_selection;
 
 static void
 gpg_init (void)
 {
-  const uint8_t *flash_data_start;
+  const uint8_t *flash_do_start;
+  const uint8_t *flash_do_end;
+
+  flash_init (&flash_do_start, &flash_do_end);
 
-  file_selection = FILE_NONE;
-  flash_data_start = flash_init ();
-  gpg_data_scan (flash_data_start);
+  if (flash_do_start == NULL)
+    file_selection = FILE_CARD_TERMINATED;
+  else
+    file_selection = FILE_NONE;
+
+  gpg_data_scan (flash_do_start, flash_do_end);
+  flash_init_keys ();
 }
 
 static void
@@ -99,7 +127,7 @@ gpg_fini (void)
 }
 
 #if defined(PINPAD_SUPPORT)
-/* 
+/*
  * Let user input PIN string.
  * Return length of the string.
  * The string itself is in PIN_INPUT_BUFFER.
@@ -109,9 +137,9 @@ get_pinpad_input (int msg_code)
 {
   int r;
 
-  chEvtSignal (main_thread, LED_INPUT_MODE);
-  r = pinpad_getline (msg_code, MS2ST (8000));
-  chEvtSignal (main_thread, LED_STATUS_MODE);
+  led_blink (LED_START_COMMAND);
+  r = pinpad_getline (msg_code, 8000000);
+  led_blink (LED_FINISH_COMMAND);
   return r;
 }
 #endif
@@ -120,6 +148,7 @@ static void
 cmd_verify (void)
 {
   int len;
+  uint8_t p1 = P1 (apdu);
   uint8_t p2 = P2 (apdu);
   int r;
   const uint8_t *pw;
@@ -130,6 +159,41 @@ cmd_verify (void)
   len = apdu.cmd_apdu_data_len;
   pw = apdu.cmd_apdu_data;
 
+  if (len == 0)
+    {
+      if (p1 == 0)
+       {                       /* This is to examine status.  */
+         if (p2 == 0x81)
+           r = ac_check_status (AC_PSO_CDS_AUTHORIZED);
+         else if (p2 == 0x82)
+           r = ac_check_status (AC_OTHER_AUTHORIZED);
+         else
+           r = ac_check_status (AC_ADMIN_AUTHORIZED);
+
+         if (r)
+           GPG_SUCCESS ();     /* If authentication done already, return success.  */
+         else
+           {            /* If not, return retry counter, encoded.  */
+             r = gpg_pw_get_retry_counter (p2);
+             set_res_sw (0x63, 0xc0 | (r&0x0f));
+           }
+       }
+      else if (p1 == 0xff)
+       {                       /* Reset the status.  */
+         if (p2 == 0x81)
+           ac_reset_pso_cds ();
+         else if (p2 == 0x82)
+           ac_reset_other ();
+         else
+           ac_reset_admin ();
+         GPG_SUCCESS ();
+       }
+      else
+       GPG_BAD_P1_P2 ();
+      return;
+    }
+
+  /* This is real authentication.  */
   if (p2 == 0x81)
     r = verify_pso_cds (pw, len);
   else if (p2 == 0x82)
@@ -207,16 +271,22 @@ static void
 cmd_change_password (void)
 {
   uint8_t old_ks[KEYSTRING_MD_SIZE];
-  uint8_t new_ks0[KEYSTRING_MD_SIZE+1];
-  uint8_t *new_ks = &new_ks0[1];
+  uint8_t new_ks0[KEYSTRING_SIZE];
+  uint8_t *new_salt = KS_GET_SALT (new_ks0);
+  int newsalt_len = SALT_SIZE;
+  uint8_t *new_ks = KS_GET_KEYSTRING (new_ks0);
   uint8_t p1 = P1 (apdu);      /* 0: change (old+new), 1: exchange (new) */
   uint8_t p2 = P2 (apdu);
   int len;
-  const uint8_t *pw;
-  const uint8_t *newpw;
+  uint8_t *pw, *newpw;
   int pw_len, newpw_len;
   int who = p2 - 0x80;
+  int who_old;
   int r;
+  int pw3_null = 0;
+  const uint8_t *salt;
+  int salt_len;
+  const uint8_t *ks_pw3;
 
   DEBUG_INFO ("Change PW\r\n");
   DEBUG_BYTE (who);
@@ -226,7 +296,7 @@ cmd_change_password (void)
 
   if (p1 != 0)
     {
-      GPG_FUNCTION_NOT_SUPPORTED();
+      GPG_FUNCTION_NOT_SUPPORTED ();
       return;
     }
 
@@ -234,7 +304,19 @@ cmd_change_password (void)
     {
       const uint8_t *ks_pw1 = gpg_do_read_simple (NR_DO_KEYSTRING_PW1);
 
-      pw_len = verify_user_0 (AC_PSO_CDS_AUTHORIZED, pw, len, -1, ks_pw1);
+      who_old = who;
+      pw_len = verify_user_0 (AC_PSO_CDS_AUTHORIZED, pw, len, -1, ks_pw1, 0);
+
+      if (ks_pw1 == NULL)
+       {
+         salt = NULL;
+         salt_len = 0;
+       }
+      else
+       {
+         salt = KS_GET_SALT (ks_pw1);
+         salt_len = SALT_SIZE;
+       }
 
       if (pw_len < 0)
        {
@@ -252,11 +334,32 @@ cmd_change_password (void)
        {
          newpw = pw + pw_len;
          newpw_len = len - pw_len;
+         ks_pw3 = gpg_do_read_simple (NR_DO_KEYSTRING_PW3);
+
+         /* Check length of password for admin-less mode.  */
+         if (ks_pw3 == NULL && newpw_len < ADMIN_PASSWD_MINLEN)
+           {
+             DEBUG_INFO ("new password length is too short.");
+             GPG_CONDITION_NOT_SATISFIED ();
+             return;
+           }
        }
     }
   else                         /* PW3 (0x83) */
     {
-      pw_len = verify_admin_0 (pw, len, -1);
+      ks_pw3 = gpg_do_read_simple (NR_DO_KEYSTRING_PW3);
+      pw_len = verify_admin_0 (pw, len, -1, ks_pw3, 0);
+
+      if (ks_pw3 == NULL)
+       {
+         salt = NULL;
+         salt_len = 0;
+       }
+      else
+       {
+         salt = KS_GET_SALT (ks_pw3);
+         salt_len = SALT_SIZE;
+       }
 
       if (pw_len < 0)
        {
@@ -274,15 +377,25 @@ cmd_change_password (void)
        {
          newpw = pw + pw_len;
          newpw_len = len - pw_len;
-         gpg_set_pw3 (newpw, newpw_len);
+         if (newpw_len == 0 && admin_authorized == BY_ADMIN)
+           {
+             newpw_len = strlen (OPENPGP_CARD_INITIAL_PW3);
+             memcpy (newpw, OPENPGP_CARD_INITIAL_PW3, newpw_len);
+             newsalt_len = 0;
+             pw3_null = 1;
+           }
+
+         who_old = admin_authorized;
        }
     }
 
-  sha1 (pw, pw_len, old_ks);
-  sha1 (newpw, newpw_len, new_ks);
+  if (newsalt_len != 0)
+    random_get_salt (new_salt);
+  s2k (salt, salt_len, pw, pw_len, old_ks);
+  s2k (new_salt, newsalt_len, newpw, newpw_len, new_ks);
   new_ks0[0] = newpw_len;
 
-  r = gpg_change_keystring (who, old_ks, who, new_ks);
+  r = gpg_change_keystring (who_old, old_ks, who, new_ks);
   if (r <= -2)
     {
       DEBUG_INFO ("memory error.\r\n");
@@ -295,28 +408,90 @@ cmd_change_password (void)
     }
   else if (r == 0 && who == BY_USER)   /* no prvkey */
     {
-      gpg_do_write_simple (NR_DO_KEYSTRING_PW1, new_ks0, KEYSTRING_SIZE_PW1);
-      ac_reset_pso_cds ();
-      ac_reset_other ();
-      DEBUG_INFO ("Changed DO_KEYSTRING_PW1.\r\n");
-      GPG_SUCCESS ();
+      DEBUG_INFO ("user pass change not supported with no keys.\r\n");
+      GPG_CONDITION_NOT_SATISFIED ();
     }
   else if (r > 0 && who == BY_USER)
     {
-      gpg_do_write_simple (NR_DO_KEYSTRING_PW1, new_ks0, 1);
+      gpg_do_write_simple (NR_DO_KEYSTRING_PW1, new_ks0, KS_META_SIZE);
       ac_reset_pso_cds ();
       ac_reset_other ();
+      if (admin_authorized == BY_USER)
+       ac_reset_admin ();
       DEBUG_INFO ("Changed length of DO_KEYSTRING_PW1.\r\n");
       GPG_SUCCESS ();
     }
-  else                         /* r >= 0 && who == BY_ADMIN */
+  else if (r > 0 && who == BY_ADMIN)
     {
-      DEBUG_INFO ("done.\r\n");
+      if (pw3_null)
+       gpg_do_write_simple (NR_DO_KEYSTRING_PW3, NULL, 0);
+      else
+       gpg_do_write_simple (NR_DO_KEYSTRING_PW3, new_ks0, KS_META_SIZE);
+
+      ac_reset_admin ();
+      DEBUG_INFO ("Changed length of DO_KEYSTRING_PW3.\r\n");
+      GPG_SUCCESS ();
+    }
+  else /* r == 0 && who == BY_ADMIN */ /* no prvkey */
+    {
+      if (pw3_null)
+       gpg_do_write_simple (NR_DO_KEYSTRING_PW3, NULL, 0);
+      else
+       {
+         new_ks0[0] |= PW_LEN_KEYSTRING_BIT;
+         gpg_do_write_simple (NR_DO_KEYSTRING_PW3, new_ks0, KEYSTRING_SIZE);
+       }
+      DEBUG_INFO ("Changed DO_KEYSTRING_PW3.\r\n");
       ac_reset_admin ();
       GPG_SUCCESS ();
     }
 }
 
+
+#ifndef S2KCOUNT
+/*
+ * OpenPGP uses the value 65535 for the key on disk.
+ * Given the condition that the access to flash ROM is harder than disk,
+ * that is, the threat model is different, we chose the default value 192.
+ */
+#define S2KCOUNT 192
+#endif
+void
+s2k (const unsigned char *salt, size_t slen,
+     const unsigned char *input, size_t ilen, unsigned char output[32])
+{
+  sha256_context ctx;
+  size_t count = S2KCOUNT;
+  const uint8_t *unique = unique_device_id ();
+
+  sha256_start (&ctx);
+
+  sha256_update (&ctx, unique, 12);
+
+  while (count > slen + ilen)
+    {
+      if (slen)
+       sha256_update (&ctx, salt, slen);
+      sha256_update (&ctx, input, ilen);
+      count -= slen + ilen;
+    }
+
+  if (count <= slen)
+    sha256_update (&ctx, salt, count);
+  else
+    {
+      if (slen)
+       {
+         sha256_update (&ctx, salt, slen);
+         count -= slen;
+       }
+      sha256_update (&ctx, input, count);
+    }
+
+  sha256_finish (&ctx, output);
+}
+
+
 static void
 cmd_reset_user_password (void)
 {
@@ -326,8 +501,11 @@ cmd_reset_user_password (void)
   const uint8_t *newpw;
   int pw_len, newpw_len;
   int r;
-  uint8_t new_ks0[KEYSTRING_MD_SIZE+1];
-  uint8_t *new_ks = &new_ks0[1];
+  uint8_t new_ks0[KEYSTRING_SIZE];
+  uint8_t *new_ks = KS_GET_KEYSTRING (new_ks0);
+  uint8_t *new_salt = KS_GET_SALT (new_ks0);
+  const uint8_t *salt;
+  int salt_len;
 
   DEBUG_INFO ("Reset PW1\r\n");
   DEBUG_BYTE (p1);
@@ -354,11 +532,14 @@ cmd_reset_user_password (void)
          return;
        }
 
-      pw_len = ks_rc[0];
+      pw_len = ks_rc[0] & PW_LEN_MASK;
+      salt = KS_GET_SALT (ks_rc);
+      salt_len = SALT_SIZE;
       newpw = pw + pw_len;
       newpw_len = len - pw_len;
-      sha1 (pw, pw_len, old_ks);
-      sha1 (newpw, newpw_len, new_ks);
+      random_get_salt (new_salt);
+      s2k (salt, salt_len, pw, pw_len, old_ks);
+      s2k (new_salt, SALT_SIZE, newpw, newpw_len, new_ks);
       new_ks0[0] = newpw_len;
       r = gpg_change_keystring (BY_RESETCODE, old_ks, BY_USER, new_ks);
       if (r <= -2)
@@ -368,29 +549,23 @@ cmd_reset_user_password (void)
        }
       else if (r < 0)
        {
-       sec_fail:
          DEBUG_INFO ("failed.\r\n");
          gpg_pw_increment_err_counter (PW_ERR_RC);
          GPG_SECURITY_FAILURE ();
        }
       else if (r == 0)
        {
-         if (memcmp (ks_rc+1, old_ks, KEYSTRING_MD_SIZE) != 0)
-           goto sec_fail;
-         DEBUG_INFO ("done (no prvkey).\r\n");
-         gpg_do_write_simple (NR_DO_KEYSTRING_PW1, new_ks0,
-                              KEYSTRING_SIZE_PW1);
-         ac_reset_pso_cds ();
-         ac_reset_other ();
-         gpg_pw_reset_err_counter (PW_ERR_RC);
-         gpg_pw_reset_err_counter (PW_ERR_PW1);
-         GPG_SUCCESS ();
+         DEBUG_INFO ("user pass change not supported with no keys.\r\n");
+         GPG_CONDITION_NOT_SATISFIED ();
        }
       else
        {
          DEBUG_INFO ("done.\r\n");
+         gpg_do_write_simple (NR_DO_KEYSTRING_PW1, new_ks0, KS_META_SIZE);
          ac_reset_pso_cds ();
          ac_reset_other ();
+         if (admin_authorized == BY_USER)
+           ac_reset_admin ();
          gpg_pw_reset_err_counter (PW_ERR_RC);
          gpg_pw_reset_err_counter (PW_ERR_PW1);
          GPG_SUCCESS ();
@@ -409,7 +584,8 @@ cmd_reset_user_password (void)
 
       newpw_len = len;
       newpw = pw;
-      sha1 (newpw, newpw_len, new_ks);
+      random_get_salt (new_salt);
+      s2k (new_salt, SALT_SIZE, newpw, newpw_len, new_ks);
       new_ks0[0] = newpw_len;
       r = gpg_change_keystring (admin_authorized, old_ks, BY_USER, new_ks);
       if (r <= -2)
@@ -424,19 +600,17 @@ cmd_reset_user_password (void)
        }
       else if (r == 0)
        {
-         DEBUG_INFO ("done (no privkey).\r\n");
-         gpg_do_write_simple (NR_DO_KEYSTRING_PW1, new_ks0,
-                              KEYSTRING_SIZE_PW1);
-         ac_reset_pso_cds ();
-         ac_reset_other ();
-         gpg_pw_reset_err_counter (PW_ERR_PW1);
-         GPG_SUCCESS ();
+         DEBUG_INFO ("user pass change not supported with no keys.\r\n");
+         GPG_CONDITION_NOT_SATISFIED ();
        }
       else
        {
          DEBUG_INFO ("done.\r\n");
+         gpg_do_write_simple (NR_DO_KEYSTRING_PW1, new_ks0, KS_META_SIZE);
          ac_reset_pso_cds ();
          ac_reset_other ();
+         if (admin_authorized == BY_USER)
+           ac_reset_admin ();
          gpg_pw_reset_err_counter (PW_ERR_PW1);
          GPG_SUCCESS ();
        }
@@ -452,9 +626,6 @@ cmd_put_data (void)
 
   DEBUG_INFO (" - PUT DATA\r\n");
 
-  if (file_selection != FILE_DF_OPENPGP)
-    GPG_NO_RECORD();
-
   tag = ((P1 (apdu)<<8) | P2 (apdu));
   len = apdu.cmd_apdu_data_len;
   data = apdu.cmd_apdu_data;
@@ -471,32 +642,102 @@ cmd_pgp_gakp (void)
     /* Get public key */
     gpg_do_public_key (apdu.cmd_apdu_data[0]);
   else
-    {                                  /* Generate key pair */
+    {
       if (!ac_check_status (AC_ADMIN_AUTHORIZED))
        GPG_SECURITY_FAILURE ();
-
-      /* XXX: Not yet supported */
-      GPG_ERROR ();
+      gpg_do_keygen (apdu.cmd_apdu_data[0]);
     }
 }
 
+const uint8_t *
+gpg_get_firmware_update_key (uint8_t keyno)
+{
+  extern uint8_t _updatekey_store;
+  const uint8_t *p;
+
+  p = &_updatekey_store + keyno * FIRMWARE_UPDATE_KEY_CONTENT_LEN;
+  return p;
+}
+
+#ifdef CERTDO_SUPPORT
+#define FILEID_CH_CERTIFICATE_IS_VALID 1
+#else
+#define FILEID_CH_CERTIFICATE_IS_VALID 0
+#endif
+
 static void
 cmd_read_binary (void)
 {
+  int is_short_EF = (P1 (apdu) & 0x80) != 0;
+  uint8_t file_id;
+  const uint8_t *p;
+  uint16_t offset;
+
   DEBUG_INFO (" - Read binary\r\n");
 
-  if (file_selection == FILE_EF_SERIAL)
+  if (is_short_EF)
+    file_id = (P1 (apdu) & 0x1f);
+  else
+    file_id = file_selection - FILE_EF_SERIAL_NO + FILEID_SERIAL_NO;
+
+  if ((!FILEID_CH_CERTIFICATE_IS_VALID && file_id == FILEID_CH_CERTIFICATE)
+      || file_id > FILEID_CH_CERTIFICATE)
+    {
+      GPG_NO_FILE ();
+      return;
+    }
+
+  if (is_short_EF)
+    {
+      file_selection = file_id - FILEID_SERIAL_NO + FILE_EF_SERIAL_NO;
+      offset = P2 (apdu);
+    }
+  else
+    offset = (P1 (apdu) << 8) | P2 (apdu);
+
+  if (file_id == FILEID_SERIAL_NO)
     {
-      if (P2 (apdu) >= 6)
-       GPG_BAD_P0_P1 ();
+      if (offset != 0)
+       GPG_BAD_P1_P2 ();
       else
        {
-         gpg_do_get_data (0x004f, 1); /* AID */
-         res_APDU[0] = 0x5a;
+         gpg_do_get_data (0x004f, 1); /* Get AID... */
+         res_APDU[0] = 0x5a; /* ... and overwrite the first byte of data. */
        }
+      return;
     }
-  else
-    GPG_NO_RECORD();
+
+  if (file_id >= FILEID_UPDATE_KEY_0 && file_id <= FILEID_UPDATE_KEY_3)
+    {
+      if (offset != 0)
+       GPG_MEMORY_FAILURE ();
+      else
+       {
+         p = gpg_get_firmware_update_key (file_id - FILEID_UPDATE_KEY_0);
+         res_APDU_size = FIRMWARE_UPDATE_KEY_CONTENT_LEN;
+         memcpy (res_APDU, p, FIRMWARE_UPDATE_KEY_CONTENT_LEN);
+         GPG_SUCCESS ();
+       }
+    }
+#if defined(CERTDO_SUPPORT)
+  else /* file_id == FILEID_CH_CERTIFICATE */
+    {
+      uint16_t len = 256;
+
+      p = &ch_certificate_start;
+      if (offset >= FLASH_CH_CERTIFICATE_SIZE)
+       GPG_MEMORY_FAILURE ();
+      else
+       {
+         if (offset + len >= FLASH_CH_CERTIFICATE_SIZE)
+           len = FLASH_CH_CERTIFICATE_SIZE - offset;
+
+         res_APDU_size = len;
+         memcpy (res_APDU, p + offset, len);
+         GPG_SUCCESS ();
+       }
+    }
+#endif
 }
 
 static void
@@ -517,19 +758,17 @@ cmd_select_file (void)
          return;
        }
 
-      file_selection = FILE_DF_OPENPGP;
-      if ((P2 (apdu) & 0x0c) == 0x0c)  /* No FCI */
-       GPG_SUCCESS ();
-      else
+      if (file_selection == FILE_CARD_TERMINATED)
        {
-         gpg_do_get_data (0x004f, 1); /* AID */
-         memmove (res_APDU+2, res_APDU, res_APDU_size);
-         res_APDU[0] = 0x6f;
-         res_APDU[1] = 0x12;
-         res_APDU[2] = 0x84;   /* overwrite: DF name */
-         res_APDU_size += 2;
-         GPG_SUCCESS ();
+         file_selection = FILE_CARD_TERMINATED_OPENPGP;
+         GPG_APPLICATION_TERMINATED();
+         return;
        }
+
+      file_selection = FILE_DF_OPENPGP;
+
+      /* Behave just like original OpenPGP card.  */
+      GPG_SUCCESS ();
     }
   else if (apdu.cmd_apdu_data_len == 2
           && apdu.cmd_apdu_data[0] == 0x2f && apdu.cmd_apdu_data[1] == 0x02)
@@ -539,7 +778,7 @@ cmd_select_file (void)
        * MF.EF-GDO -- Serial number of the card and name of the owner
        */
       GPG_SUCCESS ();
-      file_selection = FILE_EF_SERIAL;
+      file_selection = FILE_EF_SERIAL_NO;
     }
   else if (apdu.cmd_apdu_data_len == 2
           && apdu.cmd_apdu_data[0] == 0x3f && apdu.cmd_apdu_data[1] == 0x00)
@@ -579,24 +818,38 @@ cmd_get_data (void)
 
   DEBUG_INFO (" - Get Data\r\n");
 
-  if (file_selection != FILE_DF_OPENPGP)
-    GPG_NO_RECORD ();
-
   gpg_do_get_data (tag, 0);
 }
 
+#define ECDSA_HASH_LEN 32
+#define ECDSA_SIGNATURE_LENGTH 64
+
+#define EDDSA_HASH_LEN_MAX 256
+#define EDDSA_SIGNATURE_LENGTH 64
+
+#define ECC_CIPHER_DO_HEADER_SIZE 7
+
 static void
 cmd_pso (void)
 {
   int len = apdu.cmd_apdu_data_len;
-  int r;
+  int r = -1;
+  int attr;
+  int pubkey_len;
+  unsigned int result_len = 0;
+  int cs;
 
   DEBUG_INFO (" - PSO: ");
   DEBUG_WORD ((uint32_t)&r);
   DEBUG_BINARY (apdu.cmd_apdu_data, apdu.cmd_apdu_data_len);
+  DEBUG_SHORT (len);
 
   if (P1 (apdu) == 0x9e && P2 (apdu) == 0x9a)
     {
+      attr = gpg_get_algo_attr (GPG_KEY_FOR_SIGNING);
+      pubkey_len = gpg_get_algo_attr_key_size (GPG_KEY_FOR_SIGNING,
+                                              GPG_KEY_PUBLIC);
+
       if (!ac_check_status (AC_PSO_CDS_AUTHORIZED))
        {
          DEBUG_INFO ("security error.");
@@ -604,39 +857,89 @@ cmd_pso (void)
          return;
        }
 
-      /* Check size of digestInfo */
-      if (len != 34            /* MD5 */
-         && len != 35          /* SHA1 / RIPEMD-160 */
-         && len != 47          /* SHA224 */
-         && len != 51          /* SHA256 */
-         && len != 67          /* SHA384 */
-         && len != 83)         /* SHA512 */
+      if (attr == ALGO_RSA2K || attr == ALGO_RSA4K)
        {
-         DEBUG_INFO (" wrong length: ");
-         DEBUG_SHORT (len);
-         GPG_ERROR ();
+         /* Check size of digestInfo */
+         if (len != 34         /* MD5 */
+             && len != 35              /* SHA1 / RIPEMD-160 */
+             && len != 47              /* SHA224 */
+             && len != 51              /* SHA256 */
+             && len != 67              /* SHA384 */
+             && len != 83)             /* SHA512 */
+           {
+             DEBUG_INFO (" wrong length");
+             GPG_CONDITION_NOT_SATISFIED ();
+             return;
+           }
+
+         DEBUG_BINARY (kd[GPG_KEY_FOR_SIGNING].data, pubkey_len);
+
+         result_len = pubkey_len;
+         r = rsa_sign (apdu.cmd_apdu_data, res_APDU, len,
+                       &kd[GPG_KEY_FOR_SIGNING], pubkey_len);
        }
-      else
+      else if (attr == ALGO_NISTP256R1 || attr == ALGO_SECP256K1)
        {
-         DEBUG_SHORT (len);
-         DEBUG_BINARY (&kd[GPG_KEY_FOR_SIGNING], KEY_CONTENT_LEN);
+         /* ECDSA with p256r1/p256k1 for signature */
+         if (len != ECDSA_HASH_LEN)
+           {
+             DEBUG_INFO (" wrong length");
+             GPG_CONDITION_NOT_SATISFIED ();
+             return;
+           }
 
-         r = rsa_sign (apdu.cmd_apdu_data, res_APDU, len,
-                       &kd[GPG_KEY_FOR_SIGNING]);
-         if (r < 0)
+         cs = chopstx_setcancelstate (0);
+         result_len = ECDSA_SIGNATURE_LENGTH;
+         if (attr == ALGO_NISTP256R1)
+           r = ecdsa_sign_p256r1 (apdu.cmd_apdu_data, res_APDU,
+                                  kd[GPG_KEY_FOR_SIGNING].data);
+         else                  /* ALGO_SECP256K1 */
+           r = ecdsa_sign_p256k1 (apdu.cmd_apdu_data, res_APDU,
+                                  kd[GPG_KEY_FOR_SIGNING].data);
+         chopstx_setcancelstate (cs);
+       }
+      else if (attr == ALGO_ED25519)
+       {
+         uint32_t output[64/4];        /* Require 4-byte alignment. */
+
+         if (len > EDDSA_HASH_LEN_MAX)
            {
-             ac_reset_pso_cds ();
-             GPG_ERROR ();
+             DEBUG_INFO ("wrong hash length.");
+             GPG_CONDITION_NOT_SATISFIED ();
+             return;
            }
-         else
-           /* Success */
-           gpg_increment_digital_signature_counter ();
+
+         cs = chopstx_setcancelstate (0);
+         result_len = EDDSA_SIGNATURE_LENGTH;
+         r = eddsa_sign_25519 (apdu.cmd_apdu_data, len, output,
+                               kd[GPG_KEY_FOR_SIGNING].data,
+                               kd[GPG_KEY_FOR_SIGNING].data+32,
+                               kd[GPG_KEY_FOR_SIGNING].pubkey);
+         chopstx_setcancelstate (cs);
+         memcpy (res_APDU, output, EDDSA_SIGNATURE_LENGTH);
+       }
+      else
+       {
+         DEBUG_INFO ("unknown algo.");
+         GPG_FUNCTION_NOT_SUPPORTED ();
+         return;
+       }
+
+      if (r == 0)
+       {
+         res_APDU_size = result_len;
+         gpg_increment_digital_signature_counter ();
        }
+      else   /* Failure */
+       ac_reset_pso_cds ();
     }
   else if (P1 (apdu) == 0x80 && P2 (apdu) == 0x86)
     {
-      DEBUG_SHORT (len);
-      DEBUG_BINARY (&kd[GPG_KEY_FOR_DECRYPTION], KEY_CONTENT_LEN);
+      attr = gpg_get_algo_attr (GPG_KEY_FOR_DECRYPTION);
+      pubkey_len = gpg_get_algo_attr_key_size (GPG_KEY_FOR_DECRYPTION,
+                                              GPG_KEY_PUBLIC);
+
+      DEBUG_BINARY (kd[GPG_KEY_FOR_DECRYPTION].data, pubkey_len);
 
       if (!ac_check_status (AC_OTHER_AUTHORIZED))
        {
@@ -645,14 +948,68 @@ cmd_pso (void)
          return;
        }
 
-      /* Skip padding 0x00 */
-      len--;
-      r = rsa_decrypt (apdu.cmd_apdu_data+1, res_APDU, len,
-                      &kd[GPG_KEY_FOR_DECRYPTION]);
-      if (r < 0)
-       GPG_ERROR ();
+      if (attr == ALGO_RSA2K || attr == ALGO_RSA4K)
+       {
+         /* Skip padding 0x00 */
+         len--;
+         if (len != pubkey_len)
+           {
+             GPG_CONDITION_NOT_SATISFIED ();
+             return;
+           }
+         r = rsa_decrypt (apdu.cmd_apdu_data+1, res_APDU, len,
+                          &kd[GPG_KEY_FOR_DECRYPTION], &result_len);
+       }
+      else if (attr == ALGO_NISTP256R1 || attr == ALGO_SECP256K1)
+       {
+         int header = ECC_CIPHER_DO_HEADER_SIZE;
+
+         /* Format is in big endian MPI: 04 || x || y */
+         if (len != 65 + ECC_CIPHER_DO_HEADER_SIZE
+             || apdu.cmd_apdu_data[header] != 0x04)
+           {
+             GPG_CONDITION_NOT_SATISFIED ();
+             return;
+           }
+
+         cs = chopstx_setcancelstate (0);
+         result_len = 65;
+         if (attr == ALGO_NISTP256R1)
+           r = ecdh_decrypt_p256r1 (apdu.cmd_apdu_data + header, res_APDU,
+                                    kd[GPG_KEY_FOR_DECRYPTION].data);
+         else
+           r = ecdh_decrypt_p256k1 (apdu.cmd_apdu_data + header, res_APDU,
+                                    kd[GPG_KEY_FOR_DECRYPTION].data);
+         chopstx_setcancelstate (cs);
+       }
+      else if (attr == ALGO_CURVE25519)
+       {
+         int header = ECC_CIPHER_DO_HEADER_SIZE;
+
+         if (len != 32 + ECC_CIPHER_DO_HEADER_SIZE)
+           {
+             GPG_CONDITION_NOT_SATISFIED ();
+             return;
+           }
+
+         cs = chopstx_setcancelstate (0);
+         result_len = 32;
+         r = ecdh_decrypt_curve25519 (apdu.cmd_apdu_data + header, res_APDU,
+                                      kd[GPG_KEY_FOR_DECRYPTION].data);
+         chopstx_setcancelstate (cs);
+       }
+      else
+       {
+         DEBUG_INFO ("unknown algo.");
+         GPG_FUNCTION_NOT_SUPPORTED ();
+         return;
+       }
+
+      if (r == 0)
+       res_APDU_size = result_len;
     }
-  else
+
+  if (r < 0)
     {
       DEBUG_INFO (" - ??");
       DEBUG_BYTE (P1 (apdu));
@@ -664,52 +1021,123 @@ cmd_pso (void)
   DEBUG_INFO ("PSO done.\r\n");
 }
 
+
+#define MAX_RSA_DIGEST_INFO_LEN 102 /* 40% */
 static void
 cmd_internal_authenticate (void)
 {
+  int attr = gpg_get_algo_attr (GPG_KEY_FOR_AUTHENTICATION);
+  int pubkey_len = gpg_get_algo_attr_key_size (GPG_KEY_FOR_AUTHENTICATION,
+                                              GPG_KEY_PUBLIC);
   int len = apdu.cmd_apdu_data_len;
-  int r;
+  int r = -1;
+  unsigned int result_len = 0;
+  int cs;
 
   DEBUG_INFO (" - INTERNAL AUTHENTICATE\r\n");
 
-  if (P1 (apdu) == 0x00 && P2 (apdu) == 0x00)
+  if (P1 (apdu) != 0x00 || P2 (apdu) != 0x00)
     {
-      DEBUG_SHORT (len);
+      DEBUG_INFO (" - ??");
+      DEBUG_BYTE (P1 (apdu));
+      DEBUG_INFO (" - ??");
+      DEBUG_BYTE (P2 (apdu));
+      GPG_CONDITION_NOT_SATISFIED ();
+      return;
+    }
 
-      if (!ac_check_status (AC_OTHER_AUTHORIZED))
+  DEBUG_SHORT (len);
+  if (!ac_check_status (AC_OTHER_AUTHORIZED))
+    {
+      DEBUG_INFO ("security error.");
+      GPG_SECURITY_FAILURE ();
+      return;
+    }
+
+  if (attr == ALGO_RSA2K || attr == ALGO_RSA4K)
+    {
+      if (len > MAX_RSA_DIGEST_INFO_LEN)
        {
-         DEBUG_INFO ("security error.");
-         GPG_SECURITY_FAILURE ();
+         DEBUG_INFO ("input is too long.");
+         GPG_CONDITION_NOT_SATISFIED ();
          return;
        }
 
+      result_len = pubkey_len;
       r = rsa_sign (apdu.cmd_apdu_data, res_APDU, len,
-                   &kd[GPG_KEY_FOR_AUTHENTICATION]);
-      if (r < 0)
-       GPG_ERROR ();
+                   &kd[GPG_KEY_FOR_AUTHENTICATION], pubkey_len);
     }
-  else
+  else if (attr == ALGO_NISTP256R1)
     {
-      DEBUG_INFO (" - ??");
-      DEBUG_BYTE (P1 (apdu));
-      DEBUG_INFO (" - ??");
-      DEBUG_BYTE (P2 (apdu));
-      GPG_ERROR ();
+      if (len != ECDSA_HASH_LEN)
+       {
+         DEBUG_INFO ("wrong hash length.");
+         GPG_CONDITION_NOT_SATISFIED ();
+         return;
+       }
+
+      cs = chopstx_setcancelstate (0);
+      result_len = ECDSA_SIGNATURE_LENGTH;
+      r = ecdsa_sign_p256r1 (apdu.cmd_apdu_data, res_APDU,
+                            kd[GPG_KEY_FOR_AUTHENTICATION].data);
+      chopstx_setcancelstate (cs);
+    }
+  else if (attr == ALGO_SECP256K1)
+    {
+      if (len != ECDSA_HASH_LEN)
+       {
+         DEBUG_INFO ("wrong hash length.");
+         GPG_CONDITION_NOT_SATISFIED ();
+         return;
+       }
+
+      cs = chopstx_setcancelstate (0);
+      result_len = ECDSA_SIGNATURE_LENGTH;
+      r = ecdsa_sign_p256k1 (apdu.cmd_apdu_data, res_APDU,
+                            kd[GPG_KEY_FOR_AUTHENTICATION].data);
+      chopstx_setcancelstate (cs);
+    }
+  else if (attr == ALGO_ED25519)
+    {
+      uint32_t output[64/4];   /* Require 4-byte alignment. */
+
+      if (len > EDDSA_HASH_LEN_MAX)
+       {
+         DEBUG_INFO ("wrong hash length.");
+         GPG_CONDITION_NOT_SATISFIED ();
+         return;
+       }
+
+      cs = chopstx_setcancelstate (0);
+      result_len = EDDSA_SIGNATURE_LENGTH;
+      r = eddsa_sign_25519 (apdu.cmd_apdu_data, len, output,
+                           kd[GPG_KEY_FOR_AUTHENTICATION].data,
+                           kd[GPG_KEY_FOR_AUTHENTICATION].data+32,
+                           kd[GPG_KEY_FOR_AUTHENTICATION].pubkey);
+      chopstx_setcancelstate (cs);
+      memcpy (res_APDU, output, EDDSA_SIGNATURE_LENGTH);
     }
 
+  if (r == 0)
+    res_APDU_size = result_len;
+  else
+    GPG_ERROR ();
+
   DEBUG_INFO ("INTERNAL AUTHENTICATE done.\r\n");
 }
 
 
+#define MBD_OPRATION_WRITE  0
+#define MBD_OPRATION_UPDATE 1
+
 static void
-cmd_update_binary (void)
+modify_binary (uint8_t op, uint8_t p1, uint8_t p2, int len)
 {
-  int len = apdu.cmd_apdu_data_len;
+  uint8_t file_id;
   uint16_t offset;
+  int is_short_EF = (p1 & 0x80) != 0;
   int r;
 
-  DEBUG_INFO (" - UPDATE BINARY\r\n");
-
   if (!ac_check_status (AC_ADMIN_AUTHORIZED))
     {
       DEBUG_INFO ("security error.");
@@ -717,42 +1145,56 @@ cmd_update_binary (void)
       return;
     }
 
-  if ((P1 (apdu) & 0x80))
-    if ((P1 (apdu) & 0x7f) <= FILEID_RANDOM)
-      {
-       file_selection = FILE_EF_CH_CERTIFICATE + (P1 (apdu) & 0x7f);
-       r = flash_erase_binary (file_selection - FILE_EF_CH_CERTIFICATE);
-       if (r < 0)
-         {
-           DEBUG_INFO ("memory error.\r\n");
-           GPG_MEMORY_FAILURE ();
-           return;
-         }
-
-       offset = 0;
-      }
-    else
-      {
-       GPG_NO_FILE ();
-       return;
-      }
+  if (is_short_EF)
+    file_id = (p1 & 0x1f);
   else
+    file_id = file_selection - FILE_EF_SERIAL_NO + FILEID_SERIAL_NO;
+
+  if (!FILEID_CH_CERTIFICATE_IS_VALID && file_id == FILEID_CH_CERTIFICATE)
+    {
+      GPG_NO_FILE ();
+      return;
+    }
+
+  if (op == MBD_OPRATION_UPDATE && file_id != FILEID_CH_CERTIFICATE)
     {
-      if (file_selection != FILE_EF_CH_CERTIFICATE
-         && file_selection != FILE_EF_RANDOM)
+      GPG_CONDITION_NOT_SATISFIED ();
+      return;
+    }
+
+  if (file_id > FILEID_CH_CERTIFICATE)
+    {
+      GPG_NO_FILE ();
+      return;
+    }
+
+  if (is_short_EF)
+    {
+      file_selection = file_id - FILEID_SERIAL_NO + FILE_EF_SERIAL_NO;
+      offset = p2;
+
+      if (op == MBD_OPRATION_UPDATE)
        {
-         GPG_COMMAND_NOT_ALLOWED ();
-         return;
+         r = flash_erase_binary (file_id);
+         if (r < 0)
+           {
+             DEBUG_INFO ("memory error.\r\n");
+             GPG_MEMORY_FAILURE ();
+             return;
+           }
        }
-
-      offset = (P1 (apdu) << 8) | P2 (apdu);
     }
+  else
+    offset = (p1 << 8) | p2;
 
   DEBUG_SHORT (len);
   DEBUG_SHORT (offset);
 
-  r = flash_write_binary (file_selection - FILE_EF_CH_CERTIFICATE,
-                         apdu.cmd_apdu_data, len, offset);
+  if (file_id == FILEID_CH_CERTIFICATE && (len&1))
+    /* It's OK the size of last write is odd.  */
+    apdu.cmd_apdu_data[len++] = 0xff;
+
+  r = flash_write_binary (file_id, apdu.cmd_apdu_data, len, offset);
   if (r < 0)
     {
       DEBUG_INFO ("memory error.\r\n");
@@ -760,66 +1202,179 @@ cmd_update_binary (void)
       return;
     }
 
+  if (file_id >= FILEID_UPDATE_KEY_0 && file_id <= FILEID_UPDATE_KEY_3
+      && len == 0 && offset == 0)
+    {
+      int i;
+      const uint8_t *p;
+
+      for (i = 0; i < 4; i++)
+       {
+         p = gpg_get_firmware_update_key (i);
+         if (p[0] != 0x00 || p[1] != 0x00) /* still valid */
+           break;
+       }
+
+      if (i == 4)                      /* all update keys are removed */
+       {
+         p = gpg_get_firmware_update_key (0);
+         flash_erase_page ((uint32_t)p);
+       }
+    }
+
   GPG_SUCCESS ();
+}
+
+
+#if defined(CERTDO_SUPPORT)
+static void
+cmd_update_binary (void)
+{
+  int len = apdu.cmd_apdu_data_len;
+
+  DEBUG_INFO (" - UPDATE BINARY\r\n");
+  modify_binary (MBD_OPRATION_UPDATE, P1 (apdu), P2 (apdu), len);
   DEBUG_INFO ("UPDATE BINARY done.\r\n");
 }
+#endif
 
 
 static void
 cmd_write_binary (void)
 {
   int len = apdu.cmd_apdu_data_len;
-  uint16_t offset;
-  int r;
 
   DEBUG_INFO (" - WRITE BINARY\r\n");
+  modify_binary (MBD_OPRATION_WRITE, P1 (apdu), P2 (apdu), len);
+  DEBUG_INFO ("WRITE BINARY done.\r\n");
+}
 
-  if (!ac_check_status (AC_ADMIN_AUTHORIZED))
+
+static void
+cmd_external_authenticate (void)
+{
+  const uint8_t *pubkey;
+  const uint8_t *signature = apdu.cmd_apdu_data;
+  int len = apdu.cmd_apdu_data_len;
+  uint8_t keyno = P2 (apdu);
+  int r;
+
+  DEBUG_INFO (" - EXTERNAL AUTHENTICATE\r\n");
+
+  if (keyno >= 4)
+    {
+      GPG_CONDITION_NOT_SATISFIED ();
+      return;
+    }
+
+  pubkey = gpg_get_firmware_update_key (keyno);
+  if (len != 256
+      || (pubkey[0] == 0xff && pubkey[1] == 0xff) /* not registered */
+      || (pubkey[0] == 0x00 && pubkey[1] == 0x00) /* removed */)
+    {
+      GPG_CONDITION_NOT_SATISFIED ();
+      return;
+    }
+
+  r = rsa_verify (pubkey, FIRMWARE_UPDATE_KEY_CONTENT_LEN,
+                 challenge, signature);
+  random_bytes_free (challenge);
+  challenge = NULL;
+
+  if (r < 0)
     {
-      DEBUG_INFO ("security error.");
       GPG_SECURITY_FAILURE ();
       return;
     }
 
-  if ((P1 (apdu) & 0x80))
-    if ((P1 (apdu) & 0x7f) <= FILEID_SERIAL_NO)
-      {
-       file_selection = FILE_EF_CH_CERTIFICATE + (P1 (apdu) & 0x7f);
-       offset = 0;
-      }
-    else
-      {
-       GPG_NO_FILE ();
-       return;
-      }
-  else
+  eventflag_signal (openpgp_comm, EV_EXIT); /* signal to self.  */
+  set_res_sw (0xff, 0xff);
+  DEBUG_INFO ("EXTERNAL AUTHENTICATE done.\r\n");
+}
+
+static void
+cmd_get_challenge (void)
+{
+  int len = apdu.expected_res_size;
+
+  DEBUG_INFO (" - GET CHALLENGE\r\n");
+
+  if (len > CHALLENGE_LEN)
     {
-      if (file_selection != FILE_EF_CH_CERTIFICATE
-         && file_selection != FILE_EF_RANDOM
-         && file_selection != FILE_EF_SERIAL)
-       {
-         GPG_COMMAND_NOT_ALLOWED ();
-         return;
-       }
+      GPG_CONDITION_NOT_SATISFIED ();
+      return;
+    }
+  else if (len == 0)
+    /* Le is not specified.  Return full-sized challenge by GET_RESPONSE.  */
+    len = CHALLENGE_LEN;
+
+  if (challenge)
+    random_bytes_free (challenge);
 
-      offset = (P1 (apdu) << 8) | P2 (apdu);
+  challenge = random_bytes_get ();
+  memcpy (res_APDU, challenge, len);
+  res_APDU_size = len;
+  GPG_SUCCESS ();
+  DEBUG_INFO ("GET CHALLENGE done.\r\n");
+}
+
+
+#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT
+static void
+cmd_activate_file (void)
+{
+  if (file_selection != FILE_CARD_TERMINATED_OPENPGP)
+    {
+      GPG_NO_RECORD();
+      return;
     }
 
-  DEBUG_SHORT (len);
-  DEBUG_SHORT (offset);
+  flash_activate ();
+  file_selection = FILE_DF_OPENPGP;
+  GPG_SUCCESS ();
+}
 
-  r = flash_write_binary (file_selection - FILE_EF_CH_CERTIFICATE,
-                         apdu.cmd_apdu_data, len, offset);
-  if (r < 0)
+static void
+cmd_terminate_df (void)
+{
+  uint8_t p1 = P1 (apdu);
+  uint8_t p2 = P2 (apdu);
+
+  if (file_selection != FILE_DF_OPENPGP)
     {
-      DEBUG_INFO ("memory error.\r\n");
-      GPG_MEMORY_FAILURE ();
+      GPG_NO_RECORD();
+      return;
+    }
+
+  if (p1 != 0 || p2 != 0)
+    {
+      GPG_BAD_P1_P2();
+      return;
+    }
+
+  if (apdu.cmd_apdu_data_len != 0)
+    {
+      GPG_WRONG_LENGTH();
       return;
     }
 
+
+  if (!ac_check_status (AC_ADMIN_AUTHORIZED) && !gpg_pw_locked (PW_ERR_PW3))
+    {
+      /* Only allow the case admin authorized, or, admin pass is locked.  */
+      GPG_SECURITY_FAILURE();
+      return;
+    }
+
+  ac_reset_admin ();
+  ac_reset_pso_cds ();
+  ac_reset_other ();
+  gpg_do_terminate ();
+  flash_terminate ();
+  file_selection = FILE_CARD_TERMINATED;
   GPG_SUCCESS ();
-  DEBUG_INFO ("WRITE BINARY done.\r\n");
 }
+#endif
 
 
 struct command
@@ -833,15 +1388,26 @@ const struct command cmds[] = {
   { INS_CHANGE_REFERENCE_DATA, cmd_change_password },
   { INS_PSO, cmd_pso },
   { INS_RESET_RETRY_COUNTER, cmd_reset_user_password },
+#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT
+  { INS_ACTIVATE_FILE, cmd_activate_file },
+#endif
   { INS_PGP_GENERATE_ASYMMETRIC_KEY_PAIR, cmd_pgp_gakp },
+  { INS_EXTERNAL_AUTHENTICATE,             /* Not in OpenPGP card protocol */
+    cmd_external_authenticate },
+  { INS_GET_CHALLENGE, cmd_get_challenge }, /* Not in OpenPGP card protocol */
   { INS_INTERNAL_AUTHENTICATE, cmd_internal_authenticate },
   { INS_SELECT_FILE, cmd_select_file },
-  { INS_READ_BINARY, cmd_read_binary },
+  { INS_READ_BINARY, cmd_read_binary },     /* Not in OpenPGP card protocol */
   { INS_GET_DATA, cmd_get_data },
-  { INS_WRITE_BINARY, cmd_write_binary}, /* Not in OpenPGP card protocol */
+  { INS_WRITE_BINARY, cmd_write_binary},    /* Not in OpenPGP card protocol */
+#if defined(CERTDO_SUPPORT)
   { INS_UPDATE_BINARY, cmd_update_binary }, /* Not in OpenPGP card protocol */
+#endif
   { INS_PUT_DATA, cmd_put_data },
   { INS_PUT_DATA_ODD, cmd_put_data },
+#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT
+  { INS_TERMINATE_DF, cmd_terminate_df},
+#endif
 };
 #define NUM_CMDS ((int)(sizeof (cmds) / sizeof (struct command)))
 
@@ -856,7 +1422,24 @@ process_command_apdu (void)
       break;
 
   if (i < NUM_CMDS)
-    cmds[i].cmd_handler ();
+    {
+      if (file_selection == FILE_CARD_TERMINATED
+         && cmd != INS_SELECT_FILE && cmd != INS_ACTIVATE_FILE
+         && cmd != INS_GET_CHALLENGE && cmd != INS_EXTERNAL_AUTHENTICATE)
+       GPG_APPLICATION_TERMINATED();
+      else if (file_selection != FILE_DF_OPENPGP
+              && cmd != INS_SELECT_FILE && cmd != INS_ACTIVATE_FILE
+              && cmd != INS_GET_CHALLENGE && cmd != INS_EXTERNAL_AUTHENTICATE
+              && cmd != INS_WRITE_BINARY && cmd != INS_UPDATE_BINARY
+              && cmd != INS_READ_BINARY)
+       GPG_NO_RECORD();
+      else
+       {
+         chopstx_setcancelstate (1);
+         cmds[i].cmd_handler ();
+         chopstx_setcancelstate (0);
+       }
+    }
   else
     {
       DEBUG_INFO (" - ??");
@@ -865,21 +1448,21 @@ process_command_apdu (void)
     }
 }
 
-msg_t
-GPGthread (void *arg)
+void *
+openpgp_card_thread (void *arg)
 {
-  Thread *icc_thread = (Thread *)arg;
+  struct eventflag *ccid_comm = (struct eventflag *)arg;
 
-  gpg_init ();
+  openpgp_comm = ccid_comm + 1;
 
-  chEvtClear (ALL_EVENTS);
+  gpg_init ();
 
-  while (!chThdShouldTerminate ())
+  while (1)
     {
-      eventmask_t m = chEvtWaitOne (ALL_EVENTS);
 #if defined(PINPAD_SUPPORT)
       int len, pw_len, newpw_len;
 #endif
+      eventmask_t m = eventflag_wait (openpgp_comm);
 
       DEBUG_INFO ("GPG!: ");
 
@@ -908,7 +1491,7 @@ GPGthread (void *arg)
       else if (m == EV_MODIFY_CMD_AVAILABLE)
        {
 #if defined(PINPAD_SUPPORT)
-         uint8_t bConfirmPIN = apdu.cmd_apdu_data[5];
+         uint8_t bConfirmPIN = apdu.cmd_apdu_data[0];
          uint8_t *p = apdu.cmd_apdu_data;
 
          if (INS (apdu) != INS_CHANGE_REFERENCE_DATA
@@ -963,14 +1546,16 @@ GPGthread (void *arg)
          goto done;
 #endif
        }
-      else if (m == EV_NOP)
-       continue;
+      else if (m == EV_EXIT)
+       break;
 
+      led_blink (LED_START_COMMAND);
       process_command_apdu ();
+      led_blink (LED_FINISH_COMMAND);
     done:
-      chEvtSignal (icc_thread, EV_EXEC_FINISHED);
+      eventflag_signal (ccid_comm, EV_EXEC_FINISHED);
     }
 
   gpg_fini ();
-  return 0;
+  return NULL;
 }