common/iobuf.c: Combine iobuf_open, iobuf_create and iobuf_openrw.
[gnupg.git] / scd / app-openpgp.c
index 673570d..461c710 100644 (file)
@@ -1,6 +1,6 @@
 /* app-openpgp.c - The OpenPGP card application.
  * Copyright (C) 2003, 2004, 2005, 2007, 2008,
- *               2009, 2013 Free Software Foundation, Inc.
+ *               2009, 2013, 2014, 2015 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -45,6 +45,7 @@
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdarg.h>
 #include <string.h>
 #include <assert.h>
 #include <time.h>
 #include "options.h"
 #include "errors.h"
 #include "memory.h"
-#include "util.h"
 #include "cardglue.h"
 #else /* GNUPG_MAJOR_VERSION != 1 */
 #include "scdaemon.h"
 #endif /* GNUPG_MAJOR_VERSION != 1 */
 
+#include "util.h"
 #include "i18n.h"
 #include "iso7816.h"
 #include "app-common.h"
 #include "tlv.h"
+#include "host2net.h"
+#include "openpgpdefs.h"
 
 
 /* A table describing the DOs of the card.  */
@@ -112,6 +115,9 @@ static struct {
   { 0x0103, 0,    0, 0, 0, 0, 0, 0, "Private DO 3"},
   { 0x0104, 0,    0, 0, 0, 0, 0, 0, "Private DO 4"},
   { 0x7F21, 1,    0, 1, 0, 0, 0, 1, "Cardholder certificate"},
+  /* V3.0 */
+  { 0x7F74, 0,    0, 1, 0, 0, 0, 0, "General Feature Management"},
+  { 0x00D5, 0,    0, 1, 0, 0, 0, 0, "AES key data"},
   { 0 }
 };
 
@@ -119,8 +125,7 @@ static struct {
 /* Type of keys.  */
 typedef enum
   {
-    KEY_TYPE_ECDH,
-    KEY_TYPE_ECDSA,
+    KEY_TYPE_ECC,
     KEY_TYPE_RSA,
   }
 key_type_t;
@@ -138,15 +143,6 @@ typedef enum
 rsa_key_format_t;
 
 
-/* Elliptic Curves.  */
-enum
-  {
-    CURVE_NIST_P256,
-    CURVE_NIST_P384,
-    CURVE_NIST_P521
-  };
-
-
 /* One cache item for DOs.  */
 struct cache_s {
   struct cache_s *next;
@@ -190,13 +186,15 @@ struct app_local_s {
   struct
   {
     unsigned int is_v2:1;              /* This is a v2.0 compatible card.  */
+    unsigned int sm_supported:1;       /* Secure Messaging is supported.  */
     unsigned int get_challenge:1;
     unsigned int key_import:1;
     unsigned int change_force_chv:1;
     unsigned int private_dos:1;
     unsigned int algo_attr_change:1;   /* Algorithm attributes changeable.  */
-    unsigned int sm_supported:1;       /* Secure Messaging is supported.  */
-    unsigned int sm_aes128:1;          /* Use AES-128 for SM.  */
+    unsigned int has_decrypt:1;        /* Support symmetric decryption.  */
+    unsigned int has_button:1;
+    unsigned int sm_algo:2;            /* Symmetric crypto algo for SM.  */
     unsigned int max_certlen_3:16;
     unsigned int max_get_challenge:16; /* Maximum size for get_challenge.  */
     unsigned int max_cmd_data:16;      /* Maximum data size for a command.  */
@@ -230,17 +228,14 @@ struct app_local_s {
         rsa_key_format_t format;
       } rsa;
       struct {
-        int curve;
-      } ecdsa;
-      struct {
-        int curve;
-        int hashalgo;
-        int cipheralgo;
-      } ecdh;
+        const char *oid;
+        int flags;
+      } ecc;
     };
    } keyattr[3];
 };
 
+#define ECC_FLAG_DJB_TWEAK (1 << 0)
 
 
 /***** Local prototypes  *****/
@@ -660,7 +655,11 @@ parse_login_data (app_t app)
     if (*buffer == '\n')
       break;
   if (buflen < 2 || buffer[1] != '\x14')
-    return; /* No control sequences.  */
+    {
+      xfree (relptr);
+      return; /* No control sequences.  */
+    }
+
   buflen--;
   buffer++;
   do
@@ -707,14 +706,11 @@ parse_login_data (app_t app)
                       m = strtol (q, &q, 10);
                     }
 
-                  buffer = q;
                   if (buflen < ((unsigned char *)q - buffer))
-                    {
-                      buflen = 0;
-                      break;
-                    }
-                  else
-                    buflen -= ((unsigned char *)q - buffer);
+                    break;
+
+                  buflen -= ((unsigned char *)q - buffer);
+                  buffer = q;
 
                   if (buflen && !(*buffer == '\n' || *buffer == '\x18'))
                     goto next;
@@ -725,33 +721,50 @@ parse_login_data (app_t app)
             }
         }
     next:
-      for (; buflen && *buffer != '\x18'; buflen--, buffer++)
-        if (*buffer == '\n')
-          buflen = 1;
+      /* Skip to FS (0x18) or LF (\n).  */
+      for (; buflen && *buffer != '\x18' && *buffer != '\n'; buflen--)
+        buffer++;
     }
-  while (buflen);
+  while (buflen && *buffer != '\n');
 
   xfree (relptr);
 }
 
+
+#define MAX_ARGS_STORE_FPR 3
+
 /* Note, that FPR must be at least 20 bytes. */
 static gpg_error_t
-store_fpr (app_t app, int keynumber, u32 timestamp,
-           const unsigned char *m, size_t mlen,
-           const unsigned char *e, size_t elen,
-           unsigned char *fpr, unsigned int card_version)
+store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr,
+           int algo, ...)
 {
   unsigned int n, nbits;
   unsigned char *buffer, *p;
   int tag, tag2;
   int rc;
+  const unsigned char *m[MAX_ARGS_STORE_FPR];
+  size_t mlen[MAX_ARGS_STORE_FPR];
+  va_list ap;
+  int argc;
+  int i;
 
-  for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
-    ;
-  for (; elen && !*e; elen--, e++) /* strip leading zeroes */
-    ;
+  n = 6;    /* key packet version, 4-byte timestamps, and algorithm */
+  if (algo == PUBKEY_ALGO_ECDH)
+    argc = 3;
+  else
+    argc = 2;
+
+  va_start (ap, algo);
+  for (i = 0; i < argc; i++)
+    {
+      m[i] = va_arg (ap, const unsigned char *);
+      mlen[i] = va_arg (ap, size_t);
+      if (algo == PUBKEY_ALGO_RSA || i == 1)
+        n += 2;
+      n += mlen[i];
+    }
+  va_end (ap);
 
-  n = 6 + 2 + mlen + 2 + elen;
   p = buffer = xtrymalloc (3 + n);
   if (!buffer)
     return gpg_error_from_syserror ();
@@ -764,21 +777,25 @@ store_fpr (app_t app, int keynumber, u32 timestamp,
   *p++ = timestamp >> 16;
   *p++ = timestamp >>  8;
   *p++ = timestamp;
-  *p++ = 1; /* RSA */
-  nbits = count_bits (m, mlen);
-  *p++ = nbits >> 8;
-  *p++ = nbits;
-  memcpy (p, m, mlen); p += mlen;
-  nbits = count_bits (e, elen);
-  *p++ = nbits >> 8;
-  *p++ = nbits;
-  memcpy (p, e, elen); p += elen;
+  *p++ = algo;
+
+  for (i = 0; i < argc; i++)
+    {
+      if (algo == PUBKEY_ALGO_RSA || i == 1)
+        {
+          nbits = count_bits (m[i], mlen[i]);
+          *p++ = nbits >> 8;
+          *p++ = nbits;
+        }
+      memcpy (p, m[i], mlen[i]);
+      p += mlen[i];
+    }
 
   gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3);
 
   xfree (buffer);
 
-  tag = (card_version > 0x0007? 0xC7 : 0xC6) + keynumber;
+  tag = (app->card_version > 0x0007? 0xC7 : 0xC6) + keynumber;
   flush_cache_item (app, 0xC5);
   tag2 = 0xCE + keynumber;
   flush_cache_item (app, 0xCD);
@@ -787,7 +804,7 @@ store_fpr (app_t app, int keynumber, u32 timestamp,
   if (rc)
     log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc));
 
