gpg: Allow direct key generation from card with --full-gen-key.
authorWerner Koch <wk@gnupg.org>
Tue, 2 Apr 2019 16:57:09 +0000 (18:57 +0200)
committerWerner Koch <wk@gnupg.org>
Tue, 2 Apr 2019 16:57:09 +0000 (18:57 +0200)
* g10/call-agent.c (agent_scd_readkey): New.
* g10/keygen.c (ask_key_flags): Factor code out to ..
(ask_key_flags_with_mask): new.
(ask_algo): New mode 14.
--

Note that this new menu 14 is always displayed.  The usage flags can
be changed only in --expert mode, though.  Creating and using signing
keys works but decryption does not yet work; we will need to tweak a
couple of other places for that.  Tested with a Yubikey's PIV app.

Signed-off-by: Werner Koch <wk@gnupg.org>
doc/DETAILS
doc/gpg-card.texi
g10/call-agent.c
g10/call-agent.h
g10/keygen.c

index 74a63ef..3046523 100644 (file)
@@ -1572,6 +1572,7 @@ Description of some debug flags:
    | ecc/*   | 11 | ECC (set your own capabilities) |
    | ecc/e   | 12 | ECC (encrypt only)              |
    | keygrip | 13 | Existing key                    |
+   | cardkey | 14 | Existing key from card          |
 
    If one of the "foo/*" names are used a "keygen.flags" prompt needs
    to be answered as well.  Instead of toggling the predefined flags,
index aa49f81..92379aa 100644 (file)
@@ -210,7 +210,7 @@ Key management ...: [none]
       keyref .....: PIV.9D
 @end example
 
-Note that the ``Displayed s/sn'' is printed on the token and also
+Note that the ``Displayed s/n'' is printed on the token and also
 shown in Pinentry prompts asking for the PIN.  The four standard key
 slots are always shown, if other key slots are initialized they are
 shown as well.  The @emph{PIV authentication} key (internal reference
@@ -231,11 +231,11 @@ which needs to be provided only once so that decryption operations can
 then be done until the card is reset or removed from the reader or USB
 port.
 
-We now generate tree of the four keys.  Note that GnuPG does currently
-not use the the @emph{Card authentication} key but because it is
-mandatory by the specs we create it anyway.  Key generation requires
-that we authenticate to the card.  This can be done either on the
-command line (which would reveal the key):
+We now generate three of the four keys.  Note that GnuPG does
+currently not use the the @emph{Card authentication} key; however,
+that key is mandatory by the PIV standard and thus we create it too.
+Key generation requires that we authenticate to the card.  This can be
+done either on the command line (which would reveal the key):
 
 @example
 gpg/card> auth 010203040506070801020304050607080102030405060708
@@ -360,7 +360,7 @@ gpgsm: total number processed: 1
 gpgsm:               imported: 1
 @end example
 
-Note the last steps which imported the created certificate.  If you
+Note the last step which imported the created certificate.  If you
 you instead created a certificate signing request (CSR) instead of a
 self-signed certificate and sent this off to a CA you would do the
 same import step with the certificate received from the CA.  Take note
index 83ca921..a0c5f81 100644 (file)
@@ -1251,6 +1251,49 @@ agent_scd_readcert (const char *certidstr,
 }
 
 
+/* This is a variant of agent_readkey which sends a READKEY command
+ * directly Scdaemon.  On success a new s-expression is stored at
+ * R_RESULT.  */
+gpg_error_t
+agent_scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result)
+{
+  gpg_error_t err;
+  char line[ASSUAN_LINELENGTH];
+  membuf_t data;
+  unsigned char *buf;
+  size_t len, buflen;
+  struct default_inq_parm_s dfltparm;
+
+  memset (&dfltparm, 0, sizeof dfltparm);
+  dfltparm.ctx = agent_ctx;
+
+  *r_result = NULL;
+  err = start_agent (NULL, 1);
+  if (err)
+    return err;
+
+  init_membuf (&data, 1024);
+  snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr);
+  err = assuan_transact (agent_ctx, line,
+                         put_membuf_cb, &data,
+                         default_inq_cb, &dfltparm,
+                         NULL, NULL);
+  if (err)
+    {
+      xfree (get_membuf (&data, &len));
+      return err;
+    }
+  buf = get_membuf (&data, &buflen);
+  if (!buf)
+    return gpg_error_from_syserror ();
+
+  err = gcry_sexp_new (r_result, buf, buflen, 0);
+  xfree (buf);
+
+  return err;
+}
+
+
 \f
 struct card_cardlist_parm_s {
   int error;
index 0a545b2..cb874fd 100644 (file)
@@ -114,10 +114,13 @@ int agent_scd_writecert (const char *certidstr,
 /* Send a GENKEY command to the SCdaemon. */
 int agent_scd_genkey (int keyno, int force, u32 *createtime);
 
-/* Send a READKEY command to the SCdaemon. */
+/* Send a READCERT command to the SCdaemon. */
 int agent_scd_readcert (const char *certidstr,
                         void **r_buf, size_t *r_buflen);
 
+/* Send a READKEY command to the SCdaemon.  */
+gpg_error_t agent_scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result);
+
 /* Change the PIN of an OpenPGP card or reset the retry counter. */
 int agent_scd_change_pin (int chvno, const char *serialno);
 
index 943b401..6ea4e72 100644 (file)
@@ -1881,24 +1881,26 @@ print_key_flags(int flags)
 
 
 /* Ask for the key flags and return them.  CURRENT gives the current
- * usage which should normally be given as 0. */
+ * usage which should normally be given as 0.  MASK gives the allowed
+ * flags.  */
 unsigned int
-ask_key_flags (int algo, int subkey, unsigned int current)
+ask_key_flags_with_mask (int algo, int subkey, unsigned int current,
+                         unsigned int mask)
 {
   /* TRANSLATORS: Please use only plain ASCII characters for the
-     translation.  If this is not possible use single digits.  The
-     string needs to 8 bytes long. Here is a description of the
-     functions:
-
-       s = Toggle signing capability
-       e = Toggle encryption capability
-       a = Toggle authentication capability
-       q = Finish
-  */
+   * translation.  If this is not possible use single digits.  The
+   * string needs to 8 bytes long. Here is a description of the
+   * functions:
+   *
+   *   s = Toggle signing capability
+   *   e = Toggle encryption capability
+   *   a = Toggle authentication capability
+   *   q = Finish
+   */
   const char *togglers = _("SsEeAaQq");
   char *answer = NULL;
   const char *s;
-  unsigned int possible = openpgp_pk_algo_usage(algo);
+  unsigned int possible;
 
   if ( strlen(togglers) != 8 )
     {
@@ -1907,22 +1909,26 @@ ask_key_flags (int algo, int subkey, unsigned int current)
       togglers = "11223300";
     }
 
-  /* Only primary keys may certify. */
-  if(subkey)
-    possible&=~PUBKEY_USAGE_CERT;
+  /* Mask the possible usage flags.  This is for example used for a
+   * card based key.  */
+  possible = (openpgp_pk_algo_usage (algo) & mask);
 
-  /* Preload the current set with the possible set, minus
-     authentication if CURRENT has been given as 0.  If CURRENT has
-     been has non-zero we mask with all possible usages. */
+  /* However, only primary keys may certify. */
+  if (subkey)
+    possible &= ~PUBKEY_USAGE_CERT;
+
+  /* Preload the current set with the possible set, without
+   * authentication if CURRENT is 0.  If CURRENT is non-zero we mask
+   * with all possible usages.  */
   if (current)
     current &= possible;
   else
     current = (possible&~PUBKEY_USAGE_AUTH);
 
-  for(;;)
+  for (;;)
     {
       tty_printf("\n");
-      tty_printf(_("Possible actions for a %s key: "),
+      tty_printf(_("Possible actions for this %s key: "),
                  (algo == PUBKEY_ALGO_ECDSA
                   || algo == PUBKEY_ALGO_EDDSA)
                  ? "ECDSA/EdDSA" : openpgp_pk_algo_name (algo));
@@ -2009,6 +2015,13 @@ ask_key_flags (int algo, int subkey, unsigned int current)
 }
 
 
+unsigned int
+ask_key_flags (int algo, int subkey, unsigned int current)
+{
+  return ask_key_flags_with_mask (algo, subkey, current, ~0);
+}
+
+
 /* Check whether we have a key for the key with HEXGRIP.  Returns 0 if
    there is no such key or the OpenPGP algo number for the key.  */
 static int
@@ -2047,10 +2060,12 @@ static int
 ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage,
           char **r_keygrip)
 {
+  gpg_error_t err;
   char *keygrip = NULL;
   char *answer = NULL;
   int algo;
   int dummy_algo;
+  char *p;
 
   if (!r_subkey_algo)
     r_subkey_algo = &dummy_algo;
@@ -2101,6 +2116,8 @@ ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage,
 
   if (opt.expert && r_keygrip)
     tty_printf (_("  (%d) Existing key\n"), 13 );
+  if (r_keygrip)
+    tty_printf (_("  (%d) Existing key from card\n"), 14 );
 
   for (;;)
     {
@@ -2221,9 +2238,130 @@ ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage,
           *r_usage = ask_key_flags (algo, addmode, 0);
           break;
        }
+      else if ((algo == 14 || !strcmp (answer, "cardkey")) && r_keygrip)
+        {
+          char *serialno;
+          strlist_t keypairlist, sl;
+          int count, selection;
+
+          err = agent_scd_serialno (&serialno, NULL);
+          if (err)
+            {
+              tty_printf (_("error reading the card: %s\n"),
+                          gpg_strerror (err));
+              goto ask_again;
+            }
+          tty_printf (_("Serial number of the card: %s\n"), serialno);
+          xfree (serialno);
+
+          err = agent_scd_keypairinfo (ctrl, &keypairlist);
+          if (err)
+            {
+              tty_printf (_("error reading the card: %s\n"),
+                          gpg_strerror (err));
+              goto ask_again;
+            }
+
+          do
+            {
+              tty_printf (_("Available keys:\n"));
+              for (count=1,sl=keypairlist; sl; sl = sl->next, count++)
+                {
+                  gcry_sexp_t s_pkey;
+                  char *algostr = NULL;
+                  enum gcry_pk_algos algoid = 0;
+                  const char *keyref;
+                  int any = 0;
+
+                  keyref = strchr (sl->d, ' ');
+                  if (keyref)
+                    {
+                      keyref++;
+                      if (!agent_scd_readkey (keyref, &s_pkey))
+                        {
+                          algostr = pubkey_algo_string (s_pkey, &algoid);
+                          gcry_sexp_release (s_pkey);
+                        }
+                    }
+                  /* We use the flags also encode the algo for use
+                   * below.  We need to tweak the algo in case
+                   * GCRY_PK_ECC is returned becuase pubkey_algo_string
+                   * is not aware of the OpenPGP algo mapping.
+                   * FIXME: This is an ugly hack. */
+                  sl->flags &= 0xff;
+                  if (algoid == GCRY_PK_ECC
+                      && algostr && !strncmp (algostr, "nistp", 5)
+                      && !(sl->flags & GCRY_PK_USAGE_ENCR))
+                    sl->flags |= (PUBKEY_ALGO_ECDSA << 8);
+                  else
+                    sl->flags |= (map_pk_gcry_to_openpgp (algoid) << 8);
+
+                  tty_printf ("   (%d) %s %s", count, sl->d, algostr);
+                  if ((sl->flags & GCRY_PK_USAGE_CERT))
+                    {
+                      tty_printf ("%scert", any?",":" (");
+                      any = 1;
+                    }
+                  if ((sl->flags & GCRY_PK_USAGE_SIGN))
+                    {
+                      tty_printf ("%ssign", any?",":" (");
+                      any = 1;
+                    }
+                  if ((sl->flags & GCRY_PK_USAGE_AUTH))
+                    {
+                      tty_printf ("%sauth", any?",":" (");
+                      any = 1;
+                    }
+                  if ((sl->flags & GCRY_PK_USAGE_ENCR))
+                    {
+                      tty_printf ("%sencr", any?",":" (");
+                      any = 1;
+                    }
+                  tty_printf ("%s\n", any?")":"");
+                  xfree (algostr);
+                }
+
+              xfree (answer);
+              answer = cpr_get ("keygen.cardkey", _("Your selection? "));
+              cpr_kill_prompt ();
+              trim_spaces (answer);
+              selection = atoi (answer);
+            }
+          while (!(selection > 0 && selection < count));
+
+          for (count=1,sl=keypairlist; sl; sl = sl->next, count++)
+            if (count == selection)
+              break;
+          if (!sl)
+            {
+              /* Just in case COUNT is zero (no keys).  */
+              free_strlist (keypairlist);
+              goto ask_again;
+            }
+
+          xfree (keygrip);
+          keygrip = xstrdup (sl->d);
+          if ((p = strchr (keygrip, ' ')))
+            *p = 0;
+          algo = (sl->flags >>8);
+          if (opt.expert)
+            *r_usage = ask_key_flags_with_mask (algo, addmode,
+                                                (sl->flags & 0xff),
+                                                (sl->flags & 0xff));
+          else
+            {
+              *r_usage = (sl->flags & 0xff);
+              if (addmode)
+                *r_usage &= ~GCRY_PK_USAGE_CERT;
+            }
+          free_strlist (keypairlist);
+          break;
+       }
       else
         tty_printf (_("Invalid selection.\n"));
 
+    ask_again:
+      ;
     }
 
   xfree(answer);