[PATCH] scd: support ECDSA public key

NIIBE Yutaka gniibe at fsij.org
Thu Feb 28 05:38:04 CET 2013


Here is another patch for GnuPG in the smartcard ECDSA support series.
(There will be two more: support ECDSA signing and support writekey
for ECDSA.)

This is to add ECDSA public key handling in OpenPGP card support.  It
includes things for ECDH too, but it is not yet fully implemented.

Currently, the implementation assumes RSA.  We extend it to support
Elliptic Curve Cryptography (ECDH and ECDSA), too.

For OpenPGP card specification.  The implementation (6) and (8) below
relates modify something to the specification.  (6) for public key
representation, and (8) for algorithm attributes.

I think that we won't use compressed format for EC point for obvious
reason, so we could get consensus on (6).  For (8), it requires only
OID, which defines all EC parameters.  This is different which is
mentioned in the specification currently.


Summary:

(1) We add key_type_t to support different key types.
(2) We define curves by enumeration.  This time only for NIST curves.
(3) We extend keyattr: add key_type and union for RSA, ECDH, and ECDSA.
(4) Extend SCDaemon to GPG protocol for key attributes.
    Which is implemented send_key_attr using new function
    get_ecc_key_parameters.

    Currently, it is like following for RSA:

    <KEYNO> <ALGORITHM> <PUBLIC_KEY_NUMBER_OF_BITS> <NUMBER_OF_BITS_E> <FORMAT>

    ... but only first two data are examined and used.

    In this patch, we re-define it as (in compatible way):

    <KEYNO> <ALGORITHM> <PUBLIC_KEY_NUMBER_OF_BITS> [algorithm specific part...]

    and extend to ECDH and ECDSA.  For ECDSA:

    <PUBLIC_KEY_NUMBER_OF_BITS> <CURVE-OID>

    and for ECDH

    <PUBLIC_KEY_NUMBER_OF_BITS> <CURVE-OID> <HASH_ALGO> <CIPHER_ALGO>

(5) New function get_curve_name for name of Elliptic Curve in public
    key SEXP.

(6) We extend get_public_key to support ECDSA.  ECDH is not yet
    supported.  It assumes OpenPGP card returns MPI of an EC point
    representing a public key for CMD_GENERATE_KEYPAIR.
    That is, in the format of uncompressed point format:

	04 || x || y
    
(7) New function parse_ecc_curve to parse OID and determice the curve.
    It's not fully parse the OID now.

(8) We extend parse_algorithm_attribute to support ECDH and ECDSA.

(9) Other changes following the change of keyattr.


diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c
index 23b28c3..f7f0f63 100644
--- a/scd/app-openpgp.c
+++ b/scd/app-openpgp.c
@@ -116,6 +116,16 @@ static struct {
 };
 

+/* Type of keys.  */
+typedef enum
+  {
+    KEY_TYPE_ECDH,
+    KEY_TYPE_ECDSA,
+    KEY_TYPE_RSA,
+  }
+key_type_t;
+
+
 /* The format of RSA private keys.  */
 typedef enum
   {
@@ -128,6 +138,15 @@ 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;
@@ -199,15 +218,27 @@ struct app_local_s {
     int fixedlen_admin;
   } pinpad;
 
-  struct
-  {
-    unsigned int n_bits;     /* Size of the modulus in bits.  The rest
-                                of this strucuire is only valid if
-                                this is not 0.  */
-    unsigned int e_bits;     /* Size of the public exponent in bits.  */
-    rsa_key_format_t format;
-  } keyattr[3];
-
+   struct
+   {
+    key_type_t key_type;
+    union {
+      struct {
+        unsigned int n_bits;     /* Size of the modulus in bits.  The rest
+                                    of this strucuire is only valid if
+                                    this is not 0.  */
+        unsigned int e_bits;     /* Size of the public exponent in bits.  */
+        rsa_key_format_t format;
+      } rsa;
+      struct {
+        int curve;
+      } ecdsa;
+      struct {
+        int curve;
+        int hashalgo;
+        int cipheralgo;
+      } ecdh;
+    };
+   } keyattr[3];
 };
 

@@ -845,18 +876,59 @@ 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)
 {
   char buffer[200];
+  int n_bits;
+  const char *curve_oid;
 
   assert (number >=0 && number < DIM(app->app_local->keyattr));
 
-  /* We only support RSA thus the algo identifier is fixed to 1.  */
-  snprintf (buffer, sizeof buffer, "%d 1 %u %u %d",
-            number+1,
-            app->app_local->keyattr[number].n_bits,
-            app->app_local->keyattr[number].e_bits,
-            app->app_local->keyattr[number].format);
+  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)
+    {
+      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);
+    }
+  else
+    snprintf (buffer, sizeof buffer, "0 0 UNKNOWN");
+
   send_status_direct (ctrl, keyword, buffer);
 }
 
@@ -1154,6 +1226,18 @@ 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
@@ -1171,11 +1255,14 @@ get_public_key (app_t app, int keyno)
   gpg_error_t err = 0;
   unsigned char *buffer;
   const unsigned char *keydata, *m, *e;