-  if (!rc && card_version > 0x0100)
+  if (!rc && app->card_version > 0x0100)
     {
       unsigned char buf[4];
 
@@ -835,7 +852,7 @@ send_fprtime_if_not_null (ctrl_t ctrl, const char *keyword,
   char numbuf1[50], numbuf2[50];
   unsigned long value;
 
-  value = (stamp[0] << 24) | (stamp[1]<<16) | (stamp[2]<<8) | stamp[3];
+  value = buf32_to_ulong (stamp);
   if (!value)
     return;
   sprintf (numbuf1, "%d", number);
@@ -876,58 +893,29 @@ send_key_data (ctrl_t ctrl, const char *name,
 
 
 static void
-get_ecc_key_parameters (int curve, int *r_n_bits, const char **r_curve_oid)
-{
-  if (curve == CURVE_NIST_P256)
-    {
-      *r_n_bits = 256;
-      *r_curve_oid = "1.2.840.10045.3.1.7";
-    }
-  else if (curve == CURVE_NIST_P384)
-    {
-      *r_n_bits = 384;
-      *r_curve_oid = "1.3.132.0.34";
-    }
-  else
-    {
-      *r_n_bits = 521;
-      *r_curve_oid = "1.3.132.0.35";
-    }
-}
-
-static void
-send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int number)
+send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int keyno)
 {
   char buffer[200];
-  int n_bits;
-  const char *curve_oid;
 
-  assert (number >=0 && number < DIM(app->app_local->keyattr));
+  assert (keyno >=0 && keyno < DIM(app->app_local->keyattr));
 
-  if (app->app_local->keyattr[number].key_type == KEY_TYPE_RSA)
-    snprintf (buffer, sizeof buffer, "%d 1 %u %u %d",
-              number+1,
-              app->app_local->keyattr[number].rsa.n_bits,
-              app->app_local->keyattr[number].rsa.e_bits,
-              app->app_local->keyattr[number].rsa.format);
-  else if (app->app_local->keyattr[number].key_type == KEY_TYPE_ECDSA)
-    {
-      get_ecc_key_parameters (app->app_local->keyattr[number].ecdsa.curve,
-                              &n_bits, &curve_oid);
-      snprintf (buffer, sizeof buffer, "%d 19 %u %s",
-                number+1, n_bits, curve_oid);
-    }
-  else if (app->app_local->keyattr[number].key_type == KEY_TYPE_ECDH)
+  if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
+    snprintf (buffer, sizeof buffer, "%d 1 rsa%u %u %d",
+              keyno+1,
+              app->app_local->keyattr[keyno].rsa.n_bits,
+              app->app_local->keyattr[keyno].rsa.e_bits,
+              app->app_local->keyattr[keyno].rsa.format);
+  else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
     {
-      get_ecc_key_parameters (app->app_local->keyattr[number].ecdh.curve,
-                              &n_bits, &curve_oid);
-      snprintf (buffer, sizeof buffer, "%d 18 %u %s %d %d",
-                number+1, n_bits, curve_oid,
-                app->app_local->keyattr[number].ecdh.hashalgo,
-                app->app_local->keyattr[number].ecdh.cipheralgo);
+      snprintf (buffer, sizeof buffer, "%d %d %s",
+                keyno+1,
+                keyno==1? PUBKEY_ALGO_ECDH :
+                app->app_local->keyattr[keyno].ecc.flags?
+                PUBKEY_ALGO_EDDSA : PUBKEY_ALGO_ECDSA,
+                openpgp_oid_to_curve (app->app_local->keyattr[keyno].ecc.oid, 0));
     }
   else
-    snprintf (buffer, sizeof buffer, "0 0 UNKNOWN");
+    snprintf (buffer, sizeof buffer, "%d 0 0 UNKNOWN", keyno+1);
 
   send_status_direct (ctrl, keyword, buffer);
 }
@@ -999,10 +987,11 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
     }
   if (table[idx].special == -2)
     {
-      char tmp[100];
+      char tmp[110];
 
       snprintf (tmp, sizeof tmp,
-                "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d sm=%d",
+                "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d "
+                "sm=%d si=%u dec=%d bt=%d",
                 app->app_local->extcap.get_challenge,
                 app->app_local->extcap.key_import,
                 app->app_local->extcap.change_force_chv,
@@ -1010,8 +999,13 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
                 app->app_local->extcap.max_certlen_3,
                 app->app_local->extcap.algo_attr_change,
                 (app->app_local->extcap.sm_supported
-                 ? (app->app_local->extcap.sm_aes128? 7 : 2)
-                 : 0));
+                 ? (app->app_local->extcap.sm_algo == 0? CIPHER_ALGO_3DES :
+                    (app->app_local->extcap.sm_algo == 1?
+                     CIPHER_ALGO_AES : CIPHER_ALGO_AES256))
+                 : 0),
+                app->app_local->status_indicator,
+                app->app_local->extcap.has_decrypt,
+                app->app_local->extcap.has_button);
       send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
       return 0;
     }
