gpg: Make export of ECC keys work again.
authorWerner Koch <wk@gnupg.org>
Fri, 20 Jun 2014 12:54:01 +0000 (14:54 +0200)
committerWerner Koch <wk@gnupg.org>
Fri, 20 Jun 2014 12:54:01 +0000 (14:54 +0200)
* agent/cvt-openpgp.c (convert_to_openpgp): Use the curve name instead
of the curve parameters.
* g10/export.c (canon_pubkey_algo): Rename to ...
(canon_pk_algo): this.  Support ECC.
(transfer_format_to_openpgp): Expect curve name.

agent/cvt-openpgp.c
g10/export.c

index 7f4afd4..1b4c9d5 100644 (file)
@@ -1142,6 +1142,7 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
   const char *algoname;
   int npkey, nskey;
   gcry_mpi_t array[10];
+  gcry_sexp_t curve = NULL;
   char protect_iv[16];
   char salt[8];
   unsigned long s2k_count;
@@ -1200,13 +1201,26 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
     }
   else if (!strcmp (name, "ecc"))
     {
-      /* FIXME: We need to use the curve parameter.  */
+      gcry_buffer_t iob;
+      char iobbuf[32];
+
       algoname = "ecc"; /* Decide later by checking the usage.  */
-      npkey = 6;
-      nskey = 7;
-      err = gcry_sexp_extract_param (list, NULL, "pabgnqd",
-                                     array+0, array+1, array+2, array+3,
-                                     array+4, array+5, array+6, NULL);
+      npkey = 1;
+      nskey = 2;
+      iob.data = iobbuf;
+      iob.size = sizeof iobbuf - 1;
+      iob.off = 0;
+      iob.len = 0;
+      err = gcry_sexp_extract_param (list, NULL, "&'curve'/qd",
+                                     &iob, array+0, array+1, NULL);
+      if (!err)
+        {
+          assert (iob.len < sizeof iobbuf -1);
+          iobbuf[iob.len] = 0;
+          err = gcry_sexp_build (&curve, NULL, "(curve %s)", iobbuf);
+
+          gcry_log_debugsxp ("at 1", curve);
+        }
     }
   else if (!strcmp (name, "ecdsa"))
     {
@@ -1231,9 +1245,12 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
       err = gpg_error (GPG_ERR_PUBKEY_ALGO);
     }
   xfree (name);
-  gcry_sexp_release (list);
+  gcry_sexp_release (list); list = NULL;
   if (err)
-    return err;
+    {
+      gcry_sexp_release (curve);
+      return err;
+    }
 
   gcry_create_nonce (protect_iv, sizeof protect_iv);
   gcry_create_nonce (salt, sizeof salt);
@@ -1282,9 +1299,10 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
                                "(openpgp-private-key\n"
                                " (version 1:4)\n"
                                " (algo %s)\n"
-                               " %S\n"
+                               " %S%S\n"
                                " (protection sha1 aes %b 1:3 sha1 %b %s))\n",
                                algoname,
+                               curve,
                                tmpkey,
                                (int)sizeof protect_iv, protect_iv,
                                (int)sizeof salt, salt,
@@ -1297,6 +1315,7 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
 
   for (i=0; i < DIM (array); i++)
     gcry_mpi_release (array[i]);
+  gcry_sexp_release (curve);
 
   return err;
 }
index 9aa012e..acf38a7 100644 (file)
@@ -1,6 +1,7 @@
 /* export.c - Export keys in the OpenPGP defined format.
  * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
  *               2005, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2014  Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -338,8 +339,8 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node)
 /* Return a canonicalized public key algoithms.  This is used to
    compare different flavors of algorithms (e.g. ELG and ELG_E are
    considered the same).  */
-static int
-canon_pubkey_algo (int algo)
+static enum gcry_pk_algos
+canon_pk_algo (enum gcry_pk_algos algo)
 {
   switch (algo)
     {
@@ -348,6 +349,9 @@ canon_pubkey_algo (int algo)
     case GCRY_PK_RSA_S: return GCRY_PK_RSA;
     case GCRY_PK_ELG:
     case GCRY_PK_ELG_E: return GCRY_PK_ELG;
+    case GCRY_PK_ECC:
+    case GCRY_PK_ECDSA:
+    case GCRY_PK_ECDH: return GCRY_PK_ECC;
     default: return algo;
     }
 }
