gpg,sm: New option --with-key-screening.
authorWerner Koch <wk@gnupg.org>
Tue, 17 Oct 2017 19:10:19 +0000 (21:10 +0200)
committerWerner Koch <wk@gnupg.org>
Tue, 17 Oct 2017 19:10:19 +0000 (21:10 +0200)
* common/pkscreening.c: New.
* common/pkscreening.h: New.
* common/Makefile.am (common_sources): Add them.
* g10/gpg.c (opts): New option --with-key-screening.
* g10/options.h (struct opt): New field with_key_screening.
* g10/keylist.c: Include pkscreening.h.
(print_pk_screening): New.
(list_keyblock_print): Call it.
(print_compliance_flags): Call it.
* sm/gpgsm.c (opts): New option --with-key-screening.
* sm/gpgsm.h (scruct opt): New field with_key_screening.
* sm/keylist.c:  Include pkscreening.h.
(print_pk_screening): New.
(print_compliance_flags): Call it.  Add new arg cert.
(list_cert_colon): Pass arg cert
(list_cert_std): Call print_pk_screening.
* sm/fingerprint.c (gpgsm_get_rsa_modulus): New.
--

This new option can be used to detect ROCA affected keys.  To scan an
entire keyring and print the affected fingerprints use this:

  gpg -k --with-key-screening --with-colons | gawk -F: \
       '$1~/pub|sub|sec|ssb|crt/ && $18~/\<6001\>/ {found=1;next};
        $1=="fpr" && found {print $10}; {found=0}'

The same works for gpgsm.  Note that we need gawk due to the "\<" in
the r.e.

Signed-off-by: Werner Koch <wk@gnupg.org>
common/Makefile.am
common/pkscreening.c [new file with mode: 0644]
common/pkscreening.h [new file with mode: 0644]
doc/DETAILS
g10/gpg.c
g10/keylist.c
g10/options.h
sm/fingerprint.c
sm/gpgsm.c
sm/gpgsm.h
sm/keylist.c

index fcbe7ea..94318da 100644 (file)
@@ -94,7 +94,8 @@ common_sources = \
        name-value.c name-value.h \
        recsel.c recsel.h \
        ksba-io-support.c ksba-io-support.h \
-       compliance.c compliance.h
+       compliance.c compliance.h \
+       pkscreening.c pkscreening.h
 
 
 if HAVE_W32_SYSTEM