@@ -1140,7 +1134,7 @@ retrieve_key_material (FILE *fp, const char *hexkeyid,
   for (;;)
     {
       char *p;
-      char *fields[6];
+      char *fields[6] = { NULL, NULL, NULL, NULL, NULL, NULL };
       int nfields;
       size_t max_length;
       gcry_mpi_t mpi;
@@ -1226,18 +1220,6 @@ retrieve_key_material (FILE *fp, const char *hexkeyid,
 #endif /*GNUPG_MAJOR_VERSION > 1*/
 
 
-static const char *
-get_curve_name (int curve)
-{
-  if (curve == CURVE_NIST_P256)
-    return "NIST P-256";
-  else if (curve == CURVE_NIST_P384)
-    return "NIST P-384";
-  else
-    return "NIST P-521";
-}
-
-
 /* Get the public key for KEYNO and store it as an S-expresion with
    the APP handle.  On error that field gets cleared.  If we already
    know about the public key we will just return.  Note that this does
@@ -1370,9 +1352,8 @@ get_public_key (app_t app, int keyno)
        }
       hexkeyid = fpr + 24;
 
-      ret = estream_asprintf (&command,
-                             "gpg --list-keys --with-colons --with-key-data '%s'",
-                             fpr);
+      ret = gpgrt_asprintf
+        (&command, "gpg --list-keys --with-colons --with-key-data '%s'", fpr);
       if (ret < 0)
        {
          err = gpg_error_from_syserror ();
@@ -1389,7 +1370,7 @@ get_public_key (app_t app, int keyno)
        }
 
       err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen);
-      fclose (fp);
+      pclose (fp);
       if (err)
        {
          log_error ("error while retrieving key material through pipe: %s\n",
@@ -1398,85 +1379,85 @@ get_public_key (app_t app, int keyno)
        }
     }
 
-
-  mbuf = xtrymalloc ( mlen + 1);
+  mbuf = xtrymalloc (mlen + 1);
   if (!mbuf)
     {
       err = gpg_error_from_syserror ();
       goto leave;
     }
-  /* Prepend numbers with a 0 if needed.  */
-  if (mlen && (*m & 0x80))
-    {
+
+  if ((app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA
+       || (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC
+           && !app->app_local->keyattr[keyno].ecc.flags))
+      && mlen && (*m & 0x80))
+    {               /* Prepend numbers with a 0 if needed for MPI.  */
       *mbuf = 0;
       memcpy (mbuf+1, m, mlen);
       mlen++;
     }
-  else
-    memcpy (mbuf, m, mlen);
-
-  ebuf = xtrymalloc ( elen + 1);
-  if (!ebuf)
-    {
-      err = gpg_error_from_syserror ();
-      goto leave;
-    }
-  /* Prepend numbers with a 0 if needed.  */
-  if (elen && (*e & 0x80))
-    {
-      *ebuf = 0;
-      memcpy (ebuf+1, e, elen);
-      elen++;
+  else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC
+           && app->app_local->keyattr[keyno].ecc.flags)
+    {               /* Prepend 0x40 prefix.  */
+      *mbuf = 0x40;
+      memcpy (mbuf+1, m, mlen);
+      mlen++;
     }
   else
-    memcpy (ebuf, e, elen);
+    memcpy (mbuf, m, mlen);
 
   if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
     {
-      err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))",
-                             mlen, mbuf, elen, ebuf);
-      if (err)
-        goto leave;
-
-      len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
-      keybuf = xtrymalloc (len);
-      if (!keybuf)
+      ebuf = xtrymalloc (elen + 1);
+      if (!ebuf)
         {
-          gcry_sexp_release (s_pkey);
           err = gpg_error_from_syserror ();
           goto leave;
         }
-      gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
-      gcry_sexp_release (s_pkey);
+      /* Prepend numbers with a 0 if needed.  */
+      if (elen && (*e & 0x80))
+        {
+          *ebuf = 0;
+          memcpy (ebuf+1, e, elen);
+          elen++;
+        }
+      else
+        memcpy (ebuf, e, elen);
+
+      err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))",
+                             (int)mlen, mbuf, (int)elen, ebuf);
     }
-  else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECDSA)
+  else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
     {
-      const char *curve_name
-        = get_curve_name (app->app_local->keyattr[keyno].ecdsa.curve);
-
-      err = gcry_sexp_build (&s_pkey, NULL,
-                             "(public-key(ecdsa(curve%s)(q%b)))",
-                             curve_name, mlen, mbuf);
-      if (err)
-        goto leave;
+      char *format;
 
-      len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
+      if (!app->app_local->keyattr[keyno].ecc.flags)
+        format = "(public-key(ecc(curve%s)(q%b)))";
+      else if (keyno == 1)
+        format = "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))";
+      else
+        format = "(public-key(ecc(curve%s)(flags eddsa)(q%b)))";
 
-      keybuf = xtrymalloc (len);
-      if (!keybuf)
-        {
-          gcry_sexp_release (s_pkey);
-          err = gpg_error_from_syserror ();
-          goto leave;
-        }
-      gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
-      gcry_sexp_release (s_pkey);
+      err = gcry_sexp_build (&s_pkey, NULL, format,
+                openpgp_oid_to_curve (app->app_local->keyattr[keyno].ecc.oid, 1),
+                             (int)mlen, mbuf);
     }
   else
+    err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+  if (err)
+    goto leave;
+
+  len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
+
+  keybuf = xtrymalloc (len);
+  if (!keybuf)
     {
-      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      gcry_sexp_release (s_pkey);
+      err = gpg_error_from_syserror ();
       goto leave;
     }
+  gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
+  gcry_sexp_release (s_pkey);
 
   app->app_local->pk[keyno].key = (unsigned char*)keybuf;
   app->app_local->pk[keyno].keylen = len - 1; /* Decrement for trailing '\0' */
@@ -2034,6 +2015,7 @@ do_setattr (app_t app, const char *name,
     { "SM-KEY-ENC",   0x00D1, 3, 0, 1 },
     { "SM-KEY-MAC",   0x00D2, 3, 0, 1 },
     { "KEY-ATTR",     0,      0, 3, 1 },
+    { "AESKEY",       0x00D5, 3, 0, 1 },
     { NULL, 0 }
   };
   int exmode;
@@ -2499,18 +2481,19 @@ add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
 }
 
 
