[git] GnuPG - branch, master, updated. gnupg-2.2.7-368-g53beea5

by Werner Koch cvs at cvs.gnupg.org
Fri Feb 8 17:06:15 CET 2019


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "The GNU Privacy Guard".

The branch, master has been updated
       via  53beea56afecde76f0f4ca93fc50ca59298a093e (commit)
      from  0328976c94adc2c518c7a7763a35319a0000c5e2 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 53beea56afecde76f0f4ca93fc50ca59298a093e
Author: Werner Koch <wk at gnupg.org>
Date:   Fri Feb 8 16:46:52 2019 +0100

    scd: Implement RSA signing for PIV cards.
    
    * scd/app-piv.c (concat_tlv_list): New.
    (get_key_algorithm_by_dobj): Rename args for clarity.
    (do_auth): factor all code out to ...
    (do_sign): new.  Implement RSA signing.
    
    Signed-off-by: Werner Koch <wk at gnupg.org>

diff --git a/scd/app-piv.c b/scd/app-piv.c
index 4387b3a..1d70db5 100644
--- a/scd/app-piv.c
+++ b/scd/app-piv.c
@@ -469,6 +469,107 @@ add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
 }
 
 
+/* Function to build a list of TLV and return the result in a mallcoed
+ * buffer.  The varargs are tuples of (int,size_t,void) each with the
+ * tag, the length and the actual data.  A (0,0,NULL) tuple terminates
+ * the list.  Up to 10 tuples are supported.  */
+static gpg_error_t
+concat_tlv_list (unsigned char **r_result, size_t *r_resultlen, ...)
+{
+  gpg_error_t err;
+  va_list arg_ptr;
+  struct {
+    int tag;
+    unsigned int len;
+    unsigned int contlen;
+    const void *data;
+  } argv[10];
+  int i, j, argc;
+  unsigned char *data = NULL;
+  size_t datalen;
+  unsigned char *p;
+  size_t n;
+
+  *r_result = NULL;
+  *r_resultlen = 0;
+
+  /* Collect all args.  Check that length is <= 2^16 to match the
+   * behaviour of add_tlv.  */
+  va_start (arg_ptr, r_resultlen);
+  argc = 0;
+  while (((argv[argc].tag = va_arg (arg_ptr, int))))
+    {
+      argv[argc].len = va_arg (arg_ptr, size_t);
+      argv[argc].contlen = 0;
+      argv[argc].data = va_arg (arg_ptr, const void *);
+      if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
+        {
+          va_end (arg_ptr);
+          err = gpg_error (GPG_ERR_EINVAL);
+          goto leave;
+        }
+      argc++;
+    }
+  va_end (arg_ptr);
+
+  /* Compute the required buffer length and allocate the buffer.  */
+  datalen = 0;
+  for (i=0; i < argc; i++)
+    {
+      if (!argv[i].len && !argv[i].data)
+        {
+          /* Constructed tag.  Compute its length.  Note that we
+           * currently allow only one constructed tag in the list.  */
+          for (n=0, j = i + 1; j < argc; j++)
+            {
+              log_assert (!(!argv[j].len && !argv[j].data));
+              n += add_tlv (NULL, argv[j].tag, argv[j].len);
+              n += argv[j].len;
+            }
+          argv[i].contlen = n;
+          datalen += add_tlv (NULL, argv[i].tag, n);
+        }
+      else
+        {
+          datalen += add_tlv (NULL, argv[i].tag, argv[i].len);
+          datalen += argv[i].len;
+        }
+    }
+  data = xtrymalloc (datalen);
+  if (!data)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  /* Copy that data to the buffer.  */
+  p = data;
+  for (i=0; i < argc; i++)
+    {
+      if (!argv[i].len && !argv[i].data)
+        {
+          /* Constructed tag.  */
+          p += add_tlv (p, argv[i].tag, argv[i].contlen);
+        }
+      else
+        {
+          p += add_tlv (p, argv[i].tag, argv[i].len);
+          memcpy (p, argv[i].data, argv[i].len);
+          p += argv[i].len;
+        }
+    }
+  log_assert ( data + datalen == p );
+  *r_result = data;
+  data = NULL;
+  *r_resultlen = datalen;
+  err = 0;
+
+ leave:
+  xfree (data);
+  return err;
+}
+
+
 /* Wrapper around iso7816_put_data_odd which also sets the tag into
  * the '5C' data object.  The varargs are tuples of (int,size_t,void)
  * with the tag, the length and the actual data.  A (0,0,NULL) tuple
@@ -1354,7 +1455,7 @@ do_readkey (app_t app, int advanced, const char *keyrefstr,
  * store it at R_ALGO.  The algorithm is taken from the corresponding
  * certificate or from a cache.  */
 static gpg_error_t