diff --git a/common/pkscreening.c b/common/pkscreening.c
new file mode 100644 (file)
index 0000000..a3bfb47
--- /dev/null
@@ -0,0 +1,159 @@
+/* pkscreening.c - Screen public keys for vulnerabilities
+ * Copyright (C) 2017 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+
+#include "util.h"
+#include "pkscreening.h"
+
+
+/* Helper */
+static inline gpg_error_t
+my_error (gpg_err_code_t ec)
+{
+  return gpg_err_make (default_errsource, ec);
+}
+
+
+/* Emulation of the new gcry_mpi_get_ui function.  */
+static gpg_error_t
+my_mpi_get_ui (unsigned int *v, gcry_mpi_t a)
+{
+  gpg_error_t err;
+  unsigned char buf[8];
+  size_t n;
+  int i, mul;
+
+  if (gcry_mpi_cmp_ui (a, 16384) > 0)
+    return my_error (GPG_ERR_ERANGE); /* Clearly too large for our purpose.  */
+
+  err = gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, a);
+  if (err)
+    return err;
+
+  *v = 0;
+  for (i = n - 1, mul = 1; i >= 0; i--, mul *= 256)
+    *v += mul * buf[i];
+
+  return 0;
+}
+
+
+/* Detect whether the MODULUS of a public RSA key is affected by the
+ * ROCA vulnerability as found in the Infinion RSA library
+ * (CVE-2017-15361).  Returns 0 if not affected, GPG_ERR_TRUE if
+ * affected, GPG_ERR_BAD_MPI if an opaque RSA was passed, or other
+ * error codes if something weird happened  */
+gpg_error_t
+screen_key_for_roca (gcry_mpi_t modulus)
+{
+  static struct {
+    unsigned int prime_ui;
+    const char *print_hex;
+    gcry_mpi_t prime;
+    gcry_mpi_t print;
+  } table[] = {
+   { 3,   "0x6" },
+   { 5,   "0x1E" },
+   { 7,   "0x7E" },
+   { 11,  "0x402" },
+   { 13,  "0x161A" },
+   { 17,  "0x1A316" },
+   { 19,  "0x30AF2" },
+   { 23,  "0x7FFFFE" },
+   { 29,  "0x1FFFFFFE" },
+   { 31,  "0x7FFFFFFE" },
+   { 37,  "0x4000402"  },
+   { 41,  "0x1FFFFFFFFFE" },
+   { 43,  "0x7FFFFFFFFFE" },
+   { 47,  "0x7FFFFFFFFFFE" },
+   { 53,  "0x12DD703303AED2" },
+   { 59,  "0x7FFFFFFFFFFFFFE" },
+   { 61,  "0x1434026619900B0A" },
+   { 67,  "0x7FFFFFFFFFFFFFFFE" },
+   { 71,  "0x1164729716B1D977E" },
+   { 73,  "0x147811A48004962078A" },
+   { 79,  "0xB4010404000640502"   },
+   { 83,  "0x7FFFFFFFFFFFFFFFFFFFE" },
+   { 89,  "0x1FFFFFFFFFFFFFFFFFFFFFE" },
+   { 97,  "0x1000000006000001800000002" },
+   { 101, "0x1FFFFFFFFFFFFFFFFFFFFFFFFE" },
+   { 103, "0x16380E9115BD964257768FE396" },
+   { 107, "0x27816EA9821633397BE6A897E1A" },
+   { 109, "0x1752639F4E85B003685CBE7192BA" },
+   { 113, "0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFE" },
+   { 127, "0x6CA09850C2813205A04C81430A190536" },
+   { 131, "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" },
+   { 137, "0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" },
+   { 139, "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" },
+   { 149, "0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" },
+   { 151, "0x50C018BC00482458DAC35B1A2412003D18030A" },
+   { 157, "0x161FB414D76AF63826461899071BD5BACA0B7E1A" },
+   { 163, "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" },
+   { 167, "0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" }
+  };
+  gpg_error_t err;
+  int i;
+  gcry_mpi_t rem;
+  unsigned int bitno;
+
+  /* Initialize on the first call. */
+  if (!table[0].prime)
+    {
+      /* We pass primes[i] to the call so that in case of a concurrent
+       * second thread the already allocated space is reused.  */
+      for (i = 0; i < DIM (table); i++)
+        {
+          table[i].prime = gcry_mpi_set_ui (table[i].prime, table[i].prime_ui);
+          if (gcry_mpi_scan (&table[i].print, GCRYMPI_FMT_HEX,
+                             table[i].print_hex, 0, NULL))
+            BUG ();
+        }
+    }
+
+  /* Check that it is not NULL or an opaque MPI.  */
+  if (!modulus || gcry_mpi_get_flag (modulus, GCRYMPI_FLAG_OPAQUE))
+    return my_error (GPG_ERR_BAD_MPI);
+
+  /* We divide the modulus of an RSA public key by a set of small
+   * PRIMEs and examine all the remainders.  If all the bits at the
+   * index given by the remainder are set in the corresponding PRINT
+   * masks the key is very likely vulnerable.  If any of the tested
+   * bits is zero, the key is not vulnerable.  */
+  rem = gcry_mpi_new (0);
+  for (i = 0; i < DIM (table); i++)
+    {
+      gcry_mpi_mod (rem, modulus, table[i].prime);
+      err = my_mpi_get_ui (&bitno, rem);
+      if (gpg_err_code (err) == GPG_ERR_ERANGE)
+        continue;
+      if (err)
+        goto leave;
+      if (!gcry_mpi_test_bit (table[i].print, bitno))
+        goto leave;  /* Not vulnerable.  */
+    }
+
+  /* Very likely vulnerable */
+  err = my_error (GPG_ERR_TRUE);
+
+ leave:
+  gcry_mpi_release (rem);
+  return err;
+}
diff --git a/common/pkscreening.h b/common/pkscreening.h
new file mode 100644 (file)
index 0000000..a647589
--- /dev/null
@@ -0,0 +1,26 @@
+/* pkscreening.c - Screen public keys for vulnerabilities
+ * Copyright (C) 2017 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef GNUPG_COMMON_PKSCREENING_H
+#define GNUPG_COMMON_PKSCREENING_H
+
+gpg_error_t screen_key_for_roca (gcry_mpi_t modulus);
+
+
+#endif /*GNUPG_COMMON_PKSCREENING_H*/
index 0be55f4..8ead6a8 100644 (file)
@@ -222,12 +222,14 @@ described here.
 
 *** Field 18 - Compliance flags
 