-/* Build the private key template as specified in the OpenPGP specs
-   v2.0 section 4.3.3.7.  */
 static gpg_error_t
 build_privkey_template (app_t app, int keyno,
                         const unsigned char *rsa_n, size_t rsa_n_len,
                         const unsigned char *rsa_e, size_t rsa_e_len,
                         const unsigned char *rsa_p, size_t rsa_p_len,
                         const unsigned char *rsa_q, size_t rsa_q_len,
+                        const unsigned char *rsa_u, size_t rsa_u_len,
+                        const unsigned char *rsa_dp, size_t rsa_dp_len,
+                        const unsigned char *rsa_dq, size_t rsa_dq_len,
                         unsigned char **result, size_t *resultlen)
 {
   size_t rsa_e_reqlen;
-  unsigned char privkey[7*(1+3)];
+  unsigned char privkey[7*(1+3+3)];
   size_t privkey_len;
   unsigned char exthdr[2+2+3];
   size_t exthdr_len;
@@ -2528,17 +2511,16 @@ build_privkey_template (app_t app, int keyno,
     {
     case RSA_STD:
     case RSA_STD_N:
-      break;
     case RSA_CRT:
     case RSA_CRT_N:
-      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+      break;
 
     default:
       return gpg_error (GPG_ERR_INV_VALUE);
     }
 
-  /* Get the required length for E.  */
-  rsa_e_reqlen = app->app_local->keyattr[keyno].rsa.e_bits/8;
+  /* Get the required length for E. Rounded up to the nearest byte  */
+  rsa_e_reqlen = (app->app_local->keyattr[keyno].rsa.e_bits + 7) / 8;
   assert (rsa_e_len <= rsa_e_reqlen);
 
   /* Build the 7f48 cardholder private key template.  */
@@ -2554,6 +2536,17 @@ build_privkey_template (app_t app, int keyno,
   tp += add_tlv (tp, 0x93, rsa_q_len);
   datalen += rsa_q_len;
 
+  if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT
+      || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
+    {
+      tp += add_tlv (tp, 0x94, rsa_u_len);
+      datalen += rsa_u_len;
+      tp += add_tlv (tp, 0x95, rsa_dp_len);
+      datalen += rsa_dp_len;
+      tp += add_tlv (tp, 0x96, rsa_dq_len);
+      datalen += rsa_dq_len;
+    }
+
   if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
       || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
     {
@@ -2607,6 +2600,17 @@ build_privkey_template (app_t app, int keyno,
   memcpy (tp, rsa_q, rsa_q_len);
   tp += rsa_q_len;
 
+  if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT
+      || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
+    {
+      memcpy (tp, rsa_u, rsa_u_len);
+      tp += rsa_u_len;
+      memcpy (tp, rsa_dp, rsa_dp_len);
+      tp += rsa_dp_len;
+      memcpy (tp, rsa_dq, rsa_dq_len);
+      tp += rsa_dq_len;
+    }
+
   if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
       || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
     {
@@ -2623,58 +2627,99 @@ build_privkey_template (app_t app, int keyno,
   return 0;
 }
 
+static gpg_error_t
+build_ecc_privkey_template (app_t app, int keyno,
+                            const unsigned char *ecc_d, size_t ecc_d_len,
+                            unsigned char **result, size_t *resultlen)
+{
+  unsigned char privkey[2];
+  size_t privkey_len;
+  unsigned char exthdr[2+2+1];
+  size_t exthdr_len;
+  unsigned char suffix[2+1];
+  size_t suffix_len;
+  unsigned char *tp;
+  size_t datalen;
+  unsigned char *template;
+  size_t template_size;
+
+  (void)app;
+
+  *result = NULL;
+  *resultlen = 0;
+
+  /* Build the 7f48 cardholder private key template.  */
+  datalen = 0;
+  tp = privkey;
+
+  tp += add_tlv (tp, 0x92, ecc_d_len);
+  datalen += ecc_d_len;
+
+  privkey_len = tp - privkey;
+
+  /* Build the extended header list without the private key template.  */
+  tp = exthdr;
+  *tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4;
+  *tp++ = 0;
+  tp += add_tlv (tp, 0x7f48, privkey_len);
+  exthdr_len = tp - exthdr;
+
+  /* Build the 5f48 suffix of the data.  */
+  tp = suffix;
+  tp += add_tlv (tp, 0x5f48, datalen);
+  suffix_len = tp - suffix;
+
+  /* Now concatenate everything.  */
+  template_size = (1 + 1   /* 0x4d and len. */
+                   + exthdr_len
+                   + privkey_len
+                   + suffix_len
+                   + datalen);
+  tp = template = xtrymalloc_secure (template_size);
+  if (!template)
+    return gpg_error_from_syserror ();
+
+  tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen);
+  memcpy (tp, exthdr, exthdr_len);
+  tp += exthdr_len;
+  memcpy (tp, privkey, privkey_len);
+  tp += privkey_len;
+  memcpy (tp, suffix, suffix_len);
+  tp += suffix_len;
+
+  memcpy (tp, ecc_d, ecc_d_len);
+  tp += ecc_d_len;
+
+  assert (tp - template == template_size);
+
+  *result = template;
+  *resultlen = tp - template;
+  return 0;
+}
+
 
 /* Helper for do_writekley to change the size of a key.  Not ethat
    this deletes the entire key without asking.  */
 static gpg_error_t
-change_keyattr (app_t app, int keyno, unsigned int nbits,
+change_keyattr (app_t app, int keyno, const unsigned char *buf, size_t buflen,
                 gpg_error_t (*pincb)(void*, const char *, char **),
                 void *pincb_arg)
 {
   gpg_error_t err;
-  unsigned char *buffer;
-  size_t buflen;
-  void *relptr;
 
   assert (keyno >=0 && keyno <= 2);
 
-  if (nbits > 4096)
-    return gpg_error (GPG_ERR_TOO_LARGE);
-
-  /* Read the current attributes into a buffer.  */
-  relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL);
-  if (!relptr)
-    return gpg_error (GPG_ERR_CARD);
-  if (buflen < 6 || buffer[0] != 1)
-    {
-      /* Attriutes too short or not an RSA key.  */
-      xfree (relptr);
-      return gpg_error (GPG_ERR_CARD);
-    }
-
-  /* We only change n_bits and don't touch anything else.  Before we
-     do so, we round up NBITS to a sensible way in the same way as
-     gpg's key generation does it.  This may help to sort out problems
-     with a few bits too short keys.  */
-  nbits = ((nbits + 31) / 32) * 32;
-  buffer[1] = (nbits >> 8);
-  buffer[2] = nbits;
-
   /* Prepare for storing the key.  */
   err = verify_chv3 (app, pincb, pincb_arg);
   if (err)
-    {
-      xfree (relptr);
-      return err;
-    }
+    return err;
 
   /* Change the attribute.  */
-  err = iso7816_put_data (app->slot, 0, 0xC1+keyno, buffer, buflen);
-  xfree (relptr);
+  err = iso7816_put_data (app->slot, 0, 0xC1+keyno, buf, buflen);
   if (err)
-    log_error ("error changing size of key %d to %u bits\n", keyno+1, nbits);
+    log_error ("error changing key attribute (key=%d)\n", keyno+1);
   else
-    log_info ("size of key %d changed to %u bits\n", keyno+1, nbits);
+    log_info ("key attribute changed (key=%d)\n", keyno+1);
   flush_cache (app);
   parse_algorithm_attribute (app, keyno);
   app->did_chv1 = 0;
@@ -2684,18 +2729,21 @@ change_keyattr (app_t app, int keyno, unsigned int nbits,
 }
 
 
-/* Helper to process an setattr command for name KEY-ATTR.  It expects
-   a string "--force <keyno> <algo> <nbits>" in (VALUE,VALUELEN).  */
+/* Helper to process an setattr command for name KEY-ATTR.
+   In (VALUE,VALUELEN), it expects following string:
+        RSA: "--force <keyno> <algo> rsa<nbits>"
+        ECC: "--force <keyno> <algo> <curvename>"
+  */
 static gpg_error_t
 change_keyattr_from_string (app_t app,
                             gpg_error_t (*pincb)(void*, const char *, char **),
                             void *pincb_arg,
                             const void *value, size_t valuelen)
 {
-  gpg_error_t err;
+  gpg_error_t err = 0;
   char *string;
   int keyno, algo;
-  unsigned int nbits;
+  int n = 0;
 
   /* VALUE is expected to be a string but not guaranteed to be
      terminated.  Thus copy it to an allocated buffer first. */
@@ -2708,42 +2756,105 @@ change_keyattr_from_string (app_t app,
   /* Because this function deletes the key we require the string
      "--force" in the data to make clear that something serious might
      happen.  */
-  if (sscanf (string, " --force %d %d %u", &keyno, &algo, &nbits) != 3)
-    err = gpg_error (GPG_ERR_INV_DATA);
-  else if (keyno < 1 || keyno > 3)
+  sscanf (string, " --force %d %d %n", &keyno, &algo, &n);
+  if (n < 13)
+    {
+      err = gpg_error (GPG_ERR_INV_DATA);
+      goto leave;
+    }
+
+  if (keyno < 1 || keyno > 3)
     err = gpg_error (GPG_ERR_INV_ID);
-  else if (algo != 1)
-    err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Not RSA.  */
-  else if (nbits < 1024)
-    err = gpg_error (GPG_ERR_TOO_SHORT);
+  else if (algo == PUBKEY_ALGO_RSA)
+    {
+      unsigned int nbits;
+
+      errno = 0;
+      nbits = strtoul (string+n+3, NULL, 10);
+      if (errno)
+        err = gpg_error (GPG_ERR_INV_DATA);
+      else if (nbits < 1024)
+        err = gpg_error (GPG_ERR_TOO_SHORT);
+      else if (nbits > 4096)
+        err = gpg_error (GPG_ERR_TOO_LARGE);
+      else
+        {
+          unsigned char *buf;
+          size_t buflen;
+          void *relptr;
+
+          /* Read the current attributes into a buffer.  */
+          relptr = get_one_do (app, 0xC1+keyno, &buf, &buflen, NULL);
+          if (!relptr)
+            {
+              err = gpg_error (GPG_ERR_CARD);
+              goto leave;
+            }
+          if (buflen < 6 || buf[0] != PUBKEY_ALGO_RSA)
+            {
+              /* Attriutes too short or not an RSA key.  */
+              xfree (relptr);
+              err = gpg_error (GPG_ERR_CARD);
+              goto leave;
+            }
+
+          /* We only change n_bits and don't touch anything else.  Before we
+             do so, we round up NBITS to a sensible way in the same way as
+             gpg's key generation does it.  This may help to sort out problems
+             with a few bits too short keys.  */
+          nbits = ((nbits + 31) / 32) * 32;
+          buf[1] = (nbits >> 8);
+          buf[2] = nbits;
+          err = change_keyattr (app, keyno-1, buf, buflen, pincb, pincb_arg);
+          xfree (relptr);
+        }
+    }
+  else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
+           || algo == PUBKEY_ALGO_EDDSA)
+    {
+      const char *oidstr;
+
+      oidstr = openpgp_curve_to_oid (string+n, NULL);
+      if (!oidstr)
+        err = gpg_error (GPG_ERR_INV_DATA);
+      else
+        {
+          gcry_mpi_t m;
+
+          err = openpgp_oid_from_str (oidstr, &m);
+          if (!err)
+            {
+              unsigned int len;
+              const unsigned char *buf = gcry_mpi_get_opaque (m, &len);
+
+              /* We have enough room at STRING.  */
+              len = buf[0];
+              string[0] = algo;
+              memcpy (string+1, buf+1, len++);
+              err = change_keyattr (app, keyno-1, string, len,
+                                    pincb, pincb_arg);
+              gcry_mpi_release (m);
+            }
+        }
+    }
   else
-    err = change_keyattr (app, keyno-1, nbits, pincb, pincb_arg);
+    err = gpg_error (GPG_ERR_PUBKEY_ALGO);
 
+ leave:
   xfree (string);
   return err;
 }
 
 
-/* Handle the WRITEKEY command for OpenPGP.  This function expects a
-   canonical encoded S-expression with the secret key in KEYDATA and
-   its length (for assertions) in KEYDATALEN.  KEYID needs to be the
-   usual keyid which for OpenPGP is the string "OPENPGP.n" with
-   n=1,2,3.  Bit 0 of FLAGS indicates whether an existing key shall
-   get overwritten.  PINCB and PINCB_ARG are the usual arguments for
-   the pinentry callback.  */
 static gpg_error_t
-do_writekey (app_t app, ctrl_t ctrl,
-             const char *keyid, unsigned int flags,
-             gpg_error_t (*pincb)(void*, const char *, char **),
-             void *pincb_arg,
-             const unsigned char *keydata, size_t keydatalen)
+rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
+              void *pincb_arg, int keyno,
+              const unsigned char *buf, size_t buflen, int depth)
 {
   gpg_error_t err;
-  int force = (flags & 1);
-  int keyno;
-  const unsigned char *buf, *tok;
-  size_t buflen, toklen;
-  int depth, last_depth1, last_depth2;
+  const unsigned char *tok;
+  size_t toklen;
+  int last_depth1, last_depth2;
   const unsigned char *rsa_n = NULL;
   const unsigned char *rsa_e = NULL;
   const unsigned char *rsa_p = NULL;
@@ -2757,52 +2868,13 @@ do_writekey (app_t app, ctrl_t ctrl,
   unsigned char fprbuf[20];
   u32 created_at = 0;
 
-  (void)ctrl;
-
-  if (!strcmp (keyid, "OPENPGP.1"))
-    keyno = 0;
-  else if (!strcmp (keyid, "OPENPGP.2"))
-    keyno = 1;
-  else if (!strcmp (keyid, "OPENPGP.3"))
-    keyno = 2;
-  else
-    return gpg_error (GPG_ERR_INV_ID);
-
-  err = does_key_exist (app, keyno, 0, force);
-  if (err)
-    return err;
-
+  if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_RSA)
+    {
+      log_error (_("unsupported algorithm: %s"), "RSA");
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
 
-  /*
-     Parse the S-expression
-   */
-  buf = keydata;
-  buflen = keydatalen;
-  depth = 0;
-  if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
-    goto leave;
-  if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
-    goto leave;
-  if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
-    {
-      if (!tok)
-        ;
-      else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
-        log_info ("protected-private-key passed to writekey\n");
-      else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
-        log_info ("shadowed-private-key passed to writekey\n");
-      err = gpg_error (GPG_ERR_BAD_SECKEY);
-      goto leave;
-    }
-  if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
-    goto leave;
-  if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
-    goto leave;
-  if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen))
-    {
-      err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
-      goto leave;
-    }
   last_depth1 = depth;
   while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
          && depth && depth >= last_depth1)
@@ -2898,14 +2970,6 @@ do_writekey (app_t app, ctrl_t ctrl,
   if (opt.verbose)
     log_info ("RSA modulus size is %u bits (%u bytes)\n",
               nbits, (unsigned int)rsa_n_len);
-  if (nbits && nbits != maxbits
-      && app->app_local->extcap.algo_attr_change)
-    {
-      /* Try to switch the key to a new length.  */
-      err = change_keyattr (app, keyno, nbits, pincb, pincb_arg);
-      if (!err)
-        maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
-    }
   if (nbits != maxbits)
     {
       log_error (_("RSA modulus missing or not of size %d bits\n"),
@@ -2953,16 +3017,53 @@ do_writekey (app_t app, ctrl_t ctrl,
 
   if (app->app_local->extcap.is_v2)
     {
-      /* Build the private key template as described in section 4.3.3.7 of
-         the OpenPGP card specs version 2.0.  */
+      unsigned char *rsa_u, *rsa_dp, *rsa_dq;
+      size_t rsa_u_len, rsa_dp_len, rsa_dq_len;
+      gcry_mpi_t mpi_e, mpi_p, mpi_q;
+      gcry_mpi_t mpi_u = gcry_mpi_snew (0);
+      gcry_mpi_t mpi_dp = gcry_mpi_snew (0);
+      gcry_mpi_t mpi_dq = gcry_mpi_snew (0);
+      gcry_mpi_t mpi_tmp = gcry_mpi_snew (0);
       int exmode;
 
+      /* Calculate the u, dp and dq components needed by RSA_CRT cards */
+      gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL);
+      gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL);
+      gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL);
+
+      gcry_mpi_invm (mpi_u, mpi_q, mpi_p);
+      gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1);
+      gcry_mpi_invm (mpi_dp, mpi_e, mpi_tmp);
+      gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1);
+      gcry_mpi_invm (mpi_dq, mpi_e, mpi_tmp);
+
+      gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_u, &rsa_u_len, mpi_u);
+      gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dp, &rsa_dp_len, mpi_dp);
+      gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dq, &rsa_dq_len, mpi_dq);
+
+      gcry_mpi_release (mpi_e);
+      gcry_mpi_release (mpi_p);
+      gcry_mpi_release (mpi_q);
+      gcry_mpi_release (mpi_u);
+      gcry_mpi_release (mpi_dp);
+      gcry_mpi_release (mpi_dq);
+      gcry_mpi_release (mpi_tmp);
+
+      /* Build the private key template as described in section 4.3.3.7 of
+         the OpenPGP card specs version 2.0.  */
       err = build_privkey_template (app, keyno,
                                     rsa_n, rsa_n_len,
                                     rsa_e, rsa_e_len,
                                     rsa_p, rsa_p_len,
                                     rsa_q, rsa_q_len,
+                                    rsa_u, rsa_u_len,
+                                    rsa_dp, rsa_dp_len,
+                                    rsa_dq, rsa_dq_len,
                                     &template, &template_len);
+      xfree(rsa_u);
+      xfree(rsa_dp);
+      xfree(rsa_dq);
+
       if (err)
         goto leave;
 
@@ -3038,9 +3139,8 @@ do_writekey (app_t app, ctrl_t ctrl,
       goto leave;
     }
 
-  err = store_fpr (app, keyno, created_at,
-                  rsa_n, rsa_n_len, rsa_e, rsa_e_len,
-                  fprbuf, app->card_version);
+  err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA,
+                   rsa_n, rsa_n_len, rsa_e, rsa_e_len);
   if (err)
     goto leave;
 
