[PATCH] export smartcard stub (was: Private key transfer format)

NIIBE Yutaka gniibe at fsij.org
Thu Apr 9 13:52:45 CEST 2015


On 04/08/2015 05:40 PM, NIIBE Yutaka wrote:
> Hello,
> 
> I'm trying to fix the issue: https://bugs.g10code.com/gnupg/issue1937
> 
> Here, we need to enhance the OpenPGP Private Key Transfer Format.
> 
> Currently, as it is described in agent/keyformat.txt, it's like:
> 
> (openpgp-private-key
>   (version V)
>   (algo PUBKEYALGO)
>   (curve CURVENAME)
>   (skey _ P1 _ P2 _ P3 ... e PN)
>   (csum n)
>   (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT))
> 
> For private keys in smartcard, it can be something like following:
> 
> (openpgp-private-key
>   (version V)
>   (algo PUBKEYALGO)
>   (curve CURVENAME)
>   (skey _ P1 _ P2 _ P3 ... _ PN_minus_1)  # ??? pkey???
>   (csum n)
>   (shadowed PROTOCOL (INFO)))
> 
> How about this?

I implemented, but reading the implementation of GnuPG 2.0.x and
OpenPGP format for smartcard stub, I think that it is more natural
to represent it like:

(openpgp-private-key
  (version V)
  (algo PUBKEYALGO)
  (curve CURVENAME)
  (skey _ P1 _ P2 _ P3 ... _ PN)
  (csum n)
  (protection none plain SERIAL 101 none GNU 2))

Then, it will have 1-to-1 mapping to the representation in OpenPGP
format.

Here is the patch.  I tested and confirmed that GnuPG can export
secret key (with smartcard serial number).

diff --git a/agent/command.c b/agent/command.c
index 3188bbd..9fa2e90 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -2221,6 +2221,7 @@ cmd_export_key (assuan_context_t ctx, char *line)
   char *cache_nonce;
   char *passphrase = NULL;
   unsigned char *shadow_info = NULL;
+  char *serialno = NULL;
   char *pend;
   int c;

@@ -2271,9 +2272,10 @@ cmd_export_key (assuan_context_t ctx, char *line)
     goto leave;
   if (shadow_info)
     {
-      /* Key is on a smartcard.  */
-      err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
-      goto leave;
+      err = parse_shadow_info (shadow_info, &serialno, NULL, NULL);
+      xfree (shadow_info);
+      if (err)
+        goto leave;
     }

   if (openpgp)
@@ -2281,7 +2283,7 @@ cmd_export_key (assuan_context_t ctx, char *line)
       /* The openpgp option changes the key format into the OpenPGP
          key transfer format.  The result is already a padded
          canonical S-expression.  */
