From 825abec0e7f38667a34dce3025fc2f3a05001dde Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 17 Oct 2017 21:10:19 +0200 Subject: [PATCH] gpg,sm: New option --with-key-screening. * 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 --- common/Makefile.am | 3 +- common/pkscreening.c | 159 +++++++++++++++++++++++++++++++++++++++++++ common/pkscreening.h | 26 +++++++ doc/DETAILS | 4 +- g10/gpg.c | 6 ++ g10/keylist.c | 40 +++++++++++ g10/options.h | 1 + sm/fingerprint.c | 64 +++++++++++++++++ sm/gpgsm.c | 6 ++ sm/gpgsm.h | 3 + sm/keylist.c | 51 +++++++++++++- 11 files changed, 358 insertions(+), 5 deletions(-) create mode 100644 common/pkscreening.c create mode 100644 common/pkscreening.h diff --git a/common/Makefile.am b/common/Makefile.am index fcbe7ea66..94318dae4 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -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 index 000000000..a3bfb474e --- /dev/null +++ b/common/pkscreening.c @@ -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 . + */ + +#include +#include + +#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 index 000000000..a64758924 --- /dev/null +++ b/common/pkscreening.h @@ -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 . + */ + +#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*/ diff --git a/doc/DETAILS b/doc/DETAILS index 0be55f4d6..8ead6a8f5 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -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 diff --git a/g10/gpg.c b/g10/gpg.c index 62d6131ba..61e39b8e4 100644 --- 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; diff --git a/g10/keylist.c b/g10/keylist.c index dccae91c9..bcbad450a 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -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); } diff --git a/g10/options.h b/g10/options.h index 130bec84c..61f7403be 100644 --- a/g10/options.h +++ b/g10/options.h @@ -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. */ diff --git a/sm/fingerprint.c b/sm/fingerprint.c index fbcec5883..59688f3a4 100644 --- a/sm/fingerprint.c +++ b/sm/fingerprint.c @@ -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; +} + /* For certain purposes we need a certificate id which has an upper diff --git a/sm/gpgsm.c b/sm/gpgsm.c index fa37f63be..bd701ab7f 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -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) diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 8c1f520de..0421b97bb 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -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); diff --git a/sm/keylist.c b/sm/keylist.c index 9997da812..ea2a22093 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -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;