From 5c2080f4670a768787f5cb4ed5c32e0946837883 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 8 Jun 2020 20:13:25 +0200 Subject: [PATCH] gpg: If possible TRUST values now depend on signer's UID or --sender. * g10/mainproc.c (check_sig_and_print): Add failsafe check for PK. Pass KEYBLOCK down do check_signatures_trust. Protect existsing error ocde in case the signature expired. * g10/pkclist.c (is_in_sender_list): New. (check_signatures_trust): Add args keyblock and pk. Add new uid based checking code. * g10/test-stubs.c, g10/gpgv.c: Adjust stubs. -- GnuPG-bug-id: 4735 Signed-off-by: Werner Koch --- doc/gpg.texi | 31 +++++++-- g10/gpgv.c | 7 +- g10/keydb.h | 3 +- g10/mainproc.c | 37 +++++++++-- g10/pkclist.c | 168 ++++++++++++++++++++++++++++++++++++++--------- g10/test-stubs.c | 7 +- 6 files changed, 204 insertions(+), 49 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index d05699c55..970cbabb5 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -2243,11 +2243,32 @@ Use @var{name} as the key to sign with. Note that this option overrides @item --sender @var{mbox} @opindex sender This option has two purposes. @var{mbox} must either be a complete -user id with a proper mail address or just a mail address. When -creating a signature this option tells gpg the user id of a key used -to make a signature if the key was not directly specified by a user -id. When verifying a signature the @var{mbox} is used to restrict the -information printed by the TOFU code to matching user ids. +user ID containing a proper mail address or just a plain mail address. +The option can be given multiple times. + +When creating a signature this option tells gpg the signing key's user +id used to make the signature and embeds that user ID into the created +signature (using OpenPGP's ``Signer's User ID'' subpacket). If the +option is given multiple times a suitable user ID is picked. However, +if the signing key was specified directly by using a mail address +(i.e. not by using a fingerprint or key ID) this option is used and +the mail address is embedded in the created signature. + +When verifying a signature @var{mbox} is used to restrict the +information printed by the TOFU code to matching user IDs. If the +option is used and the signature contains a ``Signer's User ID'' +subpacket that information is is also used to restrict the printed +information. Note that GnuPG considers only the mail address part of +a User ID. + +If this option or the said subpacket is available the TRUST lines as +printed by option @option{status-fd} correspond to the corresponding +User ID; if no User ID is known the TRUST lines are computed directly +on the key and do not give any information about the User ID. In the +latter case it his highly recommended to scripts and other frontends +to evaluate the VALIDSIG line, retrieve the key and print all User IDs +along with their validity (trust) information. + @item --try-secret-key @var{name} @opindex try-secret-key diff --git a/g10/gpgv.c b/g10/gpgv.c index 65f5f89c7..9f8dca82f 100644 --- a/g10/gpgv.c +++ b/g10/gpgv.c @@ -299,10 +299,13 @@ g10_exit( int rc ) * We have to override the trustcheck from pkclist.c because * this utility assumes that all keys in the keyring are trustworthy */ -int -check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) +gpg_error_t +check_signatures_trust (ctrl_t ctrl, kbnode_t kblock, + PKT_public_key *pk, PKT_signature *sig) { (void)ctrl; + (void)kblock; + (void)pk; (void)sig; return 0; } diff --git a/g10/keydb.h b/g10/keydb.h index 75d3cd0d5..a6c70d682 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -263,7 +263,8 @@ gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen); /*-- pkclist.c --*/ void show_revocation_reason (ctrl_t ctrl, PKT_public_key *pk, int mode ); -int check_signatures_trust (ctrl_t ctrl, PKT_signature *sig); +gpg_error_t check_signatures_trust (ctrl_t ctrl, kbnode_t keyblock, + PKT_public_key *pk, PKT_signature *sig); void release_pk_list (PK_LIST pk_list); int expand_id (const char *id, strlist_t *into, unsigned int flags); diff --git a/g10/mainproc.c b/g10/mainproc.c index 7d8520c6c..1d48d574c 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -1848,7 +1848,7 @@ check_sig_and_print (CTX c, kbnode_t node) { PKT_signature *sig = node->pkt->pkt.signature; const char *astr; - int rc; + gpg_error_t rc; int is_expkey = 0; int is_revkey = 0; char *issuer_fpr = NULL; @@ -2031,8 +2031,9 @@ check_sig_and_print (CTX c, kbnode_t node) { rc = do_check_sig (c, node, extrahash, extrahashlen, included_pk, NULL, &is_expkey, &is_revkey, &pk); - log_debug ("checked signature using included key block: %s\n", - gpg_strerror (rc)); + if (opt.verbose) + log_debug ("checked signature using included key block: %s\n", + gpg_strerror (rc)); if (!rc) { /* The keyblock has been verified, we now import it. */ @@ -2202,10 +2203,14 @@ check_sig_and_print (CTX c, kbnode_t node) } } + /* Do do something with the result of the signature checking. */ if (!rc || gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE) { + /* We have checked the signature and the result is either a good + * signature or a bad signature. Further examination follows. */ kbnode_t un, keyblock; int count = 0; + int keyblock_has_pk = 0; /* For failsafe check. */ int statno; char keyid_str[50]; PKT_public_key *mainpk = NULL; @@ -2242,7 +2247,14 @@ check_sig_and_print (CTX c, kbnode_t node) { int valid; - if (un->pkt->pkttype==PKT_PUBLIC_KEY) + if (!keyblock_has_pk + && (un->pkt->pkttype == PKT_PUBLIC_KEY + || un->pkt->pkttype == PKT_PUBLIC_SUBKEY) + && !cmp_public_keys (un->pkt->pkt.public_key, pk)) + { + keyblock_has_pk = 1; + } + if (un->pkt->pkttype == PKT_PUBLIC_KEY) { mainpk = un->pkt->pkt.public_key; continue; @@ -2284,9 +2296,19 @@ check_sig_and_print (CTX c, kbnode_t node) log_printf ("\n"); count++; + /* At this point we could in theory stop because the primary + * UID flag is never set for more than one User ID per + * keyblock. However, we use this loop also for a failsafe + * check that the public key used to create the signature is + * contained in the keyring.*/ } log_assert (mainpk); + if (!keyblock_has_pk) + { + log_error ("signature key lost from keyblock\n"); + rc = gpg_error (GPG_ERR_INTERNAL); + } /* In case we did not found a valid textual userid above we print the first user id packet or a "[?]" instead along @@ -2442,14 +2464,15 @@ check_sig_and_print (CTX c, kbnode_t node) { if ((opt.verify_options & VERIFY_PKA_LOOKUPS)) pka_uri_from_sig (c, sig); /* Make sure PKA info is available. */ - rc = check_signatures_trust (c->ctrl, sig); + rc = check_signatures_trust (c->ctrl, keyblock, pk, sig); } /* Print extra information about the signature. */ if (sig->flags.expired) { log_info (_("Signature expired %s\n"), asctimestamp(sig->expiredate)); - rc = GPG_ERR_GENERAL; /* Need a better error here? */ + if (!rc) + rc = gpg_error (GPG_ERR_GENERAL); /* Need a better error here? */ } else if (sig->expiredate) log_info (_("Signature expires %s\n"), asctimestamp(sig->expiredate)); @@ -2526,7 +2549,7 @@ check_sig_and_print (CTX c, kbnode_t node) if (opt.batch && rc) g10_exit (1); } - else + else /* Error checking the signature. (neither Good nor Bad). */ { write_status_printf (STATUS_ERRSIG, "%08lX%08lX %d %d %02x %lu %d %s", (ulong)sig->keyid[0], (ulong)sig->keyid[1], diff --git a/g10/pkclist.c b/g10/pkclist.c index 370b474d0..ccebbb8a9 100644 --- a/g10/pkclist.c +++ b/g10/pkclist.c @@ -1,6 +1,7 @@ /* pkclist.c - create a list of public keys - * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, - * 2008, 2009, 2010 Free Software Foundation, Inc. + * Copyright (C) 1998-2020 Free Software Foundation, Inc. + * Copyright (C) 1997-2019 Werner Koch + * Copyright (C) 2015-2020 g10 Code GmbH * * This file is part of GnuPG. * @@ -16,6 +17,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 @@ -36,6 +38,7 @@ #include "../common/status.h" #include "photoid.h" #include "../common/i18n.h" +#include "../common/mbox-util.h" #include "tofu.h" #define CONTROL_D ('D' - 'A' + 1) @@ -537,44 +540,139 @@ write_trust_status (int statuscode, int trustlevel) } -/**************** - * Check whether we can trust this signature. - * Returns an error code if we should not trust this signature. - */ -int -check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) +/* Return true if MBOX matches one of the names in opt.sender_list. */ +static int +is_in_sender_list (const char *mbox) { - PKT_public_key *pk = xmalloc_clear( sizeof *pk ); + strlist_t sl; + + for (sl = opt.sender_list; sl; sl = sl->next) + if (!strcmp (mbox, sl->d)) + return 1; + return 0; +} + + +/* Check whether we can trust this signature. KEYBLOCK contains the + * key PK used to check the signature SIG. We need PK here in + * addition to KEYBLOCK so that we know the subkey used for + * verification. Returns an error code if we should not trust this + * signature (i.e. done by an not trusted key). */ +gpg_error_t +check_signatures_trust (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk, + PKT_signature *sig) +{ + gpg_error_t err = 0; + int uidbased = 0; /* 1 = signer's UID, 2 = use --sender option. */ unsigned int trustlevel = TRUST_UNKNOWN; - int rc=0; + PKT_public_key *mainpk; + PKT_user_id *targetuid; + const char *testedtarget = NULL; + kbnode_t n; - rc = get_pubkey_for_sig (ctrl, pk, sig, NULL); - if (rc) - { /* this should not happen */ - log_error("Ooops; the key vanished - can't check the trust\n"); - rc = GPG_ERR_NO_PUBKEY; - goto leave; - } - - if ( opt.trust_model==TM_ALWAYS ) + if (opt.trust_model == TM_ALWAYS) { - if( !opt.quiet ) + if (!opt.quiet) log_info(_("WARNING: Using untrusted key!\n")); if (opt.with_fingerprint) print_fingerprint (ctrl, NULL, pk, 1); goto leave; } - if(pk->flags.maybe_revoked && !pk->flags.revoked) + log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + mainpk = keyblock->pkt->pkt.public_key; + + if ((pk->flags.maybe_revoked && !pk->flags.revoked) + || (mainpk->flags.maybe_revoked && !mainpk->flags.revoked)) log_info(_("WARNING: this key might be revoked (revocation key" " not present)\n")); - trustlevel = get_validity (ctrl, NULL, pk, NULL, sig, 1); + /* Figure out the user ID which was used to create the signature. + * Note that the Signer's UID may be not a valid addr-spec but the + * plain value from the sub-packet; thus we need to check this + * before looking for the matching User ID (our parser makes sure + * that signers_uid has only the mbox if there is an mbox). */ + if (is_valid_mailbox (sig->signers_uid)) + uidbased = 1; /* We got the signer's UID and it is an addr-spec. */ + else if (opt.sender_list) + uidbased = 2; + else + uidbased = 0; + targetuid = NULL; + if (uidbased) + { + u32 tmpcreated = 0; /* Helper to find the lates user ID. */ + PKT_user_id *tmpuid; + + for (n=keyblock; n; n = n->next) + if (n->pkt->pkttype == PKT_USER_ID + && !(tmpuid = n->pkt->pkt.user_id)->attrib_data + && tmpuid->created /* (is valid) */ + && !tmpuid->flags.revoked + && !tmpuid->flags.expired) + { + if (!tmpuid->mbox) + tmpuid->mbox = mailbox_from_userid (tmpuid->name, 0); + if (!tmpuid->mbox) + continue; + + if (uidbased == 1) + { + if (!strcmp (tmpuid->mbox, sig->signers_uid) + && tmpuid->created > tmpcreated) + { + tmpcreated = tmpuid->created; + targetuid = tmpuid; + } + } + else + { + if (is_in_sender_list (tmpuid->mbox) + && tmpuid->created > tmpcreated) + { + tmpcreated = tmpuid->created; + targetuid = tmpuid; + } + } + } + + /* In addition restrict based on --sender. */ + if (uidbased == 1 && opt.sender_list + && targetuid && !is_in_sender_list (targetuid->mbox)) + { + testedtarget = targetuid->mbox; + targetuid = NULL; + } + + if (opt.verbose && targetuid) + log_info (_("checking User ID \"%s\"\n"), targetuid->mbox); + } + + trustlevel = get_validity (ctrl, NULL, pk, targetuid, sig, 1); + if (uidbased && !targetuid) + { + /* No user ID given but requested - force an undefined + * trustlevel but keep the trust flags. */ + trustlevel &= ~TRUST_MASK; + trustlevel |= TRUST_UNDEFINED; + if (!opt.quiet) + { + if (testedtarget) + log_info (_("option %s given but issuer \"%s\" does not match\n"), + "--sender", testedtarget); + else if (uidbased == 1) + log_info (_("issuer \"%s\" does not match any User ID\n"), + sig->signers_uid); + else if (opt.sender_list) + log_info (_("option %s given but no matching User ID found\n"), + "--sender"); + } + } if ( (trustlevel & TRUST_FLAG_REVOKED) ) { - write_status( STATUS_KEYREVOKED ); - if(pk->flags.revoked == 2) + write_status (STATUS_KEYREVOKED); + if (pk->flags.revoked == 2 || mainpk->flags.revoked == 2) log_info(_("WARNING: This key has been revoked by its" " designated revoker!\n")); else @@ -593,14 +691,13 @@ check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) log_info (_("Note: This key has been disabled.\n")); /* If we have PKA information adjust the trustlevel. */ - if (sig->pka_info && sig->pka_info->valid) + if (sig->pka_info && sig->pka_info->valid && !(uidbased && !targetuid)) { unsigned char fpr[MAX_FINGERPRINT_LEN]; PKT_public_key *primary_pk; size_t fprlen; int okay; - primary_pk = xmalloc_clear (sizeof *primary_pk); get_pubkey (ctrl, primary_pk, pk->main_keyid); fingerprint_from_pk (primary_pk, fpr, &fprlen); @@ -659,8 +756,12 @@ check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) case TRUST_UNKNOWN: case TRUST_UNDEFINED: write_trust_status (STATUS_TRUST_UNDEFINED, trustlevel); - log_info(_("WARNING: This key is not certified with" - " a trusted signature!\n")); + if (uidbased) + log_info(_("WARNING: The key's User ID is not certified with" + " a trusted signature!\n")); + else + log_info(_("WARNING: This key is not certified with" + " a trusted signature!\n")); log_info(_(" There is no indication that the " "signature belongs to the owner.\n" )); print_fingerprint (ctrl, NULL, pk, 1); @@ -674,12 +775,16 @@ check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) log_info(_(" The signature is probably a FORGERY.\n")); if (opt.with_fingerprint) print_fingerprint (ctrl, NULL, pk, 1); - rc = gpg_error (GPG_ERR_BAD_SIGNATURE); + err = gpg_error (GPG_ERR_BAD_SIGNATURE); break; case TRUST_MARGINAL: write_trust_status (STATUS_TRUST_MARGINAL, trustlevel); - log_info(_("WARNING: This key is not certified with" + if (uidbased) + log_info(_("WARNING: The key's User ID is not certified with" + " sufficiently trusted signatures!\n")); + else + log_info(_("WARNING: This key is not certified with" " sufficiently trusted signatures!\n")); log_info(_(" It is not certain that the" " signature belongs to the owner.\n" )); @@ -700,8 +805,7 @@ check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) } leave: - free_public_key( pk ); - return rc; + return err; } diff --git a/g10/test-stubs.c b/g10/test-stubs.c index 8b39484ef..9542d318b 100644 --- a/g10/test-stubs.c +++ b/g10/test-stubs.c @@ -57,10 +57,13 @@ g10_exit( int rc ) * We have to override the trustcheck from pkclist.c because * this utility assumes that all keys in the keyring are trustworthy */ -int -check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) +gpg_error_t +check_signatures_trust (ctrl_t ctrl, kbnode_t kblock, + PKT_public_key *pk, PKT_signature *sig) { (void)ctrl; + (void)kblock; + (void)pk; (void)sig; return 0; }