-      if (!passphrase)
+      if (!passphrase && !shadow_info)
         {
           err = agent_ask_new_passphrase
             (ctrl, _("This key (or subkey) is not protected with a passphrase."
@@ -2290,7 +2292,9 @@ cmd_export_key (assuan_context_t ctx, char *line)
           if (err)
             goto leave;
         }
-      err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
+
+      err = convert_to_openpgp (ctrl, s_skey, serialno, passphrase,
+                                &key, &keylen);
       if (!err && passphrase)
         {
           if (!cache_nonce)
@@ -2312,6 +2316,12 @@ cmd_export_key (assuan_context_t ctx, char *line)
     }
   else
     {
+      if (serialno)
+        {      /* Key is on a smartcard.  */
+          err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+          goto leave;
+        }
+
       /* Convert into a canonical S-expression and wrap that.  */
       err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
     }
@@ -2351,6 +2361,7 @@ cmd_export_key (assuan_context_t ctx, char *line)


  leave:
+  xfree (serialno);
   xfree (cache_nonce);
   xfree (passphrase);
   xfree (wrappedkey);
@@ -2359,7 +2370,6 @@ cmd_export_key (assuan_context_t ctx, char *line)
   gcry_sexp_release (s_skey);
   xfree (ctrl->server_local->keydesc);
   ctrl->server_local->keydesc = NULL;
-  xfree (shadow_info);

   return leave_cmd (ctx, err);
 }
diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c
index b00f032..bd75bd5 100644
--- a/agent/cvt-openpgp.c
+++ b/agent/cvt-openpgp.c
@@ -1391,9 +1391,13 @@ extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
 /* Convert our key S_KEY into an OpenPGP key transfer format.  On
    success a canonical encoded S-expression is stored at R_TRANSFERKEY
    and its length at R_TRANSFERKEYLEN; this S-expression is also
-   padded to a multiple of 64 bits.  */
+   padded to a multiple of 64 bits.
+   When SERIALNO is non-NULL, it's for smartcard serialno hex-string.
+   PASSPHRASE points to the memory for encrypting the private key.
+  */
 gpg_error_t
-convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
+convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key,
+                    const unsigned char *serialno, const char *passphrase,
                     unsigned char **r_transferkey, size_t *r_transferkeylen)
 {
   gpg_error_t err;
@@ -1402,8 +1406,10 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
   gcry_mpi_t array[10];
   gcry_sexp_t curve = NULL;
   gcry_sexp_t flags = NULL;
+  const char *proto_type, *proto_algo, *s2k_hash;
   char protect_iv[16];
   char salt[8];
+  unsigned int s2k_mode;
   unsigned long s2k_count;
   int i, j;

@@ -1414,32 +1420,49 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
   for (i=0; i < DIM (array); i++)
     array[i] = NULL;

-  err = extract_private_key (s_key, 1, &algoname, &npkey, &nskey, NULL,
-                             array, DIM (array), &curve, &flags);
+  err = extract_private_key (s_key, !serialno, &algoname, &npkey, &nskey,
+                             NULL, array, DIM (array), &curve, &flags);
   if (err)
     return err;

-  gcry_create_nonce (protect_iv, sizeof protect_iv);
-  gcry_create_nonce (salt, sizeof salt);
-  /* We need to use the encoded S2k count.  It is not possible to
-     encode it after it has been used because the encoding procedure
-     may round the value up.  */
-  s2k_count = get_standard_s2k_count_rfc4880 ();
-  err = apply_protection (array, npkey, nskey, passphrase,
-                          GCRY_CIPHER_AES, protect_iv, sizeof protect_iv,
-                          3, GCRY_MD_SHA1, salt, s2k_count);
+  if (serialno)
+    {
+      s2k_mode = 101;           /* GnuPG extension.  */
+      proto_type = "none";
+      proto_algo = "plain";
+      if (hex2bin (serialno, protect_iv, sizeof protect_iv) < 0)
+        err = gpg_error (GPG_ERR_BAD_SECKEY);
+      s2k_hash = "none";
+      strcpy (salt, "GNU");
+      s2k_count = 2;            /* Divert to OpenPGP smartcard.  */
+      array[npkey] = GCRYMPI_CONST_ONE;
+    }
+  else
+    {
+      s2k_mode = 3;
+      proto_type = "sha1";
+      proto_algo = "aes";
+      gcry_create_nonce (protect_iv, sizeof protect_iv);
+      s2k_hash = "sha1";
+      gcry_create_nonce (salt, sizeof salt);
+      /* We need to use the encoded S2k count.  It is not possible to
+         encode it after it has been used because the encoding procedure
+         may round the value up.  */
+      s2k_count = get_standard_s2k_count_rfc4880 ();
+      err = apply_protection (array, npkey, nskey, passphrase,
+                              GCRY_CIPHER_AES, protect_iv, sizeof protect_iv,
+                              3, GCRY_MD_SHA1, salt, s2k_count);
+    }
+
   /* Turn it into the transfer key S-expression.  Note that we always
      return a protected key.  */
   if (!err)
     {
-      char countbuf[35];
       membuf_t mbuf;
       void *format_args[10+2];
       gcry_sexp_t tmpkey;
       gcry_sexp_t tmpsexp = NULL;

-      snprintf (countbuf, sizeof countbuf, "%lu", s2k_count);
-
       init_membuf (&mbuf, 50);
       put_membuf_str (&mbuf, "(skey");
       for (i=j=0; i < npkey; i++)
@@ -1447,7 +1470,7 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
           put_membuf_str (&mbuf, " _ %m");
           format_args[j++] = array + i;
         }
-      put_membuf_str (&mbuf, " e %m");
+      put_membuf_str (&mbuf, serialno ? " _ %m" : " e %m");
       format_args[j++] = array + npkey;
       put_membuf_str (&mbuf, ")\n");
       put_membuf (&mbuf, "", 1);
@@ -1461,19 +1484,27 @@ convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase,
           err = gcry_sexp_build_array (&tmpkey, NULL, format, format_args);
         xfree (format);
       }