-get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
+get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_mechanism)
 {
   gpg_error_t err;
   unsigned char *certbuf = NULL;
@@ -1369,7 +1470,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
   size_t n;
   const char *curve_name;
 
-  *r_algo = 0;
+  *r_mechanism = 0;
 
   err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism);
   if (err)
@@ -1382,7 +1483,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
         case PIV_ALGORITHM_RSA:
         case PIV_ALGORITHM_ECC_P256:
         case PIV_ALGORITHM_ECC_P384:
-          *r_algo = mechanism;
+          *r_mechanism = mechanism;
           break;
 
         default:
@@ -1468,7 +1569,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
                  dobj->keyref, algoname, gpg_strerror (err));
       goto leave;
     }
-  *r_algo = algo;
+  *r_mechanism = algo;
 
  leave:
   gcry_free (algoname);
@@ -1862,10 +1963,11 @@ do_check_chv (app_t app, const char *pwidstr,
  * stored there and an error code returned.  For ECDSA the result is
  * the simple concatenation of R and S without any DER encoding.  R
  * and S are left extended with zeroes to make sure they have an equal
- * length.
+ * length.  If HASHALGO is not zero, the function prepends the hash's
+ * OID to the indata or checks that it is consistent.
  */
 static gpg_error_t
-do_auth (app_t app, const char *keyidstr,
+do_sign (app_t app, const char *keyidstr, int hashalgo,
          gpg_error_t (*pincb)(void*, const char *, char **),
          void *pincb_arg,
          const void *indata_arg, size_t indatalen,
@@ -1874,13 +1976,16 @@ do_auth (app_t app, const char *keyidstr,
   const unsigned char *indata = indata_arg;
   gpg_error_t err;
   data_object_t dobj;
-  unsigned char tmpl[2+2+2+128];
-  size_t tmpllen;
+  unsigned char oidbuf[64];
+  size_t oidbuflen;
   unsigned char *outdata = NULL;
   size_t outdatalen;
   const unsigned char *s;
   size_t n;
-  int keyref, algo;
+  int keyref, mechanism;
+  unsigned char *indata_buffer = NULL; /* Malloced helper.  */
+  unsigned char *apdudata = NULL;
+  size_t apdudatalen;
 
   if (!keyidstr || !*keyidstr)
     {
@@ -1888,9 +1993,6 @@ do_auth (app_t app, const char *keyidstr,
       goto leave;
     }
 
-  /* Fixme: Shall we support the KEYID/FINGERPRINT syntax?  Does it
-   * make sense for X.509 certs?  */
-
   dobj = find_dobj_by_keyref (app, keyidstr);
   if ((keyref = keyref_from_dobj (dobj)) == -1)
     {
@@ -1898,69 +2000,141 @@ do_auth (app_t app, const char *keyidstr,
       goto leave;
     }
 
-  err = get_key_algorithm_by_dobj (app, dobj, &algo);
+  err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
   if (err)
     goto leave;
 
-  /* We need to remove the ASN.1 prefix from INDATA.  We use TEMPL as
-   * a temporary buffer for the OID.  */
-  if (algo == PIV_ALGORITHM_ECC_P256)
+   /* For ECC we need to remove the ASN.1 prefix from INDATA.  For RSA
+    * we need to add the padding and possible also the ASN.1 prefix.  */
+  if (mechanism == PIV_ALGORITHM_ECC_P256
+      || mechanism == PIV_ALGORITHM_ECC_P384)
     {
-      tmpllen = sizeof tmpl;
-      err = gcry_md_get_asnoid (GCRY_MD_SHA256, &tmpl, &tmpllen);
-      if (err)
+      int need_algo, need_digestlen;
+
+      if (mechanism == PIV_ALGORITHM_ECC_P256)
         {
-          err = gpg_error (GPG_ERR_INTERNAL);
-          log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA256);
-          goto leave;
+          need_algo = GCRY_MD_SHA256;
+          need_digestlen = 32;
         }
-      if (indatalen != tmpllen + 32 || memcmp (indata, tmpl, tmpllen))
+      else
         {
-          err = GPG_ERR_INV_VALUE;
-          log_error ("piv: bad formatted input for ECC-P256 auth\n");
+          need_algo = GCRY_MD_SHA384;
+          need_digestlen = 48;
+        }
+
+      if (hashalgo && hashalgo != need_algo)
+        {
+          err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+          log_error ("piv: hash algo %d does not match mechanism %d\n",
+                     need_algo, mechanism);
           goto leave;
         }
-      indata +=tmpllen;
-      indatalen -= tmpllen;
+
+      if (indatalen > need_digestlen)
+        {
+          oidbuflen = sizeof oidbuf;
+          err = gcry_md_get_asnoid (need_algo, &oidbuf, &oidbuflen);
+          if (err)
+            {
+              err = gpg_error (GPG_ERR_INTERNAL);
+              log_debug ("piv: no OID for hash algo %d\n", need_algo);
+              goto leave;
+            }
+          if (indatalen != oidbuflen + need_digestlen
+              || memcmp (indata, oidbuf, oidbuflen))
+            {
+              err = gpg_error (GPG_ERR_INV_VALUE);
+              log_error ("piv: bad input for signing with mechanism %d\n",
+                         mechanism);
+              goto leave;
+            }
+          indata += oidbuflen;
+          indatalen -= oidbuflen;
+        }
     }
-  else if (algo == PIV_ALGORITHM_ECC_P384)
+  else if (mechanism == PIV_ALGORITHM_RSA)
     {
-      tmpllen = sizeof tmpl;
-      err = gcry_md_get_asnoid (GCRY_MD_SHA384, &tmpl, &tmpllen);
-      if (err)
+      /* PIV requires 2048 bit RSA.  */
+      unsigned int framelen = 2048 / 8;
+      unsigned char *frame;
+      int i;
+
+      oidbuflen = sizeof oidbuf;
+      if (!hashalgo)
         {
-          err = gpg_error (GPG_ERR_INTERNAL);
-          log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA384);
+          /* We assume that indata already has the required
+           * digestinfo; thus merely prepend the padding below.  */
+        }
+      else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen)))
+        {
+          log_debug ("piv: no OID for hash algo %d\n", hashalgo);
           goto leave;
         }
-      if (indatalen != tmpllen + 48 || memcmp (indata, tmpl, tmpllen))
+      else
         {
-          err = GPG_ERR_INV_VALUE;
-          log_error ("piv: bad formatted input for ECC-P384 auth\n");
+          unsigned int digestlen = gcry_md_get_algo_dlen (hashalgo);
+
+          if (indatalen == digestlen)
+            {
+              /* Plain hash in INDATA; prepend the digestinfo.  */
+              indata_buffer = xtrymalloc (oidbuflen + indatalen);
+              if (!indata_buffer)
+                {
+                  err = gpg_error_from_syserror ();
+                  goto leave;
+                }
+              memcpy (indata_buffer, oidbuf, oidbuflen);
+              memcpy (indata_buffer+oidbuflen, indata, indatalen);
+              indata = indata_buffer;
+              indatalen = oidbuflen + indatalen;
+            }
+          else if (indatalen == oidbuflen + digestlen
+                   && !memcmp (indata, oidbuf, oidbuflen))
+            ; /* Correct prefix.  */
+          else
+            {
+              err = gpg_error (GPG_ERR_INV_VALUE);
+              log_error ("piv: bad input for signing with RSA and hash %d\n",
+                         hashalgo);
+              goto leave;
+            }
+        }
+      /* Now prepend the pkcs#v1.5 padding.  We require at least 8
+       * byte of padding and 3 extra bytes for the prefix and the
+       * delimiting nul.  */
+      if (!indatalen || indatalen + 8 + 4 > framelen)
+        {
+          err = gpg_error (GPG_ERR_INV_VALUE);
+          log_error ("piv: input does not fit into a %u bit PKCS#v1.5 frame\n",
+                     8*framelen);
           goto leave;
         }
-      indata += tmpllen;
-      indatalen -= tmpllen;
-    }
-  else if (algo == PIV_ALGORITHM_RSA)
-    {
-      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
-      log_error ("piv: FIXME: implement RSA authentication\n");
-      goto leave;
+      frame = xtrymalloc (framelen);
+      if (!frame)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      n = 0;
+      frame[n++] = 0;
+      frame[n++] = 1; /* Block type. */
+      i = framelen - indatalen - 3 ;
+      memset (frame+n, 0xff, i);
+      n += i;
+      frame[n++] = 0; /* Delimiter.  */
+      memcpy (frame+n, indata, indatalen);
+      n += indatalen;
+      log_assert (n == framelen);
+      /* And now put it into the indata_buffer.  */
+      xfree (indata_buffer);
+      indata_buffer = frame;
+      indata = indata_buffer;
+      indatalen = framelen;
     }
   else
     {
       err = gpg_error (GPG_ERR_INTERNAL);
-      log_debug ("piv: unknown PIV  algo %d from helper function\n", algo);
-      goto leave;
-    }
-
-  /* Because we don't have a dynamic template builder we make sure
-   * that we can encode all lengths in one octet.  FIXME: Use add_tls
-   * from app-openpgp as a base for an strconcat like function. */
-  if (indatalen >= 100)
-    {
-      err = gpg_error (GPG_ERR_TOO_LARGE);
+      log_debug ("piv: unknown PIV mechanism %d while signing\n", mechanism);
       goto leave;
     }
 
@@ -1970,19 +2144,18 @@ do_auth (app_t app, const char *keyidstr,
     return err;
 
   /* Build the Dynamic Authentication Template.  */
-  tmpl[0] = 0x7c;
-  tmpl[1] = indatalen + 4;
-  tmpl[2] = 0x82; /* Response. */
-  tmpl[3] = 0;    /* Must be 0 to get the tag in the answer.  */
-  tmpl[4] = 0x81; /* Challenge. */
-  tmpl[5] = indatalen;
-  memcpy (tmpl+6, indata, indatalen);
-  tmpllen = indatalen + 6;
+  err = concat_tlv_list (&apdudata, &apdudatalen,
+                         (int)0x7c, (size_t)0, NULL, /* Constructed. */
+                         (int)0x82, (size_t)0, "",
+                         (int)0x81, (size_t)indatalen, indata,
+                         (int)0, (size_t)0, NULL);
+  if (err)
+    goto leave;
 
   /* Note: the -1 requests command chaining.  */
   err = iso7816_general_authenticate (app->slot, -1,
-                                      algo, keyref,
-                                      tmpl, (int)tmpllen, 0,
+                                      mechanism, keyref,
+                                      apdudata, (int)apdudatalen, 0,
                                       &outdata, &outdatalen);
   if (err)
     goto leave;
@@ -1991,42 +2164,50 @@ do_auth (app_t app, const char *keyidstr,
   if (outdatalen && *outdata == 0x7c
       && (s = find_tlv (outdata, outdatalen, 0x82, &n)))
     {
-      const unsigned char *rval, *sval;
-      size_t rlen, rlenx, slen, slenx, resultlen;
-      char *result;
-      /* The result of an ECDSA signature is
-       *   SEQUENCE { r INTEGER, s INTEGER }
-       * We re-pack that by concatenating R and S and making sure that
-       * both have the same length.  We simplify parsing by using
-       * find_tlv and not a proper DER parser.  */
-      s = find_tlv (s, n, 0x30, &n);
-      if (!s)
-        goto bad_der;
-      rval = find_tlv (s, n, 0x02, &rlen);
-      if (!rval)
-        goto bad_der;
-      log_assert (n >= (rval-s)+rlen);
-      sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen);
-      if (!rval)
-        goto bad_der;
-      rlenx = slenx = 0;
-      if (rlen > slen)
-        slenx = rlen - slen;
-      else if (slen > rlen)
-        rlenx = slen - rlen;
-
-      resultlen = rlen + rlenx + slen + slenx;
-      result = xtrycalloc (1, resultlen);
-      if (!result)
+      if (mechanism == PIV_ALGORITHM_RSA)
         {
-          err = gpg_error_from_syserror ();
-          goto leave;
+          memmove (outdata, outdata + (s - outdata), n);
+          outdatalen = n;
+        }
+      else /* ECC */
+        {
+          const unsigned char *rval, *sval;
+          size_t rlen, rlenx, slen, slenx, resultlen;
+          char *result;
+          /* The result of an ECDSA signature is
+           *   SEQUENCE { r INTEGER, s INTEGER }
+           * We re-pack that by concatenating R and S and making sure
+           * that both have the same length.  We simplify parsing by
+           * using find_tlv and not a proper DER parser.  */
+          s = find_tlv (s, n, 0x30, &n);
+          if (!s)
+            goto bad_der;
+          rval = find_tlv (s, n, 0x02, &rlen);
+          if (!rval)
+            goto bad_der;
+          log_assert (n >= (rval-s)+rlen);
+          sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen);
+          if (!rval)
+            goto bad_der;
+          rlenx = slenx = 0;
+          if (rlen > slen)
+            slenx = rlen - slen;
+          else if (slen > rlen)
+            rlenx = slen - rlen;
+
+          resultlen = rlen + rlenx + slen + slenx;
+          result = xtrycalloc (1, resultlen);
+          if (!result)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          memcpy (result + rlenx, rval, rlen);
+          memcpy (result + rlenx + rlen + slenx, sval, slen);
+          xfree (outdata);
+          outdata = result;
+          outdatalen = resultlen;
         }
-      memcpy (result + rlenx, rval, rlen);
-      memcpy (result + rlenx + rlen + slenx, sval, slen);
-      xfree (outdata);
-      outdata = result;
-      outdatalen = resultlen;
     }
   else
     {
@@ -2048,10 +2229,29 @@ do_auth (app_t app, const char *keyidstr,
       *r_outdata = outdata;
       *r_outdatalen = outdatalen;
     }
+  xfree (apdudata);
+  xfree (indata_buffer);
   return err;
 }
 
 
+/* AUTH for PIV cards is actually the same as SIGN.  The difference
+ * between AUTH and SIGN is that AUTH expects that pkcs#1.5 padding
+ * for RSA has already been done (digestInfo part w/o the padding)
+ * whereas SIGN may accept a plain digest and does the padding if
+ * needed.  This is also the reason why SIGN takes a hashalgo. */
+static gpg_error_t
+do_auth (app_t app, const char *keyidstr,
+         gpg_error_t (*pincb)(void*, const char *, char **),
+         void *pincb_arg,
+         const void *indata, size_t indatalen,
+         unsigned char **r_outdata, size_t *r_outdatalen)
+{
+  return do_sign (app, keyidstr, 0, pincb, pincb_arg, indata, indatalen,
+                  r_outdata, r_outdatalen);
+}
+
+
 /* Check whether a key for DOBJ already exists.  We detect this by
  * reading the certificate described by DOBJ.  If FORCE is TRUE a
  * diagnositic will be printed but no error returned if the key
@@ -2464,7 +2664,7 @@ app_select_piv (app_t app)
   app->fnc.writecert = do_writecert;
   /* app->fnc.writekey = do_writekey; */
   app->fnc.genkey = do_genkey;
-  /* app->fnc.sign = do_sign; */
+  app->fnc.sign = do_sign;
   app->fnc.auth = do_auth;
   /* app->fnc.decipher = do_decipher; */
   app->fnc.change_pin = do_change_chv;

-----------------------------------------------------------------------

Summary of changes:
 scd/app-piv.c | 398 +++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 299 insertions(+), 99 deletions(-)


hooks/post-receive
-- 
The GNU Privacy Guard
http://git.gnupg.org




More information about the Gnupg-commits mailing list