[PATCH v2 3/4] g10: allow receiving cleartext secret keys from agent

Daniel Kahn Gillmor dkg at fifthhorseman.net
Fri Jun 10 22:15:35 CEST 2016


* g10/export.c (match_curve_skey_pk): new function, testing whether an
  OpenPGP public key and an S-expression use the same curve.
* g10/export.c (cleartext_secret_key_to_openpgp): new function,
  filling in the secret key parameters of a PKT_public_key object from
  a corresponding cleartext S-expression.
* g10/export.c, g10/main.h (receive_seckey_from_agent): add cleartext
  parameter, enabling retrieval of the secret key, unlocked.
* g10/export.c (do_export_stream): send cleartext as 0, keeping current
  behavior.
* g10/keygen.c (card_store_key_with_backup): use cleartext=0 to ensure
  that smartcard backups are all passphrase-locked.

This sets up internal functionality to be capable of exporting
cleartext secret keys, but does not change any existing behavior.

--

Signed-off-by: Daniel Kahn Gillmor <dkg at fifthhorseman.net>
---
 g10/export.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 g10/keygen.c |   2 +-
 g10/main.h   |   1 +
 3 files changed, 223 insertions(+), 4 deletions(-)

diff --git a/g10/export.c b/g10/export.c
index 5b161ae..25a3319 100644
--- a/g10/export.c
+++ b/g10/export.c
@@ -390,6 +390,71 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node)
   return result;
 }
 