@@ -3051,6 +3151,327 @@ do_writekey (app_t app, ctrl_t ctrl,
 }
 
 
+static gpg_error_t
+ecc_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
+              void *pincb_arg, int keyno,
+              const unsigned char *buf, size_t buflen, int depth)
+{
+  gpg_error_t err;
+  const unsigned char *tok;
+  size_t toklen;
+  int last_depth1, last_depth2;
+  const unsigned char *ecc_q = NULL;
+  const unsigned char *ecc_d = NULL;
+  size_t ecc_q_len, ecc_d_len;
+  u32 created_at = 0;
+  const char *oidstr = NULL;
+  int flag_djb_tweak = 0;
+  int algo;
+
+  /* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)):
+     curve = "NIST P-256" */
+  /* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)):
+     curve = "secp256k1" */
+  /* (private-key(ecc(curve%s)(flags eddsa)(q%m)(d%m))(created-at%d)):
+      curve = "Ed25519" */
+  last_depth1 = depth;
+  while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
+         && depth && depth >= last_depth1)
+    {
+      if (tok)
+        {
+          err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
+          goto leave;
+        }
+      if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+        goto leave;
+
+      if (tok && toklen == 5 && !memcmp (tok, "curve", 5))
+        {
+          unsigned char *curve;
+
+          if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+            goto leave;
+
+          curve = xtrymalloc (toklen+1);
+          if (!curve)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+
+          memcpy (curve, tok, toklen);
+          curve[toklen] = 0;
+          oidstr = openpgp_curve_to_oid (curve, NULL);
+          xfree (curve);
+        }
+      else if (tok && toklen == 5 && !memcmp (tok, "flags", 5))
+        {
+          if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+            goto leave;
+
+          if (tok)
+            {
+              if ((toklen == 5 && !memcmp (tok, "eddsa", 5))
+                  || (toklen == 9 && !memcmp (tok, "djb-tweak", 9)))
+                flag_djb_tweak = 1;
+            }
+        }
+      else if (tok && toklen == 1)
+        {
+          const unsigned char **buf2;
+          size_t *buf2len;
+
+          switch (*tok)
+            {
+            case 'q': buf2 = &ecc_q; buf2len = &ecc_q_len; break;
+            case 'd': buf2 = &ecc_d; buf2len = &ecc_d_len; break;
+            default: buf2 = NULL;  buf2len = NULL; break;
+            }
+          if (buf2 && *buf2)
+            {
+              err = gpg_error (GPG_ERR_DUP_VALUE);
+              goto leave;
+            }
+          if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+            goto leave;
+          if (tok && buf2 && !flag_djb_tweak)
+            /* It's MPI.  Strip off leading zero bytes and save. */
+            for (;toklen && !*tok; toklen--, tok++)
+              ;
+
+          *buf2 = tok;
+          *buf2len = toklen;
+        }
+      /* Skip until end of list. */
+      last_depth2 = depth;
+      while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
+             && depth && depth >= last_depth2)
+        ;
+      if (err)
+        goto leave;
+    }
+  /* Parse other attributes. */
+  last_depth1 = depth;
+  while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
+         && depth && depth >= last_depth1)
+    {
+      if (tok)
+        {
+          err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
+          goto leave;
+        }
+      if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+        goto leave;
+      if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen))
+        {
+          if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
+            goto leave;
+          if (tok)
+            {
+              for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9';
+                   tok++, toklen--)
+                created_at = created_at*10 + (*tok - '0');
+            }
+        }
+      /* Skip until end of list. */
+      last_depth2 = depth;
+      while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
+             && depth && depth >= last_depth2)
+        ;
+      if (err)
+        goto leave;
+    }
+
+
+  /* Check that we have all parameters and that they match the card
+     description. */
+  if (!oidstr)
+    {
+      log_error (_("unsupported curve\n"));
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+  if (!created_at)
+    {
+      log_error (_("creation timestamp missing\n"));
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+  if (flag_djb_tweak && keyno != 1)
+    algo = PUBKEY_ALGO_EDDSA;
+  else if (keyno == 1)
+    algo = PUBKEY_ALGO_ECDH;
+  else
+    algo = PUBKEY_ALGO_ECDSA;
+
+  if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_ECC
+      || app->app_local->keyattr[keyno].ecc.oid != oidstr
+      || app->app_local->keyattr[keyno].ecc.flags != flag_djb_tweak)
+    {
+      log_error ("key attribute on card doesn't match\n");
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+
+  if (opt.verbose)
+    log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len);
+
+  /* We need to remove the cached public key.  */
+  xfree (app->app_local->pk[keyno].key);
+  app->app_local->pk[keyno].key = NULL;
+  app->app_local->pk[keyno].keylen = 0;
+  app->app_local->pk[keyno].read_done = 0;
+
+  if (app->app_local->extcap.is_v2)
+    {
+      /* Build the private key template as described in section 4.3.3.7 of
+         the OpenPGP card specs version 2.0.  */
+      unsigned char *template;
+      size_t template_len;
+      int exmode;
+
+      err = build_ecc_privkey_template (app, keyno,
+                                        ecc_d, ecc_d_len,
+                                        &template, &template_len);
+      if (err)
+        goto leave;
+
+      /* Prepare for storing the key.  */
+      err = verify_chv3 (app, pincb, pincb_arg);
+      if (err)
+        {
+          xfree (template);
+          goto leave;
+        }
+
+      /* Store the key. */
+      if (app->app_local->cardcap.ext_lc_le && template_len > 254)
+        exmode = 1;    /* Use extended length w/o a limit.  */
+      else if (app->app_local->cardcap.cmd_chaining && template_len > 254)
+        exmode = -254;
+      else
+        exmode = 0;
+      err = iso7816_put_data_odd (app->slot, exmode, 0x3fff,
+                                  template, template_len);
+      xfree (template);
+    }
+  else
+    err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+  if (err)
+    {
+      log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
+      goto leave;
+    }
+  else
+    {
+      gcry_mpi_t oid;
+      const unsigned char *oidbuf;
+      unsigned int n;
+      size_t oid_len;
+      unsigned char fprbuf[20];
+
+      err = openpgp_oid_from_str (oidstr, &oid);
+      if (err)
+        goto leave;
+
+      oidbuf = gcry_mpi_get_opaque (oid, &n);
+      oid_len = (n+7)/8;
+      if (!oidbuf)
+        {
+          err = gpg_error_from_syserror ();
+          gcry_mpi_release (oid);
+          goto leave;
+        }
+      err = store_fpr (app, keyno, created_at, fprbuf, algo,
+                       oidbuf, oid_len, ecc_q, ecc_q_len,
+                       "\x03\x01\x08\x07", (size_t)4);
+      gcry_mpi_release (oid);
+    }
+
+ leave:
+  return err;
+}
+
+/* Handle the WRITEKEY command for OpenPGP.  This function expects a
+   canonical encoded S-expression with the secret key in KEYDATA and
+   its length (for assertions) in KEYDATALEN.  KEYID needs to be the
+   usual keyid which for OpenPGP is the string "OPENPGP.n" with
+   n=1,2,3.  Bit 0 of FLAGS indicates whether an existing key shall
+   get overwritten.  PINCB and PINCB_ARG are the usual arguments for
+   the pinentry callback.  */
+static gpg_error_t
+do_writekey (app_t app, ctrl_t ctrl,
+             const char *keyid, unsigned int flags,
+             gpg_error_t (*pincb)(void*, const char *, char **),
+             void *pincb_arg,
+             const unsigned char *keydata, size_t keydatalen)
+{
+  gpg_error_t err;
+  int force = (flags & 1);
+  int keyno;
+  const unsigned char *buf, *tok;
+  size_t buflen, toklen;
+  int depth;
+
+  (void)ctrl;
+
+  if (!strcmp (keyid, "OPENPGP.1"))
+    keyno = 0;
+  else if (!strcmp (keyid, "OPENPGP.2"))
+    keyno = 1;
+  else if (!strcmp (keyid, "OPENPGP.3"))
+    keyno = 2;
+  else
+    return gpg_error (GPG_ERR_INV_ID);
+
+  err = does_key_exist (app, keyno, 0, force);
+  if (err)
+    return err;
+
+
+  /*
+     Parse the S-expression
+   */
+  buf = keydata;
+  buflen = keydatalen;
+  depth = 0;
+  if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+    goto leave;
+  if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+    goto leave;
+  if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
+    {
+      if (!tok)
+        ;
+      else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
+        log_info ("protected-private-key passed to writekey\n");
+      else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
+        log_info ("shadowed-private-key passed to writekey\n");
+      err = gpg_error (GPG_ERR_BAD_SECKEY);
+      goto leave;
+    }
+  if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+    goto leave;
+  if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
+    goto leave;
+  if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0)
+    err = rsa_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
+  else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0)
+    err = ecc_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth);
+  else
+    {
+      err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
+      goto leave;
+    }
+
+ leave:
+  return err;
+}
+
+
+
 /* Handle the GENKEY command. */
 static gpg_error_t
 do_genkey (app_t app, ctrl_t ctrl,  const char *keynostr, unsigned int flags,
@@ -3172,8 +3593,8 @@ do_genkey (app_t app, ctrl_t ctrl,  const char *keynostr, unsigned int flags,
   send_status_info (ctrl, "KEY-CREATED-AT",
                     numbuf, (size_t)strlen(numbuf), NULL, 0);
 
-  rc = store_fpr (app, keyno, (u32)created_at,
-                  m, mlen, e, elen, fprbuf, app->card_version);
+  rc = store_fpr (app, keyno, (u32)created_at, fprbuf, PUBKEY_ALGO_RSA,
+                  m, mlen, e, elen);
   if (rc)
     goto leave;
   send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
@@ -3521,15 +3942,25 @@ do_auth (app_t app, const char *keyidstr,
 
   if (!keyidstr || !*keyidstr)
     return gpg_error (GPG_ERR_INV_VALUE);
-  if (indatalen > 101) /* For a 2048 bit key. */
+  if (app->app_local->keyattr[2].key_type == KEY_TYPE_RSA
+      && indatalen > 101) /* For a 2048 bit key. */
     return gpg_error (GPG_ERR_INV_VALUE);
 
-  if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECDSA
-      && (indatalen == 51 || indatalen == 67 || indatalen == 83))
+  if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC)
     {
-      const char *p = (const char *)indata + 19;
-      indata = p;
-      indatalen -= 19;
+      if (!app->app_local->keyattr[2].ecc.flags
+          && (indatalen == 51 || indatalen == 67 || indatalen == 83))
+        {
+          const char *p = (const char *)indata + 19;
+          indata = p;
+          indatalen -= 19;
+        }
+      else
+        {
+          const char *p = (const char *)indata + 15;
+          indata = p;
+          indatalen -= 15;
+        }
     }
 
   /* Check whether an OpenPGP card of any version has been requested. */
@@ -3597,7 +4028,8 @@ do_decipher (app_t app, const char *keyidstr,
              gpg_error_t (*pincb)(void*, const char *, char **),
              void *pincb_arg,
              const void *indata, size_t indatalen,
-             unsigned char **outdata, size_t *outdatalen )
+             unsigned char **outdata, size_t *outdatalen,
+             unsigned int *r_info)
 {
   int rc;
   unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
@@ -3605,6 +4037,9 @@ do_decipher (app_t app, const char *keyidstr,
   int n;
   const char *fpr = NULL;
   int exmode, le_value;
+  unsigned char *fixbuf = NULL;
+  int padind = 0;
+  int fixuplen = 0;
 
   if (!keyidstr || !*keyidstr || !indatalen)
     return gpg_error (GPG_ERR_INV_VALUE);
@@ -3646,12 +4081,14 @@ do_decipher (app_t app, const char *keyidstr,
     return rc;
 
   rc = verify_chv2 (app, pincb, pincb_arg);
-  if (!rc)
-    {
-      int fixuplen;
-      unsigned char *fixbuf = NULL;
-      int padind = 0;
+  if (rc)
+    return rc;
 
+  if (indatalen == 16 + 1 || indatalen == 32 + 1)
+    /* PSO:DECIPHER with symmetric key.  */
+    padind = -1;
+  else if (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA)
+    {
       /* We might encounter a couple of leading zeroes in the
          cryptogram.  Due to internal use of MPIs these leading zeroes
          are stripped.  However the OpenPGP card expects exactly 128
@@ -3702,31 +4139,56 @@ do_decipher (app_t app, const char *keyidstr,
           /* We use the extra leading zero as the padding byte.  */
           padind = -1;
         }
+    }
+  else if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC)
+    {
+      fixuplen = 7;
+      fixbuf = xtrymalloc (fixuplen + indatalen);
+      if (!fixbuf)
+        return gpg_error_from_syserror ();
 
-      if (app->app_local->cardcap.ext_lc_le && indatalen > 254 )
-        {
-          exmode = 1;    /* Extended length w/o a limit.  */
-          le_value = app->app_local->extcap.max_rsp_data;
-        }
-      else if (app->app_local->cardcap.cmd_chaining && indatalen > 254)
-        {
-          exmode = -254; /* Command chaining with max. 254 bytes.  */
-          le_value = 0;
-        }
-      else
-        exmode = le_value = 0;
-
-      rc = iso7816_decipher (app->slot, exmode,
-                             indata, indatalen, le_value, padind,
-                             outdata, outdatalen);
-      xfree (fixbuf);
+      /* Build 'Cipher DO' */
+      fixbuf[0] = '\xa6';
+      fixbuf[1] = (char)(indatalen+5);
+      fixbuf[2] = '\x7f';
+      fixbuf[3] = '\x49';
+      fixbuf[4] = (char)(indatalen+2);
+      fixbuf[5] = '\x86';
+      fixbuf[6] = (char)indatalen;
+      memcpy (fixbuf+fixuplen, indata, indatalen);
+      indata = fixbuf;
+      indatalen = fixuplen + indatalen;
+
+      padind = -1;
+    }
+  else
+    return gpg_error (GPG_ERR_INV_VALUE);
 
-      if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */
-          && app->app_local->manufacturer == 5
-          && app->card_version == 0x0200)
-        log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)"
-                  " do not work with encryption keys > 2048 bits\n");
+  if (app->app_local->cardcap.ext_lc_le && indatalen > 254 )
+    {
+      exmode = 1;    /* Extended length w/o a limit.  */
+      le_value = app->app_local->extcap.max_rsp_data;
     }
