docker: source checking container
[gnuk/gnuk.git] / src / openpgp.c
index 7888e80..5b788b0 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * openpgp.c -- OpenPGP card protocol support
  *
- * Copyright (C) 2010, 2011, 2012 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 "sys.h"
-#include "openpgp.h"
+#include "status-code.h"
 #include "sha256.h"
+#include "random.h"
+
+static struct eventflag *openpgp_comm;
 
 #define ADMIN_PASSWD_MINLEN 8
 
@@ -40,6 +48,7 @@
 #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
@@ -51,8 +60,8 @@
 #define INS_UPDATE_BINARY                      0xd6
 #define INS_PUT_DATA                           0xda
 #define INS_PUT_DATA_ODD                       0xdb    /* For key import */
+#define INS_TERMINATE_DF                       0xe6
 
-#define CHALLENGE_LEN  32
 static const uint8_t *challenge; /* Random bytes */
 
 static const uint8_t
@@ -89,17 +98,26 @@ set_res_sw (uint8_t sw1, uint8_t sw2)
 #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;
 
-  file_selection = FILE_NONE;
-  flash_data_start = flash_init ();
-  gpg_data_scan (flash_data_start);
+  flash_init (&flash_do_start, &flash_do_end);
+
+  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
@@ -120,7 +138,7 @@ get_pinpad_input (int msg_code)
   int r;
 
   led_blink (LED_START_COMMAND);
-  r = pinpad_getline (msg_code, MS2ST (8000));
+  r = pinpad_getline (msg_code, 8000000);
   led_blink (LED_FINISH_COMMAND);
   return r;
 }
@@ -130,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;
@@ -140,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)
@@ -217,8 +271,10 @@ 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;
@@ -227,6 +283,10 @@ cmd_change_password (void)
   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);
@@ -244,8 +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)
        {
@@ -261,10 +332,9 @@ cmd_change_password (void)
        }
       else
        {
-         const uint8_t *ks_pw3 = gpg_do_read_simple (NR_DO_KEYSTRING_PW3);
-
          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)
@@ -277,7 +347,19 @@ cmd_change_password (void)
     }
   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)
        {
@@ -299,16 +381,18 @@ cmd_change_password (void)
            {
              newpw_len = strlen (OPENPGP_CARD_INITIAL_PW3);
              memcpy (newpw, OPENPGP_CARD_INITIAL_PW3, newpw_len);
-             gpg_do_write_simple (NR_DO_KEYSTRING_PW3, NULL, 0);
+             newsalt_len = 0;
+             pw3_null = 1;
            }
-         else
-           gpg_set_pw3 (newpw, newpw_len);
+
          who_old = admin_authorized;
        }
     }
 
-  s2k (who_old, pw, pw_len, old_ks);
-  s2k (who, 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, old_ks, who, new_ks);
@@ -324,17 +408,12 @@ 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 ();
-      if (admin_authorized == BY_USER)
-       ac_reset_admin ();
-      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)
@@ -342,33 +421,73 @@ cmd_change_password (void)
       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)
+    {
+      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 */
     {
-      DEBUG_INFO ("done.\r\n");
+      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 ();
     }
 }
 
 
-#define USER_S2K_MAGIC "\xffUSER\r\n"
-#define RESETCODE_S2K_MAGIC "\xffRESET\r\n"
-
+#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 (int who, const unsigned char *input, unsigned int ilen,
-     unsigned char output[32])
+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, input, ilen);
-  if (who == BY_USER)
-    sha256_update (&ctx, (unsigned char *)USER_S2K_MAGIC,
-                  sizeof (USER_S2K_MAGIC));
-  else if (who == BY_RESETCODE)
-    sha256_update (&ctx, (unsigned char *)RESETCODE_S2K_MAGIC,
-                  sizeof (RESETCODE_S2K_MAGIC));
-  /* Not add any for BY_ADMIN */
+
+  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);
 }
 
@@ -382,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);
@@ -410,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;
-      s2k (BY_RESETCODE, pw, pw_len, old_ks);
-      s2k (BY_USER, 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)
@@ -424,30 +549,19 @@ 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 ();
-         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 ();
+         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, 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)
@@ -470,7 +584,8 @@ cmd_reset_user_password (void)
 
       newpw_len = len;
       newpw = pw;
-      s2k (BY_USER, 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)
@@ -485,20 +600,13 @@ 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 ();
-         if (admin_authorized == BY_USER)
-           ac_reset_admin ();
-         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, 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)
@@ -518,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;
@@ -540,12 +645,7 @@ cmd_pgp_gakp (void)
     {
       if (!ac_check_status (AC_ADMIN_AUTHORIZED))
        GPG_SECURITY_FAILURE ();
-#ifdef KEYGEN_SUPPORT
-      /* Generate key pair */
       gpg_do_keygen (apdu.cmd_apdu_data[0]);
-#else
-      GPG_FUNCTION_NOT_SUPPORTED ();
-#endif
     }
 }
 
@@ -555,7 +655,7 @@ gpg_get_firmware_update_key (uint8_t keyno)
   extern uint8_t _updatekey_store;
   const uint8_t *p;
 
-  p = &_updatekey_store + keyno * KEY_CONTENT_LEN;
+  p = &_updatekey_store + keyno * FIRMWARE_UPDATE_KEY_CONTENT_LEN;
   return p;
 }
 
