diff --git a/NEWS b/NEWS index 7ca5b1335..894743db5 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,13 @@ Noteworthy changes in version 2.4.1 (unreleased) ------------------------------------------------ - * If the ~/.gnupg home directory does not exist, the keyboxd is now + * If the ~/.gnupg directory does not exist, the keyboxd is now automagically enabled. * gpg: New option --add-desig-revoker. [rG3d094e2bcf] + * gpg: New option --assert-signer. + * gpg: New list-option "show-unusable-sigs". Also show "[self-signature]" instead of the user-id in key signature listings. [rG103acfe9ca] diff --git a/common/status.h b/common/status.h index 0c481d247..e4cf23ee1 100644 --- a/common/status.h +++ b/common/status.h @@ -53,6 +53,7 @@ enum STATUS_NEED_PASSPHRASE, STATUS_VALIDSIG, + STATUS_ASSERT_SIGNER, STATUS_SIG_ID, STATUS_ENC_TO, STATUS_NODATA, diff --git a/doc/DETAILS b/doc/DETAILS index eee640a01..fd95e511c 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -522,6 +522,11 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: Epoch or an ISO 8601 string which can be detected by the presence of the letter 'T'. +*** ASSERT_SIGNER + This is emitted for the matching when option + --assert-signer is used. The fingerprint is printed with + uppercase hex digits. + *** SIG_ID This is emitted only for signatures of class 0 or 1 which have been verified okay. The string is a signature id and may be used diff --git a/doc/gpg.texi b/doc/gpg.texi index b526deeca..eb7c35cac 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -264,11 +264,11 @@ out the actual signed data, but there are other pitfalls with this format as well. It is suggested to avoid cleartext signatures in favor of detached signatures. -Note: Sometimes the use of the @command{gpgv} tool is easier than -using the full-fledged @command{gpg} with this option. @command{gpgv} -is designed to compare signed data against a list of trusted keys and -returns with success only for a good signature. It has its own manual -page. +Note: To check whether a file was signed by a certain key the option +@option{--assert-signer} can be used. As an alternative the +@command{gpgv} tool can be used. @command{gpgv} is designed to +compare signed data against a list of trusted keys and returns with +success only for a good signature. It has its own manual page. @item --multifile @@ -1889,6 +1889,24 @@ Set what trust model GnuPG should follow. The models are: must be enabled explicitly. @end table +@item --always-trust +@opindex always-trust +Identical to @option{--trust-model always}. + +@item --assert-signer @var{fpr_or_file} +@opindex assert-signer +This option checks whether at least one valid signature on a file has +been made with the specified key. The key is either specified as a +fingerprint or a file listing fingerprints. The fingerprint must be +given or listed in compact format (no colons or spaces in between). +This option can be given multiple times and each fingerprint is +checked against the signing key as well as the corresponding primary +key. If @var{fpr_or_file} specifies a file, empty lines are ignored +as well as all lines starting with a hash sign. With this option gpg +is guaranteed to return with an exit code of 0 if and only if a +signature has been encountered, is valid, and the key matches one of +the fingerprints given by this option. + @item --auto-key-locate @var{mechanisms} @itemx --no-auto-key-locate @@ -3856,10 +3874,6 @@ Display the keyring name at the head of key listings to show which keyring a given key resides on. This option is deprecated: use @option{--list-options [no-]show-keyring} instead. -@item --always-trust -@opindex always-trust -Identical to @option{--trust-model always}. This option is deprecated. - @item --show-notation @itemx --no-show-notation @opindex show-notation diff --git a/g10/gpg.c b/g10/gpg.c index f52d13a76..b759cc1cf 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -446,6 +446,7 @@ enum cmd_and_opt_values oRequireCompliance, oCompatibilityFlags, oAddDesigRevoker, + oAssertSigner, oNoop }; @@ -708,7 +709,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_n (oNoAutoTrustNewKey, "no-auto-trust-new-key", "@"), #endif ARGPARSE_s_s (oAddDesigRevoker, "add-desig-revoker", "@"), - + ARGPARSE_s_s (oAssertSigner, "assert-signer", "@"), ARGPARSE_header ("Input", N_("Options controlling the input")), @@ -1032,8 +1033,12 @@ static struct compatibility_flags_s compatibility_flags [] = /* The list of the default AKL methods. */ #define DEFAULT_AKL_LIST "local,wkd" - +/* Can be set to true to force gpg to return with EXIT_FAILURE. */ int g10_errors_seen = 0; +/* If opt.assert_signer_list is used and this variabale is not true + * gpg will be forced to return EXIT_FAILURE. */ +int assert_signer_true = 0; + static int utf8_strings = #ifdef HAVE_W32_SYSTEM @@ -3734,6 +3739,11 @@ main (int argc, char **argv) append_to_strlist (&opt.desig_revokers, pargs.r.ret_str); break; + case oAssertSigner: + add_to_strlist (&opt.assert_signer_list, pargs.r.ret_str); + break; + + case oNoop: break; default: @@ -5448,7 +5458,15 @@ g10_exit( int rc ) gnupg_block_all_signals (); emergency_cleanup (); - rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0; + if (rc) + ; + else if (log_get_errorcount(0)) + rc = 2; + else if (g10_errors_seen) + rc = 1; + else if (opt.assert_signer_list && !assert_signer_true) + rc = 1; + exit (rc); } diff --git a/g10/gpgv.c b/g10/gpgv.c index ceded4af9..f2895563e 100644 --- a/g10/gpgv.c +++ b/g10/gpgv.c @@ -118,7 +118,7 @@ static struct debug_flags_s debug_flags [] = int g10_errors_seen = 0; - +int assert_signer_true = 0; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) diff --git a/g10/main.h b/g10/main.h index 3d71d0c09..b29e23e51 100644 --- a/g10/main.h +++ b/g10/main.h @@ -83,6 +83,7 @@ struct weakhash /*-- gpg.c --*/ extern int g10_errors_seen; +extern int assert_signer_true; #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) void g10_exit(int rc) __attribute__ ((__noreturn__)); @@ -492,6 +493,7 @@ void print_file_status( int status, const char *name, int what ); int verify_signatures (ctrl_t ctrl, int nfiles, char **files ); int verify_files (ctrl_t ctrl, int nfiles, char **files ); int gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp); +void check_assert_signer_list (const char *mainpkhex, const char *pkhex); /*-- decrypt.c --*/ int decrypt_message (ctrl_t ctrl, const char *filename ); diff --git a/g10/mainproc.c b/g10/mainproc.c index 4710386ea..ce0fdaaac 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -2410,7 +2410,7 @@ check_sig_and_print (CTX c, kbnode_t node) } /* For good signatures print the VALIDSIG status line. */ - if (!rc && is_status_enabled () && pk) + if (!rc && (is_status_enabled () || opt.assert_signer_list) && pk) { char pkhex[MAX_FINGERPRINT_LEN*2+1]; char mainpkhex[MAX_FINGERPRINT_LEN*2+1]; @@ -2430,6 +2430,8 @@ check_sig_and_print (CTX c, kbnode_t node) sig->digest_algo, sig->sig_class, mainpkhex); + /* Handle the --assert-signer option. */ + check_assert_signer_list (mainpkhex, pkhex); } /* Print compliance warning for Good signatures. */ @@ -2510,6 +2512,7 @@ check_sig_and_print (CTX c, kbnode_t node) is not a detached signature. */ log_info (_("WARNING: not a detached signature; " "file '%s' was NOT verified!\n"), dfile); + assert_signer_true = 0; } xfree (dfile); } diff --git a/g10/options.h b/g10/options.h index 3ecf57ffb..9015e321f 100644 --- a/g10/options.h +++ b/g10/options.h @@ -235,6 +235,10 @@ struct value. */ int limit_card_insert_tries; + /* The list of --assert-signer option values. Note: The values are + * modify to be uppercase if they represent a fingerrint */ + strlist_t assert_signer_list; + struct { /* If set, require an 0x19 backsig to be present on signatures diff --git a/g10/t-keydb-get-keyblock.c b/g10/t-keydb-get-keyblock.c index 90ce6e9a6..e40be9cc1 100644 --- a/g10/t-keydb-get-keyblock.c +++ b/g10/t-keydb-get-keyblock.c @@ -67,3 +67,12 @@ do_test (int argc, char *argv[]) release_kbnode (kb1); xfree (ctrl); } + +int assert_signer_true = 0; + +void +check_assert_signer_list (const char *mainpkhex, const char *pkhex) +{ + (void)mainpkhex; + (void)pkhex; +} diff --git a/g10/t-keydb.c b/g10/t-keydb.c index 4c78dac48..9055d5b94 100644 --- a/g10/t-keydb.c +++ b/g10/t-keydb.c @@ -105,3 +105,13 @@ do_test (int argc, char *argv[]) keydb_release (hd2); xfree (ctrl); } + + +int assert_signer_true = 0; + +void +check_assert_signer_list (const char *mainpkhex, const char *pkhex) +{ + (void)mainpkhex; + (void)pkhex; +} diff --git a/g10/t-stutter.c b/g10/t-stutter.c index 503a92004..7b2ea4b37 100644 --- a/g10/t-stutter.c +++ b/g10/t-stutter.c @@ -611,3 +611,12 @@ do_test (int argc, char *argv[]) xfree (filename); } + +int assert_signer_true = 0; + +void +check_assert_signer_list (const char *mainpkhex, const char *pkhex) +{ + (void)mainpkhex; + (void)pkhex; +} diff --git a/g10/verify.c b/g10/verify.c index fc18882b0..e9792939d 100644 --- a/g10/verify.c +++ b/g10/verify.c @@ -1,6 +1,8 @@ /* verify.c - Verify signed data * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006, * 2007, 2010 Free Software Foundation, Inc. + * Copyright (C) 2003, 2006-2008, 2010-2011, 2015-2017, + * 2020, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -16,6 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -281,3 +284,124 @@ gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp) release_armor_context (afx); return rc; } + + +static int +is_fingerprint (const char *string) +{ + int n; + + if (!string || !*string) + return 0; + for (n=0; hexdigitp (string); string++) + n++; + if (!*string && (n == 40 || n == 64)) + return 1; /* v4 or v5 fingerprint. */ + + return 0; +} + + +/* This function shall be called with the main and subkey fingerprint + * iff a signature is fully valid. If the option --assert-signer is + * active it check whether the signing key matches one of the keys + * given by this option and if so, sets a global flag. */ +void +check_assert_signer_list (const char *mainpkhex, const char *pkhex) +{ + gpg_error_t err; + strlist_t item; + const char *fname; + estream_t fp = NULL; + int lnr; + int n, c; + char *p, *pend; + char line[256]; + + if (!opt.assert_signer_list) + return; /* Nothing to do. */ + if (assert_signer_true) + return; /* Already one valid signature seen. */ + + for (item = opt.assert_signer_list; item; item = item->next) + { + if (is_fingerprint (item->d)) + { + ascii_strupr (item->d); + if (!strcmp (item->d, mainpkhex) || !strcmp (item->d, pkhex)) + { + assert_signer_true = 1; + write_status_text (STATUS_ASSERT_SIGNER, item->d); + if (!opt.quiet) + log_info ("signer '%s' matched\n", item->d); + goto leave; + } + } + else /* Assume this is a file - read and compare. */ + { + fname = item->d; + es_fclose (fp); + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("error opening '%s': %s\n"), + fname, gpg_strerror (err)); + continue; + } + + lnr = 0; + err = 0; + while (es_fgets (line, DIM(line)-1, fp)) + { + lnr++; + + n = strlen (line); + if (!n || line[n-1] != '\n') + { + /* Eat until end of line. */ + while ( (c=es_getc (fp)) != EOF && c != '\n') + ; + err = gpg_error (GPG_ERR_INCOMPLETE_LINE); + log_error (_("file '%s', line %d: %s\n"), + fname, lnr, gpg_strerror (err)); + continue; + } + line[--n] = 0; /* Chop the LF. */ + if (n && line[n-1] == '\r') + line[--n] = 0; /* Chop an optional CR. */ + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + /* Get the first token and ignore trailing stuff. */ + for (pend = p; *pend && !spacep (pend); pend++) + ; + *pend = 0; + ascii_strupr (p); + + if (!strcmp (p, mainpkhex) || !strcmp (p, pkhex)) + { + assert_signer_true = 1; + write_status_text (STATUS_ASSERT_SIGNER, p); + if (!opt.quiet) + log_info ("signer '%s' matched '%s', line %d\n", + p, fname, lnr); + goto leave; + } + } + if (!err && !es_feof (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("error reading '%s', line %d: %s\n"), + fname, lnr, gpg_strerror (err)); + } + } + } + + leave: + es_fclose (fp); +}