+  else if (app->app_local->cardcap.cmd_chaining && indatalen > 254)
+    {
+      exmode = -254; /* Command chaining with max. 254 bytes.  */
+      le_value = 0;
+    }
+  else
+    exmode = le_value = 0;
+
+  rc = iso7816_decipher (app->slot, exmode,
+                         indata, indatalen, le_value, padind,
+                         outdata, outdatalen);
+  xfree (fixbuf);
+
+  if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */
+      && app->app_local->manufacturer == 5
+      && app->card_version == 0x0200)
+    log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)"
+              " do not work with encryption keys > 2048 bits\n");
+
+  *r_info |= APP_DECIPHER_INFO_NOPAD;
 
   return rc;
 }
@@ -3807,7 +4269,7 @@ do_check_pin (app_t app, const char *keyidstr,
           log_info (_("card is permanently locked!\n"));
           return gpg_error (GPG_ERR_BAD_PIN);
         }
-      else if (value[6] < 3)
+      else if (count < 3)
         {
           log_info (_("verification of Admin PIN is currently prohibited "
                       "through this command\n"));
@@ -3836,13 +4298,16 @@ show_caps (struct app_local_s *s)
   log_info ("Algo-Attr-Change: %s\n", s->extcap.algo_attr_change? "yes":"no");
   log_info ("SM-Support .....: %s", s->extcap.sm_supported? "yes":"no");
   if (s->extcap.sm_supported)
-    log_printf (" (%s)", s->extcap.sm_aes128? "AES-128":"3DES");
+    log_printf (" (%s)", s->extcap.sm_algo==2? "3DES":
+                (s->extcap.sm_algo==2? "AES-128" : "AES-256"));
   log_info ("Max-Cert3-Len ..: %u\n", s->extcap.max_certlen_3);
   log_info ("Max-Cmd-Data ...: %u\n", s->extcap.max_cmd_data);
   log_info ("Max-Rsp-Data ...: %u\n", s->extcap.max_rsp_data);
   log_info ("Cmd-Chaining ...: %s\n", s->cardcap.cmd_chaining?"yes":"no");
   log_info ("Ext-Lc-Le ......: %s\n", s->cardcap.ext_lc_le?"yes":"no");
   log_info ("Status Indicator: %02X\n", s->status_indicator);
+  log_info ("Symmetric crypto: %s\n", s->extcap.has_decrypt? "yes":"no");
+  log_info ("Button..........: %s\n", s->extcap.has_button? "yes":"no");
 
   log_info ("GnuPG-No-Sync ..: %s\n",  s->flags.no_sync? "yes":"no");
   log_info ("GnuPG-Def-PW2 ..: %s\n",  s->flags.def_chv2? "yes":"no");
@@ -3899,19 +4364,41 @@ parse_historical (struct app_local_s *apploc,
 }
 
 
-static int
-parse_ecc_curve (const unsigned char *buffer, size_t buflen)
+/*
+ * Check if the OID in an DER encoding is available by GnuPG/libgcrypt,
+ * and return the constant string in dotted decimal form.
+ * Return NULL if not available.
+ * The constant string is not allocated dynamically, never free it.
+ */
+static const char *
+ecc_oid (unsigned char *buf, size_t buflen)
 {
-  int curve;
+  gcry_mpi_t oid;
+  char *oidstr;
+  const char *result;
+  unsigned char *oidbuf;
+
+  oidbuf = xtrymalloc (buflen + 1);
+  if (!oidbuf)
+    return NULL;
+
+  memcpy (oidbuf+1, buf, buflen);
+  oidbuf[0] = buflen;
+  oid = gcry_mpi_set_opaque (NULL, oidbuf, (buflen+1) * 8);
+  if (!oid)
+    {
+      xfree (oidbuf);
+      return NULL;
+    }
 
-  if (buflen == 6 && buffer[5] == 0x22)
-    curve = CURVE_NIST_P384;
-  else if (buflen == 6 && buffer[5] == 0x23)
-    curve = CURVE_NIST_P521;
-  else
-    curve = CURVE_NIST_P256;
+  oidstr = openpgp_oid_to_str (oid);
+  gcry_mpi_release (oid);
+  if (!oidstr)
+    return NULL;
 
-  return curve;
+  result = openpgp_curve_to_oid (oidstr, NULL);
+  xfree (oidstr);
+  return result;
 }
 
 
@@ -3945,7 +4432,7 @@ parse_algorithm_attribute (app_t app, int keyno)
 
   if (opt.verbose)
     log_info ("Key-Attr-%s ..: ", desc[keyno]);
-  if (*buffer == 1 && (buflen == 5 || buflen == 6))
+  if (*buffer == PUBKEY_ALGO_RSA && (buflen == 5 || buflen == 6))
     {
       app->app_local->keyattr[keyno].rsa.n_bits = (buffer[1]<<8 | buffer[2]);
       app->app_local->keyattr[keyno].rsa.e_bits = (buffer[3]<<8 | buffer[4]);
@@ -3969,19 +4456,30 @@ parse_algorithm_attribute (app_t app, int keyno)
            app->app_local->keyattr[keyno].rsa.format == RSA_CRT?  "crt"  :
            app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N?"crt+n":"?");
     }