@@ -614,8 +714,8 @@ cmd_read_binary (void)
       else
        {
          p = gpg_get_firmware_update_key (file_id - FILEID_UPDATE_KEY_0);
-         res_APDU_size = KEY_CONTENT_LEN;
-         memcpy (res_APDU, p, KEY_CONTENT_LEN);
+         res_APDU_size = FIRMWARE_UPDATE_KEY_CONTENT_LEN;
+         memcpy (res_APDU, p, FIRMWARE_UPDATE_KEY_CONTENT_LEN);
          GPG_SUCCESS ();
        }
     }
@@ -658,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)
@@ -720,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.");
@@ -745,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))
        {
@@ -786,19 +948,68 @@ cmd_pso (void)
          return;
        }
 
-      /* Skip padding 0x00 */
-      len--;
-      if (len != KEY_CONTENT_LEN)
-       GPG_CONDITION_NOT_SATISFIED ();
-      else
+      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]);
-         if (r < 0)
-           GPG_ERROR ();
+                          &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));
@@ -811,50 +1022,111 @@ cmd_pso (void)
 }
 
 
-#define MAX_DIGEST_INFO_LEN 102 /* 40% */
+#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;
        }
 
-      if (len > MAX_DIGEST_INFO_LEN)
+      result_len = pubkey_len;
+      r = rsa_sign (apdu.cmd_apdu_data, res_APDU, len,
+                   &kd[GPG_KEY_FOR_AUTHENTICATION], pubkey_len);
+    }
+  else if (attr == ALGO_NISTP256R1)
+    {
+      if (len != ECDSA_HASH_LEN)
        {
-         DEBUG_INFO ("input is too long.");
+         DEBUG_INFO ("wrong hash length.");
          GPG_CONDITION_NOT_SATISFIED ();
          return;
        }
 
-      r = rsa_sign (apdu.cmd_apdu_data, res_APDU, len,
-                   &kd[GPG_KEY_FOR_AUTHENTICATION]);
-      if (r < 0)
-       GPG_ERROR ();
+      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
+  else if (attr == ALGO_SECP256K1)
     {
-      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_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
 
@@ -918,6 +1190,10 @@ modify_binary (uint8_t op, uint8_t p1, uint8_t p2, int len)
   DEBUG_SHORT (len);
   DEBUG_SHORT (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)
     {
@@ -926,6 +1202,26 @@ modify_binary (uint8_t op, uint8_t p1, uint8_t p2, int len)
       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 ();
 }
 
@@ -947,25 +1243,9 @@ static void
 cmd_write_binary (void)
 {
   int len = apdu.cmd_apdu_data_len;
-  int i;
-  const uint8_t *p;
 
   DEBUG_INFO (" - WRITE BINARY\r\n");
   modify_binary (MBD_OPRATION_WRITE, P1 (apdu), P2 (apdu), len);
-
-  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);
-    }
-
   DEBUG_INFO ("WRITE BINARY done.\r\n");
 }
 
@@ -981,7 +1261,7 @@ cmd_external_authenticate (void)
 
   DEBUG_INFO (" - EXTERNAL AUTHENTICATE\r\n");
 
-  if (keyno > 4)
+  if (keyno >= 4)
     {
       GPG_CONDITION_NOT_SATISFIED ();
       return;
@@ -996,7 +1276,8 @@ cmd_external_authenticate (void)
       return;
     }
 
-  r = rsa_verify (pubkey, challenge, signature);
+  r = rsa_verify (pubkey, FIRMWARE_UPDATE_KEY_CONTENT_LEN,
+                 challenge, signature);
   random_bytes_free (challenge);
   challenge = NULL;
 
@@ -1006,7 +1287,7 @@ cmd_external_authenticate (void)
       return;
     }
 
-  chThdTerminate (chThdSelf ());
+  eventflag_signal (openpgp_comm, EV_EXIT); /* signal to self.  */
   set_res_sw (0xff, 0xff);
   DEBUG_INFO ("EXTERNAL AUTHENTICATE done.\r\n");
 }
@@ -1014,19 +1295,88 @@ cmd_external_authenticate (void)
 static void
 cmd_get_challenge (void)
 {
+  int len = apdu.expected_res_size;
+
   DEBUG_INFO (" - GET CHALLENGE\r\n");
 
+  if (len > CHALLENGE_LEN)
+    {
+      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);
 
   challenge = random_bytes_get ();
-  memcpy (res_APDU, challenge, CHALLENGE_LEN);
-  res_APDU_size = CHALLENGE_LEN;
+  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;
+    }
+
+  flash_activate ();
+  file_selection = FILE_DF_OPENPGP;
+  GPG_SUCCESS ();
+}
+
+static void
+cmd_terminate_df (void)
+{
+  uint8_t p1 = P1 (apdu);
+  uint8_t p2 = P2 (apdu);
+
+  if (file_selection != FILE_DF_OPENPGP)
+    {
+      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 ();
+}
+#endif
+
+
 struct command
 {
   uint8_t command;
@@ -1038,13 +1388,16 @@ 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 */
 #if defined(CERTDO_SUPPORT)
@@ -1052,6 +1405,9 @@ const struct command cmds[] = {
 #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)))
 
@@ -1066,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 (" - ??");
@@ -1075,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!: ");
 
@@ -1118,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
@@ -1173,16 +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;
 }