-  size_t buflen, keydatalen, mlen, elen;
+  size_t buflen, keydatalen;
+  size_t mlen = 0;
+  size_t elen = 0;
   unsigned char *mbuf = NULL;
   unsigned char *ebuf = NULL;
   char *keybuf = NULL;
-  char *keybuf_p;
+  gcry_sexp_t s_pkey;
+  size_t len;
 
   if (keyno < 1 || keyno > 3)
     return gpg_error (GPG_ERR_INV_ID);
@@ -1235,43 +1322,16 @@ get_public_key (app_t app, int keyno)
           goto leave;
         }
 
-
-      e = find_tlv (keydata, keydatalen, 0x0082, &elen);
-      if (!e)
-        {
-          err = gpg_error (GPG_ERR_CARD);
-          log_error (_("response does not contain the RSA public exponent\n"));
-          goto leave;
-        }
-
-      /* Prepend numbers with a 0 if needed.  */
-      if (mlen && (*m & 0x80))
+      if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
         {
-          mbuf = xtrymalloc ( mlen + 1);
-          if (!mbuf)
+          e = find_tlv (keydata, keydatalen, 0x0082, &elen);
+          if (!e)
             {
-              err = gpg_error_from_syserror ();
+              err = gpg_error (GPG_ERR_CARD);
+              log_error (_("response does not contain the RSA public exponent\n"));
               goto leave;
             }
-          *mbuf = 0;
-          memcpy (mbuf+1, m, mlen);
-          mlen++;
-          m = mbuf;
         }
-      if (elen && (*e & 0x80))
-        {
-          ebuf = xtrymalloc ( elen + 1);
-          if (!ebuf)
-            {
-              err = gpg_error_from_syserror ();
-              goto leave;
-            }
-          *ebuf = 0;
-          memcpy (ebuf+1, e, elen);
-          elen++;
-          e = ebuf;
-        }
-
     }
   else
     {
@@ -1328,29 +1388,88 @@ get_public_key (app_t app, int keyno)
 	}
     }
 
-  /* Allocate a buffer to construct the S-expression.  */
-  /* FIXME: We should provide a generalized S-expression creation
-     mechanism. */
-  keybuf = xtrymalloc (50 + 2*35 + mlen + elen + 1);
-  if (!keybuf)
+
+  mbuf = xtrymalloc ( mlen + 1);
+  if (!mbuf)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  /* Prepend numbers with a 0 if needed.  */
+  if (mlen && (*m & 0x80))
+    {
+      *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
+    memcpy (ebuf, e, elen);
+
+  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)
+        {
+          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);
+    }
+  else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECDSA)
+    {
+      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;
+
+      len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
 
-  sprintf (keybuf, "(10:public-key(3:rsa(1:n%u:", (unsigned int) mlen);
-  keybuf_p = keybuf + strlen (keybuf);
-  memcpy (keybuf_p, m, mlen);
-  keybuf_p += mlen;
-  sprintf (keybuf_p, ")(1:e%u:", (unsigned int)elen);
-  keybuf_p += strlen (keybuf_p);
-  memcpy (keybuf_p, e, elen);
-  keybuf_p += elen;
-  strcpy (keybuf_p, ")))");
-  keybuf_p += strlen (keybuf_p);
+      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);
+    }
+  else
+    {
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      goto leave;
+    }
 
   app->app_local->pk[keyno].key = (unsigned char*)keybuf;
-  app->app_local->pk[keyno].keylen = (keybuf_p - keybuf);
+  app->app_local->pk[keyno].keylen = len - 1; /* Decrement for trailing '\0' */
 
  leave:
   /* Set a flag to indicate that we tried to read the key.  */