-  else if (*buffer == 19) /* ECDSA */
+  else if (*buffer == PUBKEY_ALGO_ECDH || *buffer == PUBKEY_ALGO_ECDSA
+           || *buffer == PUBKEY_ALGO_EDDSA)
     {
-      app->app_local->keyattr[keyno].key_type = KEY_TYPE_ECDSA;
-      app->app_local->keyattr[keyno].ecdsa.curve
-        = parse_ecc_curve (buffer + 1, buflen - 1);
-    }
-  else if (*buffer == 18 && buflen == 11) /* ECDH */
-    {
-      app->app_local->keyattr[keyno].key_type = KEY_TYPE_ECDH;
-      app->app_local->keyattr[keyno].ecdh.curve
-        = parse_ecc_curve (buffer + 1, buflen - 1);
-      app->app_local->keyattr[keyno].ecdh.hashalgo = buffer[1];
-      app->app_local->keyattr[keyno].ecdh.cipheralgo = buffer[2];
+      const char *oid = ecc_oid (buffer + 1, buflen - 1);
+
+      if (!oid)
+        log_printhex ("Curve with OID not supported: ", buffer+1, buflen-1);
+      else
+        {
+          app->app_local->keyattr[keyno].key_type = KEY_TYPE_ECC;
+          app->app_local->keyattr[keyno].ecc.oid = oid;
+          if (*buffer == PUBKEY_ALGO_EDDSA
+              || (*buffer == PUBKEY_ALGO_ECDH
+                  && !strcmp (app->app_local->keyattr[keyno].ecc.oid,
+                              "1.3.6.1.4.1.3029.1.5.1")))
+            app->app_local->keyattr[keyno].ecc.flags = ECC_FLAG_DJB_TWEAK;
+          else
+            app->app_local->keyattr[keyno].ecc.flags = 0;
+          if (opt.verbose)
+            log_printf
+              ("ECC, curve=%s%s\n", app->app_local->keyattr[keyno].ecc.oid,
+               !app->app_local->keyattr[keyno].ecc.flags ? "":
+               keyno==1? " (djb-tweak)": " (eddsa)");
+        }
     }
   else if (opt.verbose)
     log_printhex ("", buffer, buflen);