-    Space separated list of asserted compliance modes for this key.
+    Space separated list of asserted compliance modes and
+    screening result for this key.
 
     Valid values are:
 
     - 8  :: The key is compliant with RFC4880bis
     - 23 :: The key is compliant with compliance mode "de-vs".
+    - 6001 :: Screening hit on the ROCA vulnerability.
 
 *** Field 19 - Last update
 
index 62d6131..61e39b8 100644 (file)
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -197,6 +197,7 @@ enum cmd_and_opt_values
     oWithSubkeyFingerprint,
     oWithICAOSpelling,
     oWithKeygrip,
+    oWithKeyScreening,
     oWithSecret,
     oWithWKDHash,
     oWithColons,
@@ -785,6 +786,7 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"),
   ARGPARSE_s_n (oWithICAOSpelling, "with-icao-spelling", "@"),
   ARGPARSE_s_n (oWithKeygrip,     "with-keygrip", "@"),
+  ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"),
   ARGPARSE_s_n (oWithSecret,      "with-secret", "@"),
   ARGPARSE_s_n (oWithWKDHash,     "with-wkd-hash", "@"),
   ARGPARSE_s_n (oWithKeyOrigin,   "with-key-origin", "@"),
@@ -2737,6 +2739,10 @@ main (int argc, char **argv)
             opt.with_keygrip = 1;
             break;
 
+         case oWithKeyScreening:
+            opt.with_key_screening = 1;
+            break;
+
          case oWithSecret:
             opt.with_secret = 1;
             break;
index dccae91..bcbad45 100644 (file)
@@ -45,6 +45,7 @@
 #include "../common/zb32.h"
 #include "tofu.h"
 #include "../common/compliance.h"
+#include "../common/pkscreening.h"
 
 
 static void list_all (ctrl_t, int, int);
@@ -696,6 +697,37 @@ print_key_data (PKT_public_key * pk)
     }
 }
 
+
+/* Various public key screenings.  (Right now just ROCA).  With
+ * COLON_MODE set the output is formatted for use in the compliance
+ * field of a colon listing.
+ */
+static void
+print_pk_screening (PKT_public_key *pk, int colon_mode)
+{
+  gpg_error_t err;
+
+  if (is_RSA (pk->pubkey_algo) && pubkey_get_npkey (pk->pubkey_algo))
+    {
+      err = screen_key_for_roca (pk->pkey[0]);
+      if (!err)
+        ;
+      else if (gpg_err_code (err) == GPG_ERR_TRUE)
+        {
+          if (colon_mode)
+            es_fprintf (es_stdout, colon_mode > 1? " %d":"%d", 6001);
+          else
+            es_fprintf (es_stdout,
+                        "      Screening: ROCA vulnerability detected\n");
+        }
+      else if (!colon_mode)
+        es_fprintf (es_stdout, "      Screening: [ROCA check failed: %s]\n",
+                    gpg_strerror (err));
+    }
+
+}
+
+
 static void
 print_capabilities (ctrl_t ctrl, PKT_public_key *pk, KBNODE keyblock)
 {
@@ -922,6 +954,9 @@ list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr,
   if (opt.with_key_data)
     print_key_data (pk);
 
+  if (opt.with_key_screening)
+    print_pk_screening (pk, 0);
+
   if (opt.with_key_origin
       && (pk->keyorg || pk->keyupdate || pk->updateurl))
     {
@@ -1063,6 +1098,8 @@ list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr,
             es_fprintf (es_stdout, "      Keygrip = %s\n", hexgrip);
          if (opt.with_key_data)
            print_key_data (pk2);
+          if (opt.with_key_screening)
+            print_pk_screening (pk2, 0);
        }
       else if (opt.list_sigs
               && node->pkt->pkttype == PKT_SIGNATURE && !skip_sigs)
@@ -1227,6 +1264,9 @@ print_compliance_flags (PKT_public_key *pk,
                  gnupg_status_compliance_flag (CO_DE_VS));
       any++;
     }