@@ -362,12 +366,13 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   gpg_error_t err;
   gcry_sexp_t top_list;
   gcry_sexp_t list = NULL;
+  char *curve = NULL;
   const char *value;
   size_t valuelen;
   char *string;
   int  idx;
   int  is_v4, is_protected;
-  int  pubkey_algo;
+  enum gcry_pk_algos pk_algo;
   int  protect_algo = 0;
   char iv[16];
   int  ivlen = 0;
@@ -375,11 +380,13 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   int  s2k_algo = 0;
   byte s2k_salt[8];
   u32  s2k_count = 0;
+  int  is_ecdh = 0;
   size_t npkey, nskey;
   gcry_mpi_t skey[10];  /* We support up to 9 parameters.  */
   int skeyidx = 0;
   struct seckey_info *ski;
 
+  /* gcry_log_debugsxp ("transferkey", s_pgp); */
   top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
   if (!top_list)
     goto bad_seckey;
@@ -445,6 +452,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
       xfree (string);
     }
 
+  /* Parse the gcrypt PK algo and check that it is okay.  */
   gcry_sexp_release (list);
   list = gcry_sexp_find_token (top_list, "algo", 0);
   if (!list)
@@ -452,15 +460,52 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   string = gcry_sexp_nth_string (list, 1);
   if (!string)
     goto bad_seckey;