@@ -4090,11 +4588,12 @@ app_select_openpgp (app_t app)
           app->app_local->extcap.change_force_chv = !!(*buffer & 0x10);
           app->app_local->extcap.private_dos      = !!(*buffer & 0x08);
           app->app_local->extcap.algo_attr_change = !!(*buffer & 0x04);
+          app->app_local->extcap.has_decrypt      = !!(*buffer & 0x02);
         }
       if (buflen >= 10)
         {
           /* Available with v2 cards.  */
-          app->app_local->extcap.sm_aes128     = (buffer[1] == 1);
+          app->app_local->extcap.sm_algo = buffer[1];
           app->app_local->extcap.max_get_challenge
                                                = (buffer[2] << 8 | buffer[3]);
           app->app_local->extcap.max_certlen_3 = (buffer[4] << 8 | buffer[5]);
@@ -4108,6 +4607,12 @@ app_select_openpgp (app_t app)
       if (app->card_version <= 0x0100 && manufacturer == 1)
         app->app_local->extcap.change_force_chv = 1;
 
+      /* Check optional DO of "General Feature Management" for button.  */
+      relptr = get_one_do (app, 0x7f74, &buffer, &buflen, NULL);
+      if (relptr)
+        /* It must be: 03 81 01 20 */
+        app->app_local->extcap.has_button = 1;
+
       parse_login_data (app);
 
       if (opt.verbose)