From 33e571a74a7d6153ba65aeecc72539a10f1f0ae4 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 11 Sep 2024 14:24:58 +0200 Subject: [PATCH] gpgsm: New option --assert-signer * sm/gpgsm.c (oAssertSigner, oNoop): New. (opts): Add option --assert-signer. (assert_signer_true): New var. (main): Set new option. (gpgsm_exit): Handle assert_signer_true. * sm/gpgsm.h (opt): Add field assert_signer_list. * sm/verify.c (is_x509_fingerprint): New. (check_assert_signer_list): New. (gpgsm_verify): Handle option. -- GnuPG-bug-id: 7286 --- NEWS | 8 ++- doc/gpgsm.texi | 15 +++++ sm/gpgsm.c | 25 ++++++++- sm/gpgsm.h | 5 ++ sm/verify.c | 134 ++++++++++++++++++++++++++++++++++++++++++++ tools/rfc822parse.c | 1 - 6 files changed, 184 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 5180f2ba6..1f2f98cf5 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,13 @@ Noteworthy changes in version 2.5.1 (unreleased) ------------------------------------------------ - * gpg: New option --proc-all-sigs. [T7261] + * gpg: The support for composite Kyber+ECC public key algorithms + does now use the final FIPS-203 and LibrePGP specifications. The + experimental keys from 2.5.0 are no longer supported. [T6815] + + * gpg: New option --proc-all-sigs. [T7261] + + * gpgsm: New option --assert-signer. [T7286] Release-info: https://dev.gnupg.org/T7191 diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi index 1316318a6..2cb50539a 100644 --- a/doc/gpgsm.texi +++ b/doc/gpgsm.texi @@ -732,6 +732,21 @@ instead to make sure that the gpgsm process exits with a failure if the compliance rules are not fulfilled. Note that this option has currently an effect only in "de-vs" mode. +@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). +As of now only SHA-1 fingerprints are allowed. 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 gpgsm 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 --always-trust @opindex always-trust Force encryption to the specified certificates without any validation diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 70463e734..400479b1b 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -217,7 +217,10 @@ enum cmd_and_opt_values { oCompatibilityFlags, oKbxBufferSize, oAlwaysTrust, - oNoAutostart + oNoAutostart, + oAssertSigner, + + oNoop }; @@ -311,6 +314,7 @@ static gpgrt_opt_t opts[] = { N_("|FILE|take policy information from FILE")), ARGPARSE_s_s (oCompliance, "compliance", "@"), ARGPARSE_p_u (oMinRSALength, "min-rsa-length", "@"), + ARGPARSE_s_s (oAssertSigner, "assert-signer", "@"), ARGPARSE_s_n (oNoCommonCertsImport, "no-common-certs-import", "@"), ARGPARSE_s_s (oIgnoreCertExtension, "ignore-cert-extension", "@"), ARGPARSE_s_s (oIgnoreCertWithOID, "ignore-cert-with-oid", "@"), @@ -502,6 +506,9 @@ static struct compatibility_flags_s compatibility_flags [] = /* Global variable to keep an error count. */ int gpgsm_errors_seen = 0; +/* If opt.assert_signer_list is used and this variable is not true + * gpg will be forced to return EXIT_FAILURE. */ +int assert_signer_true = 0; /* It is possible that we are currentlu running under setuid permissions */ static int maybe_setuid = 1; @@ -1518,6 +1525,12 @@ main ( int argc, char **argv) keybox_set_buffersize (pargs.r.ret_ulong, 0); break; + case oAssertSigner: + add_to_strlist (&opt.assert_signer_list, pargs.r.ret_str); + break; + + case oNoop: break; + default: if (configname) pargs.err = ARGPARSE_PRINT_WARNING; @@ -2329,6 +2342,15 @@ emergency_cleanup (void) void gpgsm_exit (int rc) { + if (rc) + ; + else if (log_get_errorcount(0)) + rc = 2; + else if (gpgsm_errors_seen) + rc = 1; + else if (opt.assert_signer_list && !assert_signer_true) + rc = 1; + gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); if (opt.debug & DBG_MEMSTAT_VALUE) { @@ -2338,7 +2360,6 @@ gpgsm_exit (int rc) if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); emergency_cleanup (); - rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0; exit (rc); } diff --git a/sm/gpgsm.h b/sm/gpgsm.h index e9f74be8c..5f69db0e3 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -181,6 +181,10 @@ struct * attribute values. */ strlist_t attributes; + /* The list of --assert-signer option values. Note: The values are + * modified to uppercase if they represent a fingerrint */ + strlist_t assert_signer_list; + /* Compatibility flags (COMPAT_FLAG_xxxx). */ unsigned int compat_flags; } opt; @@ -312,6 +316,7 @@ struct rootca_flags_s /*-- gpgsm.c --*/ extern int gpgsm_errors_seen; +extern int assert_signer_true; void gpgsm_exit (int rc); void gpgsm_init_default_ctrl (struct server_control_s *ctrl); diff --git a/sm/verify.c b/sm/verify.c index 39226ed28..cd9659313 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -37,6 +37,11 @@ #include "../common/i18n.h" #include "../common/compliance.h" + +static void check_assert_signer_list (ctrl_t ctrl, const char *pkhex); + + + static char * strtimestamp_r (ksba_isotime_t atime) { @@ -647,6 +652,8 @@ gpgsm_verify (ctrl_t ctrl, estream_t in_fp, estream_t data_fp, *keyexptime? keyexptime : "0", info_pkalgo, algo); xfree (tstr); + /* Handle the --assert-signer option. */ + check_assert_signer_list (ctrl, fpr); xfree (fpr); gpgsm_status (ctrl, STATUS_VALIDSIG, buf); xfree (buf); @@ -747,3 +754,130 @@ gpgsm_verify (ctrl_t ctrl, estream_t in_fp, estream_t data_fp, return rc; } + + + +static int +is_x509_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; /* SHA1 or SHA256 fingerprint. */ + + return 0; +} + + +/* This function shall be called with the X.509 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. + * + * Note: This function is mainly a copy of the fucntion from gpg. The + * status emit function and the single X.509 fingerprint makes the + * differences. + */ +static void +check_assert_signer_list (ctrl_t ctrl, 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_x509_fingerprint (item->d)) + { + ascii_strupr (item->d); + if (!strcmp (item->d, pkhex)) + { + assert_signer_true = 1; + gpgsm_status (ctrl, STATUS_ASSERT_SIGNER, item->d); + if (!opt.quiet) + log_info ("asserted signer '%s'\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, pkhex)) + { + assert_signer_true = 1; + gpgsm_status (ctrl, STATUS_ASSERT_SIGNER, p); + if (!opt.quiet) + log_info ("asserted signer '%s' (%s:%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); +} diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c index 5aa233b12..fa1cf2a3e 100644 --- a/tools/rfc822parse.c +++ b/tools/rfc822parse.c @@ -725,7 +725,6 @@ const char * rfc822parse_enum_header_lines (rfc822parse_t msg, void **context) { HDR_LINE l; - part_t part; if (!msg) /* Close. */ return NULL;