-  pubkey_algo = gcry_pk_map_name (string);
-  xfree (string);
-
-  if (gcry_pk_algo_info (pubkey_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
-      || gcry_pk_algo_info (pubkey_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
+  pk_algo = gcry_pk_map_name (string);
+  xfree (string); string = NULL;
+  if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
+      || gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
       || !npkey || npkey >= nskey || nskey > PUBKEY_MAX_NSKEY)
     goto bad_seckey;
-  pubkey_algo = map_pk_gcry_to_openpgp (pubkey_algo);
 
+  /* Check that the pubkey algo matches the one from the public key.  */
+  switch (canon_pk_algo (pk_algo))
+    {
+    case GCRY_PK_RSA:
+      if (!is_RSA (pk->pubkey_algo))
+        pk_algo = 0;  /* Does not match.  */
+      break;
+    case GCRY_PK_DSA:
+      if (!is_DSA (pk->pubkey_algo))
+        pk_algo = 0;  /* Does not match.  */
+      break;
+    case GCRY_PK_ELG:
+      if (!is_ELGAMAL (pk->pubkey_algo))
+        pk_algo = 0;  /* Does not match.  */
+      break;
+    case GCRY_PK_ECC:
+      if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
+        ;
+      else if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
+        is_ecdh = 1;
+      else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)
+        ;
+      else
+        pk_algo = 0;  /* Does not match.  */
+      /* For ECC we do not have the domain parameters thus fix our info.  */
+      npkey = 1;
+      nskey = 2;
+      break;
+    default:
+      pk_algo = 0;   /* Oops.  */
+      break;
+    }
+  if (!pk_algo)
+    {
+      err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+      goto leave;
+    }
+
+  /* Parse the key parameters.  */
   gcry_sexp_release (list);
   list = gcry_sexp_find_token (top_list, "skey", 0);
   if (!list)
@@ -509,7 +554,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
 
   gcry_sexp_release (list); list = NULL;
 
-  /* We have no need for the CSUM valuel thus we don't parse it.  */
+  /* We have no need for the CSUM value thus we don't parse it.  */
   /* list = gcry_sexp_find_token (top_list, "csum", 0); */
   /* if (list) */
   /*   { */
@@ -523,6 +568,14 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   /*   desired_csum = 0; */
   /* gcry_sexp_release (list); list = NULL; */
 
+  /* Get the curve name if any,  */
+  list = gcry_sexp_find_token (top_list, "curve", 0);
+  if (list)
+    {
+      curve = gcry_sexp_nth_string (list, 1);
+      gcry_sexp_release (list); list = NULL;
+    }
+
   gcry_sexp_release (top_list); top_list = NULL;
 
   /* log_debug ("XXX is_v4=%d\n", is_v4); */
@@ -559,57 +612,49 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
     }
 
   /* We need to change the received parameters for ECC algorithms.
-     The transfer format has all parameters but OpenPGP defines that
-     only the OID of the curve is to be used.  */
-  if (pubkey_algo == PUBKEY_ALGO_ECDSA
-      || pubkey_algo == PUBKEY_ALGO_EDDSA
-      || pubkey_algo == PUBKEY_ALGO_ECDH)
+     The transfer format has the curve name and the parameters
+     separate.  We put them all into the SKEY array.  */
+  if (canon_pk_algo (pk_algo) == GCRY_PK_ECC)
     {
-      gcry_sexp_t s_pubkey;
-      const char *curvename, *curveoidstr;
-      gcry_mpi_t mpi;
-
-      /* We build an S-expression with the public key parameters and
-         ask Libgcrypt to return the matching curve name.  */
-      if (npkey != 6 || !skey[0] || !skey[1] || !skey[2]
-          || !skey[3] || !skey[4] || !skey[5]
-          || !skey[6] || skey[7])
+      const char *oidstr;
+
+      /* Assert that all required parameters are available.  We also
+         check that the array does not contain more parameters than
+         needed (this was used by some beta versions of 2.1.  */
+      if (!curve || !skey[0] || !skey[1] || skey[2])
         {
           err = gpg_error (GPG_ERR_INTERNAL);
           goto leave;
         }
-      err = gcry_sexp_build (&s_pubkey, NULL,
-                             "(public-key(ecc(p%m)(a%m)(b%m)(g%m)(n%m)))",
-                             skey[0], skey[1], skey[2], skey[3], skey[4]);
-      if (err)
-        goto leave;
-      curvename = gcry_pk_get_curve (s_pubkey, 0, NULL);
-      gcry_sexp_release (s_pubkey);
-      curveoidstr = openpgp_curve_to_oid (curvename, NULL);
-      if (!curveoidstr)
+
+      oidstr = openpgp_curve_to_oid (curve, NULL);
+      if (!oidstr)
         {
-          log_error ("no OID known for curve '%s'\n", curvename);
-          err = gpg_error (GPG_ERR_UNKNOWN_NAME);
+          log_error ("no OID known for curve '%s'\n", curve);
+          err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
           goto leave;
         }
-      err = openpgp_oid_from_str (curveoidstr, &mpi);
+      /* Put the curve's OID into into the MPI array.  This requires
+         that we shift Q and D.  For ECDH also insert the KDF parms. */
+      if (is_ecdh)
+        {
+          skey[4] = NULL;
+          skey[3] = skey[1];
+          skey[2] = gcry_mpi_copy (pk->pkey[2]);
+        }
+      else
+        {
+          skey[3] = NULL;
+          skey[2] = skey[1];
+        }
+      skey[1] = skey[0];
+      skey[0] = NULL;
+      err = openpgp_oid_from_str (oidstr, skey + 0);
       if (err)
         goto leave;
-
-      /* Now replace the curve parameters by the OID and shift the
-         rest of the parameters.  */
-      gcry_mpi_release (skey[0]);
-      skey[0] = mpi;
-      for (idx=1; idx <= 4; idx++)
-        gcry_mpi_release (skey[idx]);
-      skey[1] = skey[5];
-      skey[2] = skey[6];
-      for (idx=3; idx <= 6; idx++)
-        skey[idx] = NULL;
-
       /* Fixup the NPKEY and NSKEY to match OpenPGP reality.  */
-      npkey = 2;
-      nskey = 3;
+      npkey = 2 + is_ecdh;
+      nskey = 3 + is_ecdh;
 
       /* for (idx=0; skey[idx]; idx++) */
       /*   { */
@@ -634,11 +679,6 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
       err = gpg_error (GPG_ERR_INV_DATA);
       goto leave;
     }
-  if (canon_pubkey_algo (pubkey_algo) != canon_pubkey_algo (pk->pubkey_algo))
-    {
-      err = gpg_error (GPG_ERR_PUBKEY_ALGO);
-      goto leave;
-    }
   err = openpgp_cipher_test_algo (protect_algo);
   if (err)
     goto leave;
@@ -695,6 +735,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   /* That's it.  */
 
  leave:
+  gcry_free (curve);
   gcry_sexp_release (list);
   gcry_sexp_release (top_list);
   for (idx=0; idx < skeyidx; idx++)