+
       if (!err)
-        err = gcry_sexp_build (&tmpsexp, NULL,
-                               "(openpgp-private-key\n"
-                               " (version 1:4)\n"
-                               " (algo %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,
-                               countbuf);
+        {
+          char countbuf[35];
+
+          snprintf (countbuf, sizeof countbuf, "%lu", s2k_count);
+          err = gcry_sexp_build (&tmpsexp, NULL,
+                                 "(openpgp-private-key\n"
+                                 " (version 1:4)\n"
+                                 " (algo %s)\n"
+                                 " %S%S\n"
+                                 " (protection %s %s %b %u %s %b %s))\n",
+                                 algoname,
+                                 curve,
+                                 tmpkey,
+                                 proto_type, proto_algo,
+                                 (int)sizeof protect_iv, protect_iv, s2k_mode,
+                                 s2k_hash, serialno ? 3: (int)sizeof salt, salt,
+                                 countbuf);
+        }
+
       gcry_sexp_release (tmpkey);
       if (!err)
         err = make_canon_sexp_pad (tmpsexp, 0, r_transferkey, r_transferkeylen);
diff --git a/agent/cvt-openpgp.h b/agent/cvt-openpgp.h
index d27a776..9bacb06 100644
--- a/agent/cvt-openpgp.h
+++ b/agent/cvt-openpgp.h
@@ -29,6 +29,7 @@ gpg_error_t convert_from_openpgp_native (ctrl_t ctrl,
                                          unsigned char **r_key);

 gpg_error_t convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key,
+                                const unsigned char *serialno,
                                 const char *passphrase,
                                 unsigned char **r_transferkey,
                                 size_t *r_transferkeylen);
diff --git a/agent/keyformat.txt b/agent/keyformat.txt
index 42c4b1f..56a7688 100644
--- a/agent/keyformat.txt
+++ b/agent/keyformat.txt
@@ -236,6 +236,11 @@ This format is used to transfer keys between gpg and gpg-agent.
 * S2KSALT is the 8 byte salt
 * S2KCOUNT is the count value from RFC-4880.

+For smartcard stub, it's:
+
+* PN is dummy data: 1 (and not encrypted)
+* PROTECTION is: (protection none plain SERIAL 101 none GNU 2)
+

 Persistent Passphrase Format
 ============================
diff --git a/doc/DETAILS b/doc/DETAILS
index fd72b88..6fd01e0 100644
--- a/doc/DETAILS
+++ b/doc/DETAILS
@@ -1137,6 +1137,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
   1 octet  - S2K Usage: either 254 or 255.
   1 octet  - S2K Cipher Algo: 0
   1 octet  - S2K Specifier: 101
+  1 octet  - S2K Hash Algorithm: 0
   3 octets - "GNU"
   1 octet  - GNU S2K Extension Number.

diff --git a/g10/export.c b/g10/export.c
index b65fb8d..5711021 100644
--- a/g10/export.c
+++ b/g10/export.c
@@ -449,6 +449,32 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
       s2k_count = strtoul (string, NULL, 10);
       xfree (string);
     }