+/* return an error if the key represented by the S-expression s_key
+   and the OpenPGP key represented by pk do not use the same curve. */
+static gpg_error_t
+match_curve_skey_pk (gcry_sexp_t s_key, PKT_public_key *pk)
+{
+  gcry_sexp_t curve = NULL, flags = NULL;
+  char *curve_str = NULL, *flag;
+  const char *oidstr = NULL;
+  gcry_mpi_t curve_as_mpi = NULL;
+  gpg_error_t err;
+  int is_eddsa = 0, idx = 0;
+
+  if (!(pk->pubkey_algo==PUBKEY_ALGO_ECDH ||
+        pk->pubkey_algo==PUBKEY_ALGO_ECDSA ||
+        pk->pubkey_algo==PUBKEY_ALGO_EDDSA))
+    return gpg_error (GPG_ERR_PUBKEY_ALGO);
+
+  curve = gcry_sexp_find_token (s_key, "curve", 0);
+  if (!curve)
+    {
+      log_error ("no reported curve\n");
+      err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+    }
+  curve_str = gcry_sexp_nth_string (curve, 1);
+  gcry_sexp_release (curve); curve = NULL;
+  if (!curve_str)
+    {
+      log_error ("no curve name\n");
+      return gpg_error (GPG_ERR_UNKNOWN_CURVE);
+    }
+  oidstr = openpgp_curve_to_oid (curve_str, NULL);
+  if (!oidstr)
+    {
+      log_error ("no OID known for curve '%s'\n", curve_str);
+      gcry_free (curve_str);
+      return gpg_error (GPG_ERR_UNKNOWN_CURVE);
+    }
+  gcry_free (curve_str);
+  err = openpgp_oid_from_str (oidstr, &curve_as_mpi);
+  if (err)
+    return err;
+  if (gcry_mpi_cmp(pk->pkey[0], curve_as_mpi))
+    {
+      log_error ("curves do not match\n");
+      err = gpg_error (GPG_ERR_INV_CURVE);
+    }
+  gcry_mpi_release (curve_as_mpi);
+  flags = gcry_sexp_find_token (s_key, "flags", 0);
+  if (flags)
+    for (idx = 1; idx < gcry_sexp_length (flags); idx++)
+      {
+        flag = gcry_sexp_nth_string (flags, idx);
+        if (flag && (strcmp ("eddsa", flag) == 0))
+          is_eddsa = 1;
+        gcry_free (flag);
+      }
+  if (is_eddsa !=
+      (pk->pubkey_algo==PUBKEY_ALGO_EDDSA))
+    {
+      log_error ("disagreement about EdDSA\n");
+      err = gpg_error (GPG_ERR_INV_CURVE);
+    }
+
+  return err;
+}
 
 /* Return a canonicalized public key algoithms.  This is used to
    compare different flavors of algorithms (e.g. ELG and ELG_E are
@@ -411,6 +476,150 @@ canon_pk_algo (enum gcry_pk_algos algo)
     }
 }
 
+/* take a cleartext dump of a secret key in PK and change the
+   parameter array in PK to include the secret parameters.  */
+static gpg_error_t
+cleartext_secret_key_to_openpgp (gcry_sexp_t s_key, PKT_public_key *pk)
+{
+  gpg_error_t err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  gcry_sexp_t top_list;
+  gcry_sexp_t key = NULL;
+  char *key_type = NULL;
+  enum gcry_pk_algos pk_algo;
+  struct seckey_info *ski;
+  int idx, sec_start;
+  gcry_mpi_t pub_params[10] = { NULL };
+
+  /* we look for a private-key, then the first element in it tells us
+     the type */
+  top_list = gcry_sexp_find_token (s_key, "private-key", 0);
+  if (!top_list)
+    goto bad_seckey;
+  if (gcry_sexp_length(top_list) != 2)
+    goto bad_seckey;
+  key = gcry_sexp_nth (top_list, 1);
+  if (!key)
+    goto bad_seckey;
+  key_type = gcry_sexp_nth_string(key, 0);
+  pk_algo = gcry_pk_map_name (key_type);
+
+  log_assert(pk->seckey_info == NULL);
+
+  pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
+  if (!ski)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  switch (canon_pk_algo (pk_algo))
+    {
+    case GCRY_PK_RSA:
+      if (!is_RSA (pk->pubkey_algo))
+        goto bad_pubkey_algo;
+      err = gcry_sexp_extract_param (key, NULL, "ne",
+                                     &pub_params[0],
+                                     &pub_params[1],
+                                     NULL);
+      for (idx=0; idx < 2 && !err; idx++)
+        if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
+          err = gpg_error (GPG_ERR_BAD_PUBKEY);
+      if (!err)
+        err = gcry_sexp_extract_param (key, NULL, "dpqu",
+                                       &pk->pkey[2],
+                                       &pk->pkey[3],
+                                       &pk->pkey[4],
+                                       &pk->pkey[5],
+                                       NULL);
+      if (!err)
+        for (idx = 2; idx < 6; idx++)
+          ski->csum += checksum_mpi (pk->pkey[idx]);
+      break;
+
+    case GCRY_PK_DSA:
+      if (!is_DSA (pk->pubkey_algo))
+        goto bad_pubkey_algo;
+      err = gcry_sexp_extract_param (key, NULL, "pqgy",
+                                     &pub_params[0],
+                                     &pub_params[1],
+                                     &pub_params[2],
+                                     &pub_params[3],
+                                     NULL);
+      for (idx=0; idx < 4 && !err; idx++)
+        if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
+          err = gpg_error (GPG_ERR_BAD_PUBKEY);
+      if (!err)
+        err = gcry_sexp_extract_param (key, NULL, "x",
+                                       &pk->pkey[4],
+                                       NULL);
+      if (!err)
+        ski->csum += checksum_mpi (pk->pkey[4]);
+      break;
+
+    case GCRY_PK_ELG:
+      if (!is_ELGAMAL (pk->pubkey_algo))
+        goto bad_pubkey_algo;
+      err = gcry_sexp_extract_param (key, NULL, "pgy",
+                                     &pub_params[0],
+                                     &pub_params[1],
+                                     &pub_params[2],
+                                     NULL);
+      for (idx=0; idx < 3 && !err; idx++)
+        if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
+          err = gpg_error (GPG_ERR_BAD_PUBKEY);
+      if (!err)
+        err = gcry_sexp_extract_param (key, NULL, "x",
+                                       &pk->pkey[3],
+                                       NULL);
+      if (!err)
+        ski->csum += checksum_mpi (pk->pkey[3]);
+      break;
+
+    case GCRY_PK_ECC:
+      err = match_curve_skey_pk (key, pk);
+      if (err)
+        goto leave;
+      if (!err)
+        err = gcry_sexp_extract_param (key, NULL, "q",
+                                       &pub_params[0],
+                                       NULL);
+      if (!err && (gcry_mpi_cmp(pk->pkey[1], pub_params[0])))
+        err = gpg_error (GPG_ERR_BAD_PUBKEY);
+
+      sec_start = 2;
+      if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
+        sec_start += 1;
+      if (!err)
+        err = gcry_sexp_extract_param (key, NULL, "d",
+                                       &pk->pkey[sec_start],
+                                       NULL);
+
+      if (!err)
+        ski->csum += checksum_mpi (pk->pkey[sec_start]);
+      break;
+
+    default:
+      pk->seckey_info = NULL;
+      free (ski);
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+    }
+ leave:
+  gcry_sexp_release (top_list);
+  gcry_sexp_release (key);
+  gcry_free (key_type);
+
+  for (idx=0; idx < DIM(pub_params); idx++)
+    gcry_mpi_release (pub_params[idx]);
+  return err;
+
+ bad_pubkey_algo:
+  err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+  goto leave;
+
+ bad_seckey:
+  err = gpg_error (GPG_ERR_BAD_SECKEY);
+  goto leave;
+}
 
 /* Use the key transfer format given in S_PGP to create the secinfo
    structure in PK and change the parameter array in PK to include the
@@ -833,10 +1042,15 @@ print_status_exported (PKT_public_key *pk)
  * Then, parse the decrypted key data in transfer format, and put
  * secret parameters into PK.
  *
+ * if CLEARTEXT is 0, store the secret key material
+ * passphrase-protected.  otherwise, store secret key material in the
+ * clear.
+ *
  * CACHE_NONCE_ADDR is used to share nonce for multple key retrievals.
  */
 gpg_error_t
 receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