+
+  if (opt.with_key_screening)
+    print_pk_screening (pk, 1+any);
 }
 
 
index 130bec8..61f7403 100644 (file)
@@ -82,6 +82,7 @@ struct
   int with_fingerprint; /* Option --with-fingerprint active.  */
   int with_subkey_fingerprint; /* Option --with-subkey-fingerprint active.  */
   int with_keygrip;     /* Option --with-keygrip active.  */
+  int with_key_screening;/* Option --with-key-screening active.  */
   int with_tofu_info;   /* Option --with-tofu_info active.  */
   int with_secret;      /* Option --with-secret active.  */
   int with_wkd_hash;    /* Option --with-wkd-hash.  */
index fbcec58..59688f3 100644 (file)
@@ -277,6 +277,70 @@ gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits)
 }
 
 
+/* If KEY is an RSA key, return its modulus.  For non-RSA keys or on
+ * error return NULL.  */
+gcry_mpi_t
+gpgsm_get_rsa_modulus (ksba_cert_t cert)
+{
+  gpg_error_t err;
+  gcry_sexp_t key;
+  gcry_sexp_t list = NULL;
+  gcry_sexp_t l2 = NULL;
+  char *name = NULL;
+  gcry_mpi_t modulus = NULL;
+
+  {
+    ksba_sexp_t ckey;
+    size_t n;
+
+    ckey = ksba_cert_get_public_key (cert);
+    if (!ckey)
+      return NULL;
+    n = gcry_sexp_canon_len (ckey, 0, NULL, NULL);
+    if (!n)
+      {
+        xfree (ckey);
+        return NULL;
+      }
+    err = gcry_sexp_sscan (&key, NULL, (char *)ckey, n);
+    xfree (ckey);
+    if (err)
+      return NULL;
+  }
+
+  list = gcry_sexp_find_token (key, "public-key", 0);
+  if (!list)
+    list = gcry_sexp_find_token (key, "private-key", 0);
+  if (!list)
+    list = gcry_sexp_find_token (key, "protected-private-key", 0);
+  if (!list)
+    list = gcry_sexp_find_token (key, "shadowed-private-key", 0);
+
+  gcry_sexp_release (key);
+  if (!list)
+    return NULL;  /* No suitable key.  */
+
+  l2 = gcry_sexp_cadr (list);
+  gcry_sexp_release (list);
+  list = l2;
+  l2 = NULL;
+
+  name = gcry_sexp_nth_string (list, 0);
+  if (!name)
+    ;
+  else if (gcry_pk_map_name (name) == GCRY_PK_RSA)
+    {
+      l2 = gcry_sexp_find_token (list, "n", 1);
+      if (l2)
+        modulus = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+    }
+
+  gcry_free (name);
+  gcry_sexp_release (l2);
+  gcry_sexp_release (list);
+  return modulus;
+}
+
 
 \f
 /* For certain purposes we need a certificate id which has an upper
index fa37f63..bd701ab 100644 (file)
@@ -155,6 +155,7 @@ enum cmd_and_opt_values {
   oWithMD5Fingerprint,
   oWithKeygrip,
   oWithSecret,
+  oWithKeyScreening,
   oAnswerYes,
   oAnswerNo,
   oKeyring,
@@ -391,6 +392,7 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"),
   ARGPARSE_s_n (oWithKeygrip,     "with-keygrip", "@"),
   ARGPARSE_s_n (oWithSecret,      "with-secret", "@"),
+  ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"),
   ARGPARSE_s_s (oDisableCipherAlgo,  "disable-cipher-algo", "@"),
   ARGPARSE_s_s (oDisablePubkeyAlgo,  "disable-pubkey-algo", "@"),
   ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"),
@@ -1289,6 +1291,10 @@ main ( int argc, char **argv)
           opt.with_keygrip = 1;
           break;
 
+        case oWithKeyScreening:
+          opt.with_key_screening = 1;
+          break;
+
         case oOptions:
           /* config files may not be nested (silently ignore them) */
           if (!configfp)