+  else
+    { /* It's smartcard stub.  */
+      protect_algo = 0;
+      s2k_algo = 0;
+
+      value = gcry_sexp_nth_data (list, 3, &valuelen);
+      if (!value || !valuelen || valuelen > sizeof iv)
+        goto bad_seckey;
+      memcpy (iv, value, valuelen);
+      ivlen = valuelen;
+log_printhex ("XXX iv", iv, ivlen);
+      string = gcry_sexp_nth_string (list, 4);
+      if (!string)
+        goto bad_seckey;
+      s2k_mode = strtol (string, NULL, 10);
+      xfree (string);
+      value = gcry_sexp_nth_data (list, 6, &valuelen);
+      if (!value || !valuelen || valuelen > 3 || memcmp (value, "GNU", 3))
+        goto bad_seckey;
+      memset (s2k_salt, 0, sizeof s2k_salt);
+      string = gcry_sexp_nth_string (list, 7);
+      if (!string)
+        goto bad_seckey;
+      s2k_count = strtoul (string, NULL, 10);
+      xfree (string);
+    }

   /* Parse the gcrypt PK algo and check that it is okay.  */
   gcry_sexp_release (list);
@@ -606,7 +632,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   /*     log_printf ("\n"); */
   /*   } */

-  if (!is_v4 || is_protected != 2 )
+  if (!is_v4 || (is_protected != 0 && is_protected != 2) )
     {
       /* We only support the v4 format and a SHA-1 checksum.  */
       err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
@@ -675,18 +701,21 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
     }

   /* Do some sanity checks.  */
-  if (s2k_count > 255)
+  if (is_protected)
     {
-      /* We expect an already encoded S2K count.  */
-      err = gpg_error (GPG_ERR_INV_DATA);
-      goto leave;
+      if (s2k_count > 255)
+        {
+          /* We expect an already encoded S2K count.  */
+          err = gpg_error (GPG_ERR_INV_DATA);
+          goto leave;
+        }
+      err = openpgp_cipher_test_algo (protect_algo);
+      if (err)
+        goto leave;
+      err = openpgp_md_test_algo (s2k_algo);
+      if (err)
+        goto leave;
     }
-  err = openpgp_cipher_test_algo (protect_algo);
-  if (err)
-    goto leave;
-  err = openpgp_md_test_algo (s2k_algo);
-  if (err)
-    goto leave;

   /* Check that the public key parameters match.  Note that since
      Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly.  */
@@ -700,7 +729,7 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   /* Check that the first secret key parameter in SKEY is encrypted
      and that there are no more secret key parameters.  The latter is
      guaranteed by the v4 packet format.  */
-  if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE))
+  if (is_protected && !gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE))
     goto bad_seckey;
   if (npkey+1 < DIM (skey) && skey[npkey+1])
     goto bad_seckey;
@@ -721,18 +750,33 @@ transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
   ski->is_protected = 1;
   ski->sha1chk = 1;
   ski->algo = protect_algo;
-  ski->s2k.mode = s2k_mode;
+  if (is_protected == 0)
+    ski->s2k.mode = 1000 + s2k_count;
+  else
+    {
+      ski->s2k.mode = s2k_mode;
+      ski->s2k.count = s2k_count;
+    }
   ski->s2k.hash_algo = s2k_algo;
   assert (sizeof ski->s2k.salt == sizeof s2k_salt);
   memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt);
-  ski->s2k.count = s2k_count;
   assert (ivlen <= sizeof ski->iv);
   memcpy (ski->iv, iv, ivlen);
   ski->ivlen = ivlen;

   /* Store the protected secret key parameter.  */
-  pk->pkey[npkey] = skey[npkey];
-  skey[npkey] = NULL;
+  if (is_protected)
+    {
+      pk->pkey[npkey] = skey[npkey];
+      skey[npkey] = NULL;
+    }
+  else
+    {
+      pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
+                                             xstrdup ("dummydata"),
+                                             10 * 8);
+    }
+

   /* That's it.  */

--



More information about the Gnupg-devel mailing list