+                           int cleartext,
                            char **cache_nonce_addr, const char *hexgrip,
                            PKT_public_key *pk)
 {
@@ -852,7 +1066,7 @@ receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
     log_info ("key %s: asking agent for the secret parts\n", hexgrip);
 
   prompt = gpg_format_keydesc (pk, FORMAT_KEYDESC_EXPORT,1);
-  err = agent_export_key (ctrl, hexgrip, prompt, 1, cache_nonce_addr,
+  err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, cache_nonce_addr,
                           &wrappedkey, &wrappedkeylen);
   xfree (prompt);
 
@@ -880,7 +1094,10 @@ receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
   err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen);
   if (!err)
     {
-      err = transfer_format_to_openpgp (s_skey, pk);
+      if (cleartext)
+        err = cleartext_secret_key_to_openpgp (s_skey, pk);
+      else
+        err = transfer_format_to_openpgp (s_skey, pk);
       gcry_sexp_release (s_skey);
     }
 
@@ -1276,7 +1493,8 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
                 }
               else if (!err)
                 {
-                  err = receive_seckey_from_agent (ctrl, cipherhd, &cache_nonce,
+                  err = receive_seckey_from_agent (ctrl, cipherhd,
+                                                   0, &cache_nonce,
                                                    hexgrip, pk);
                   if (err)
                     {
diff --git a/g10/keygen.c b/g10/keygen.c
index b8e4cb8..afb13e0 100644
--- a/g10/keygen.c
+++ b/g10/keygen.c
@@ -4075,7 +4075,7 @@ card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk,
       goto leave;
     }
 
-  err = receive_seckey_from_agent (ctrl, cipherhd, &cache_nonce, hexgrip, sk);
+  err = receive_seckey_from_agent (ctrl, cipherhd, 0, &cache_nonce, hexgrip, sk);
   if (err)
     {
       log_error ("error getting secret key from agent: %s\n", gpg_strerror (err));
diff --git a/g10/main.h b/g10/main.h
index bda0bc1..7b716ff 100644
--- a/g10/main.h
+++ b/g10/main.h
@@ -389,6 +389,7 @@ gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec,
                                   void **r_data, size_t *r_datalen);
 
 gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
+                                       int cleartext,
                                        char **cache_nonce_addr, const char *hexgrip,
                                        PKT_public_key *pk);
 
-- 
2.8.1




More information about the Gnupg-devel mailing list