@@ -2395,7 +2514,7 @@ build_privkey_template (app_t app, int keyno,
   *result = NULL;
   *resultlen = 0;
 
-  switch (app->app_local->keyattr[keyno].format)
+  switch (app->app_local->keyattr[keyno].rsa.format)
     {
     case RSA_STD:
     case RSA_STD_N:
@@ -2409,7 +2528,7 @@ build_privkey_template (app_t app, int keyno,
     }
 
   /* Get the required length for E.  */
-  rsa_e_reqlen = app->app_local->keyattr[keyno].e_bits/8;
+  rsa_e_reqlen = app->app_local->keyattr[keyno].rsa.e_bits/8;
   assert (rsa_e_len <= rsa_e_reqlen);
 
   /* Build the 7f48 cardholder private key template.  */
@@ -2425,8 +2544,8 @@ 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].format == RSA_STD_N
-      || app->app_local->keyattr[keyno].format == RSA_CRT_N)
+  if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
+      || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
     {
       tp += add_tlv (tp, 0x97, rsa_n_len);
       datalen += rsa_n_len;
@@ -2478,8 +2597,8 @@ 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].format == RSA_STD_N
-      || app->app_local->keyattr[keyno].format == RSA_CRT_N)
+  if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
+      || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
     {
       memcpy (tp, rsa_n, rsa_n_len);
       tp += rsa_n_len;
@@ -2764,7 +2883,7 @@ do_writekey (app_t app, ctrl_t ctrl,
       goto leave;
     }
 
-  maxbits = app->app_local->keyattr[keyno].n_bits;
+  maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
   nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0;
   if (opt.verbose)
     log_info ("RSA modulus size is %u bits (%u bytes)\n",
@@ -2775,7 +2894,7 @@ do_writekey (app_t app, ctrl_t ctrl,
       /* 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].n_bits;
+        maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
     }
   if (nbits != maxbits)
     {
@@ -2785,7 +2904,7 @@ do_writekey (app_t app, ctrl_t ctrl,
       goto leave;
     }
 
-  maxbits = app->app_local->keyattr[keyno].e_bits;
+  maxbits = app->app_local->keyattr[keyno].rsa.e_bits;
   if (maxbits > 32 && !app->app_local->extcap.is_v2)
     maxbits = 32; /* Our code for v1 does only support 32 bits.  */
   nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0;
@@ -2797,7 +2916,7 @@ do_writekey (app_t app, ctrl_t ctrl,
       goto leave;
     }
 
-  maxbits = app->app_local->keyattr[keyno].n_bits/2;
+  maxbits = app->app_local->keyattr[keyno].rsa.n_bits/2;
   nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0;
   if (nbits != maxbits)
     {
@@ -2966,7 +3085,7 @@ do_genkey (app_t app, ctrl_t ctrl,  const char *keynostr, unsigned int flags,
      to put a limit on the max. allowed keysize.  2048 bit will
      already lead to a 527 byte long status line and thus a 4096 bit
      key would exceed the Assuan line length limit.  */
-  keybits = app->app_local->keyattr[keyno].n_bits;
+  keybits = app->app_local->keyattr[keyno].rsa.n_bits;
   if (keybits > 4096)
     return gpg_error (GPG_ERR_TOO_LARGE);
 
@@ -3753,6 +3872,22 @@ parse_historical (struct app_local_s *apploc,
 }
 

+static int
+parse_ecc_curve (const unsigned char *buffer, size_t buflen)
+{
+  int curve;
+
+  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;
+
+  return curve;
+}
+
+
 /* Parse and optionally show the algorithm attributes for KEYNO.
    KEYNO must be in the range 0..2.  */
 static void
@@ -3765,7 +3900,8 @@ parse_algorithm_attribute (app_t app, int keyno)
 
   assert (keyno >=0 && keyno <= 2);
 
-  app->app_local->keyattr[keyno].n_bits = 0;
+  app->app_local->keyattr[keyno].key_type = KEY_TYPE_RSA;
+  app->app_local->keyattr[keyno].rsa.n_bits = 0;
 
   relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL);
   if (!relptr)
@@ -3784,27 +3920,41 @@ parse_algorithm_attribute (app_t app, int keyno)
     log_info ("Key-Attr-%s ..: ", desc[keyno]);
   if (*buffer == 1 && (buflen == 5 || buflen == 6))
     {
-      app->app_local->keyattr[keyno].n_bits = (buffer[1]<<8 | buffer[2]);
-      app->app_local->keyattr[keyno].e_bits = (buffer[3]<<8 | buffer[4]);
-      app->app_local->keyattr[keyno].format = 0;
+      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]);
+      app->app_local->keyattr[keyno].rsa.format = 0;
       if (buflen < 6)
-        app->app_local->keyattr[keyno].format = RSA_STD;
+        app->app_local->keyattr[keyno].rsa.format = RSA_STD;
       else
-        app->app_local->keyattr[keyno].format = (buffer[5] == 0? RSA_STD   :
-                                                 buffer[5] == 1? RSA_STD_N :
-                                                 buffer[5] == 2? RSA_CRT   :
-                                                 buffer[5] == 3? RSA_CRT_N :
-                                                 RSA_UNKNOWN_FMT);
+        app->app_local->keyattr[keyno].rsa.format = (buffer[5] == 0? RSA_STD   :
+                                                     buffer[5] == 1? RSA_STD_N :
+                                                     buffer[5] == 2? RSA_CRT   :
+                                                     buffer[5] == 3? RSA_CRT_N :
+                                                     RSA_UNKNOWN_FMT);
 
       if (opt.verbose)
         log_printf
           ("RSA, n=%u, e=%u, fmt=%s\n",
-           app->app_local->keyattr[keyno].n_bits,
-           app->app_local->keyattr[keyno].e_bits,
-           app->app_local->keyattr[keyno].format == RSA_STD?  "std"  :
-           app->app_local->keyattr[keyno].format == RSA_STD_N?"std+n":
-           app->app_local->keyattr[keyno].format == RSA_CRT?  "crt"  :
-           app->app_local->keyattr[keyno].format == RSA_CRT_N?"crt+n":"?");
+           app->app_local->keyattr[keyno].rsa.n_bits,
+           app->app_local->keyattr[keyno].rsa.e_bits,
+           app->app_local->keyattr[keyno].rsa.format == RSA_STD?  "std"  :
+           app->app_local->keyattr[keyno].rsa.format == RSA_STD_N?"std+n":
+           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 */
+    {
+      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];
     }
   else if (opt.verbose)
     log_printhex ("", buffer, buflen);
-- 






More information about the Gnupg-devel mailing list