index 8c1f520..0421b97 100644 (file)
@@ -85,6 +85,8 @@ struct
 
   int with_keygrip; /* Option --with-keygrip active.  */
 
+  int with_key_screening; /* Option  --with-key-screening active.  */
+
   int pinentry_mode;
 
   int armor;        /* force base64 armoring (see also ctrl.with_base64) */
@@ -258,6 +260,7 @@ unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert,
 unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array);
 char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert);
 int  gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits);
+gcry_mpi_t gpgsm_get_rsa_modulus (ksba_cert_t cert);
 char *gpgsm_get_certid (ksba_cert_t cert);
 
 
index 9997da8..ea2a220 100644 (file)
@@ -37,6 +37,7 @@
 #include "../common/i18n.h"
 #include "../common/tlv.h"
 #include "../common/compliance.h"
+#include "../common/pkscreening.h"
 
 struct list_external_parm_s
 {
@@ -238,6 +239,38 @@ print_key_data (ksba_cert_t cert, estream_t fp)
 #endif
 }
 
+
+/* Various public key screenings.  (Right now just ROCA).  With
+ * COLON_MODE set the output is formatted for use in the compliance
+ * field of a colon listing.  */
+static void
+print_pk_screening (ksba_cert_t cert, int colon_mode, estream_t fp)
+{
+  gpg_error_t err;
+  gcry_mpi_t modulus;
+
+  modulus = gpgsm_get_rsa_modulus (cert);
+  if (modulus)
+    {
+      err = screen_key_for_roca (modulus);
+      if (!err)
+        ;
+      else if (gpg_err_code (err) == GPG_ERR_TRUE)
+        {
+          if (colon_mode)
+            es_fprintf (fp, colon_mode > 1? " %d":"%d", 6001);
+          else
+            es_fprintf (fp, "    screening: ROCA vulnerability detected\n");
+        }
+      else if (!colon_mode)
+        es_fprintf (fp, "    screening: [ROCA check failed: %s]\n",
+                    gpg_strerror (err));
+      gcry_mpi_release (modulus);
+    }
+
+}
+
+
 static void
 print_capabilities (ksba_cert_t cert, estream_t fp)
 {
@@ -348,10 +381,19 @@ email_kludge (const char *name)
 /* Print the compliance flags to field 18.  ALGO is the gcrypt algo
  * number.  NBITS is the length of the key in bits.  */
 static void
-print_compliance_flags (int algo, unsigned int nbits, estream_t fp)
+print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits,
+                        estream_t fp)
 {
+  int any = 0;
+
   if (gnupg_pk_is_compliant (CO_DE_VS, algo, NULL, nbits, NULL))
-    es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp);
+    {
+      es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp);
+      any++;
+    }
+
+  if (opt.with_key_screening)
+    print_pk_screening (cert, 1+any, fp);
 }
 
 
@@ -526,7 +568,7 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
   es_putc (':', fp);  /* End of field 15. */
   es_putc (':', fp);  /* End of field 16. */
   es_putc (':', fp);  /* End of field 17. */
-  print_compliance_flags (algo, nbits, fp);
+  print_compliance_flags (cert, algo, nbits, fp);
   es_putc (':', fp);  /* End of field 18. */
   es_putc ('\n', fp);
 
@@ -1253,6 +1295,9 @@ list_cert_std (ctrl_t ctrl, ksba_cert_t cert, estream_t fp, int have_secret,
         }
     }
 
+  if (opt.with_key_screening)
+    print_pk_screening (cert, 0, fp);
+
   if (have_secret)
     {
       char *cardsn;