diff --git a/NEWS b/NEWS index b1e37b275..e5f308ab0 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Noteworthy changes in version 2.4.8 (unreleased) ------------------------------------------------ + * gpg: Fix a verification DoS due to a malicious subkey in the + keyring. [T7527] + Release-info: https://dev.gnupg.org/T7428 diff --git a/g10/getkey.c b/g10/getkey.c index 20ae84332..c4d02fbb1 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -316,27 +316,50 @@ pk_from_block (PKT_public_key *pk, kbnode_t keyblock, kbnode_t found_key) /* Specialized version of get_pubkey which retrieves the key based on * information in SIG. In contrast to get_pubkey PK is required. IF - * FORCED_PK is not NULL, this public key is used and copied to PK. */ + * FORCED_PK is not NULL, this public key is used and copied to PK. + * If R_KEYBLOCK is not NULL the entire keyblock is stored there if + * found and FORCED_PK is not used; if not used or on error NULL is + * stored there. */ gpg_error_t get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig, - PKT_public_key *forced_pk) + PKT_public_key *forced_pk, kbnode_t *r_keyblock) { + gpg_error_t err; const byte *fpr; size_t fprlen; + if (r_keyblock) + *r_keyblock = NULL; + if (forced_pk) { copy_public_key (pk, forced_pk); return 0; } + /* Make sure to request only keys cabable of signing. This makes + * sure that a subkey w/o a valid backsig or with bad usage flags + * will be skipped. */ + pk->req_usage = PUBKEY_USAGE_SIG; + /* First try the ISSUER_FPR info. */ fpr = issuer_fpr_raw (sig, &fprlen); - if (fpr && !get_pubkey_byfprint (ctrl, pk, NULL, fpr, fprlen)) + if (fpr && !get_pubkey_byfprint (ctrl, pk, r_keyblock, fpr, fprlen)) return 0; + if (r_keyblock) + { + release_kbnode (*r_keyblock); + *r_keyblock = NULL; + } /* Fallback to use the ISSUER_KEYID. */ - return get_pubkey (ctrl, pk, sig->keyid); + err = get_pubkey_bykid (ctrl, pk, r_keyblock, sig->keyid); + if (err && r_keyblock) + { + release_kbnode (*r_keyblock); + *r_keyblock = NULL; + } + return err; } @@ -354,6 +377,10 @@ get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig, * usage will be returned. As such, it is essential that * PK->REQ_USAGE be correctly initialized! * + * If R_KEYBLOCK is not NULL, then the first result's keyblock is + * returned in *R_KEYBLOCK. This should be freed using + * release_kbnode(). + * * Returns 0 on success, GPG_ERR_NO_PUBKEY if there is no public key * with the specified key id, or another error code if an error * occurs. @@ -361,24 +388,30 @@ get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig, * If the data was not read from the cache, then the self-signed data * has definitely been merged into the public key using * merge_selfsigs. */ -int -get_pubkey (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid) +gpg_error_t +get_pubkey_bykid (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, + u32 *keyid) { int internal = 0; - int rc = 0; + gpg_error_t rc = 0; + + if (r_keyblock) + *r_keyblock = NULL; #if MAX_PK_CACHE_ENTRIES - if (pk) + if (pk && !r_keyblock) { /* Try to get it from the cache. We don't do this when pk is - NULL as it does not guarantee that the user IDs are - cached. */ + * NULL as it does not guarantee that the user IDs are cached. + * The old get_pubkey_function did not check PK->REQ_USAGE when + * reading form the caceh. This is probably a bug. Note that + * the cache is not used when the caller asked to return the + * entire keyblock. This is because the cache does not + * associate the public key wit its primary key. */ pk_cache_entry_t ce; for (ce = pk_cache; ce; ce = ce->next) { if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1]) - /* XXX: We don't check PK->REQ_USAGE here, but if we don't - read from the cache, we do check it! */ { copy_public_key (pk, ce->pk); return 0; @@ -386,6 +419,7 @@ get_pubkey (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid) } } #endif + /* More init stuff. */ if (!pk) { @@ -431,16 +465,18 @@ get_pubkey (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid) ctx.req_usage = pk->req_usage; rc = lookup (ctrl, &ctx, 0, &kb, &found_key); if (!rc) - { - pk_from_block (pk, kb, found_key); - } + pk_from_block (pk, kb, found_key); getkey_end (ctrl, &ctx); + if (!rc && r_keyblock) + { + *r_keyblock = kb; + kb = NULL; + } release_kbnode (kb); } - if (!rc) - goto leave; - rc = GPG_ERR_NO_PUBKEY; + if (rc) /* Return a more useful error code. */ + rc = gpg_error (GPG_ERR_NO_PUBKEY); leave: if (!rc) @@ -451,6 +487,14 @@ leave: } +/* Wrapper for get_pubkey_bykid w/o keyblock return feature. */ +int +get_pubkey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid) +{ + return get_pubkey_bykid (ctrl, pk, NULL, keyid); +} + + /* Same as get_pubkey but if the key was not found the function tries * to import it from LDAP. FIXME: We should not need this but swicth * to a fingerprint lookup. */ @@ -563,28 +607,6 @@ get_pubkey_fast (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid) } -/* Return the entire keyblock used to create SIG. This is a - * specialized version of get_pubkeyblock. - * - * FIXME: This is a hack because get_pubkey_for_sig was already called - * and it could have used a cache to hold the key. */ -kbnode_t -get_pubkeyblock_for_sig (ctrl_t ctrl, PKT_signature *sig) -{ - const byte *fpr; - size_t fprlen; - kbnode_t keyblock; - - /* First try the ISSUER_FPR info. */ - fpr = issuer_fpr_raw (sig, &fprlen); - if (fpr && !get_pubkey_byfprint (ctrl, NULL, &keyblock, fpr, fprlen)) - return keyblock; - - /* Fallback to use the ISSUER_KEYID. */ - return get_pubkeyblock (ctrl, sig->keyid); -} - - /* Return the key block for the key with key id KEYID or NULL, if an * error occurs. Use release_kbnode() to release the key block. * @@ -3701,6 +3723,7 @@ finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact, kbnode_t latest_key; PKT_public_key *pk; int req_prim; + int diag_exactfound = 0; u32 curtime = make_timestamp (); if (r_flags) @@ -3731,11 +3754,10 @@ finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact, { if (want_exact) { - if (DBG_LOOKUP) - log_debug ("finish_lookup: exact search requested and found\n"); foundk = k; pk = k->pkt->pkt.public_key; pk->flags.exact = 1; + diag_exactfound = 1; break; } else if (!allow_adsk && (k->pkt->pkt.public_key->pubkey_usage @@ -3765,10 +3787,14 @@ finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact, log_debug ("finish_lookup: checking key %08lX (%s)(req_usage=%x)\n", (ulong) keyid_from_pk (keyblock->pkt->pkt.public_key, NULL), foundk ? "one" : "all", req_usage); + if (diag_exactfound && DBG_LOOKUP) + log_debug ("\texact search requested and found\n"); if (!req_usage) { latest_key = foundk ? foundk : keyblock; + if (DBG_LOOKUP) + log_debug ("\tno usage requested - accepting key\n"); goto found; } diff --git a/g10/gpg.h b/g10/gpg.h index c51bbbb46..0cdcb8b12 100644 --- a/g10/gpg.h +++ b/g10/gpg.h @@ -69,7 +69,8 @@ struct dirmngr_local_s; typedef struct dirmngr_local_s *dirmngr_local_t; /* Object used to describe a keyblock node. */ -typedef struct kbnode_struct *KBNODE; /* Deprecated use kbnode_t. */typedef struct kbnode_struct *kbnode_t; +typedef struct kbnode_struct *KBNODE; /* Deprecated use kbnode_t. */ +typedef struct kbnode_struct *kbnode_t; /* The handle for keydb operations. */ typedef struct keydb_handle_s *KEYDB_HANDLE; diff --git a/g10/keydb.h b/g10/keydb.h index 658c85a29..7d25b3550 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -332,9 +332,15 @@ void getkey_disable_caches(void); /* Return the public key used for signature SIG and store it at PK. */ gpg_error_t get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig, - PKT_public_key *forced_pk); + PKT_public_key *forced_pk, + kbnode_t *r_keyblock); -/* Return the public key with the key id KEYID and store it at PK. */ +/* Return the public key with the key id KEYID and store it at PK. + * Optionally return the entire keyblock. */ +gpg_error_t get_pubkey_bykid (ctrl_t ctrl, PKT_public_key *pk, + kbnode_t *r_keyblock, u32 *keyid); + +/* Same as get_pubkey_bykid but w/o r_keyblock. */ int get_pubkey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid); /* Same as get_pubkey but with auto LDAP fetch. */ diff --git a/g10/mainproc.c b/g10/mainproc.c index 86f5a2db9..308738839 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -1150,12 +1150,15 @@ proc_compressed (CTX c, PACKET *pkt) * used to verify the signature will be stored there, or NULL if not * found. If FORCED_PK is not NULL, this public key is used to verify * _data signatures_ and no key lookup is done. Returns: 0 = valid - * signature or an error code + * signature or an error code. If R_KEYBLOCK is not NULL the keyblock + * carries the used PK is stored there. The caller should always free + * the return value using release_kbnode. */ static int do_check_sig (CTX c, kbnode_t node, const void *extrahash, size_t extrahashlen, PKT_public_key *forced_pk, int *is_selfsig, - int *is_expkey, int *is_revkey, PKT_public_key **r_pk) + int *is_expkey, int *is_revkey, + PKT_public_key **r_pk, kbnode_t *r_keyblock) { PKT_signature *sig; gcry_md_hd_t md = NULL; @@ -1165,6 +1168,8 @@ do_check_sig (CTX c, kbnode_t node, const void *extrahash, size_t extrahashlen, if (r_pk) *r_pk = NULL; + if (r_keyblock) + *r_keyblock = NULL; log_assert (node->pkt->pkttype == PKT_SIGNATURE); if (is_selfsig) @@ -1241,16 +1246,19 @@ do_check_sig (CTX c, kbnode_t node, const void *extrahash, size_t extrahashlen, /* We only get here if we are checking the signature of a binary (0x00) or text document (0x01). */ rc = check_signature (c->ctrl, sig, md, extrahash, extrahashlen, - forced_pk, NULL, is_expkey, is_revkey, r_pk); + forced_pk, NULL, is_expkey, is_revkey, + r_pk, r_keyblock); if (! rc) md_good = md; else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2) { PKT_public_key *pk2; + if (r_keyblock) + release_kbnode (*r_keyblock); rc = check_signature (c->ctrl, sig, md2, extrahash, extrahashlen, forced_pk, NULL, is_expkey, is_revkey, - r_pk? &pk2 : NULL); + r_pk? &pk2 : NULL, r_keyblock); if (!rc) { md_good = md2; @@ -1413,7 +1421,7 @@ list_node (CTX c, kbnode_t node) { fflush (stdout); rc2 = do_check_sig (c, node, NULL, 0, NULL, - &is_selfsig, NULL, NULL, NULL); + &is_selfsig, NULL, NULL, NULL, NULL); switch (gpg_err_code (rc2)) { case 0: sigrc = '!'; break; @@ -1872,7 +1880,7 @@ check_sig_and_print (CTX c, kbnode_t node) PKT_public_key *pk = NULL; /* The public key for the signature or NULL. */ const void *extrahash = NULL; size_t extrahashlen = 0; - kbnode_t included_keyblock = NULL; + kbnode_t keyblock = NULL; char pkstrbuf[PUBKEY_STRING_SIZE] = { 0 }; @@ -1993,7 +2001,8 @@ check_sig_and_print (CTX c, kbnode_t node) { ambiguous: log_error(_("can't handle this ambiguous signature data\n")); - return 0; + rc = 0; + goto leave; } } /* End checking signature packet composition. */ @@ -2029,7 +2038,7 @@ check_sig_and_print (CTX c, kbnode_t node) log_info (_(" issuer \"%s\"\n"), sig->signers_uid); rc = do_check_sig (c, node, extrahash, extrahashlen, NULL, - NULL, &is_expkey, &is_revkey, &pk); + NULL, &is_expkey, &is_revkey, &pk, &keyblock); /* If the key is not found but the signature includes a key block we * use that key block for verification and on success import it. */ @@ -2037,6 +2046,7 @@ check_sig_and_print (CTX c, kbnode_t node) && sig->flags.key_block && opt.flags.auto_key_import) { + kbnode_t included_keyblock = NULL; PKT_public_key *included_pk; const byte *kblock; size_t kblock_len; @@ -2048,10 +2058,12 @@ check_sig_and_print (CTX c, kbnode_t node) kblock+1, kblock_len-1, sig->keyid, &included_keyblock)) { + /* Note: This is the only place where we use the forced_pk + * arg (ie. included_pk) with do_check_sig. */ rc = do_check_sig (c, node, extrahash, extrahashlen, included_pk, - NULL, &is_expkey, &is_revkey, &pk); + NULL, &is_expkey, &is_revkey, &pk, NULL); if (opt.verbose) - log_debug ("checked signature using included key block: %s\n", + log_info ("checked signature using included key block: %s\n", gpg_strerror (rc)); if (!rc) { @@ -2061,6 +2073,18 @@ check_sig_and_print (CTX c, kbnode_t node) } free_public_key (included_pk); + release_kbnode (included_keyblock); + + /* To make sure that nothing strange happened we check the + * signature again now using our own key store. This also + * returns the keyblock which we use later on. */ + if (!rc) + { + release_kbnode (keyblock); + keyblock = NULL; + rc = do_check_sig (c, node, extrahash, extrahashlen, NULL, + NULL, &is_expkey, &is_revkey, &pk, &keyblock); + } } /* If the key isn't found, check for a preferred keyserver. Note @@ -2107,8 +2131,13 @@ check_sig_and_print (CTX c, kbnode_t node) KEYSERVER_IMPORT_FLAG_QUICK); glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, extrahash, extrahashlen, NULL, - NULL, &is_expkey, &is_revkey, &pk); + { + release_kbnode (keyblock); + keyblock = NULL; + rc = do_check_sig (c, node, extrahash, extrahashlen, NULL, + NULL, &is_expkey, &is_revkey, &pk, + &keyblock); + } else if (DBG_LOOKUP) log_debug ("lookup via %s failed: %s\n", "Pref-KS", gpg_strerror (res)); @@ -2149,8 +2178,12 @@ check_sig_and_print (CTX c, kbnode_t node) /* Fixme: If the fingerprint is embedded in the signature, * compare it to the fingerprint of the returned key. */ if (!res) - rc = do_check_sig (c, node, extrahash, extrahashlen, NULL, - NULL, &is_expkey, &is_revkey, &pk); + { + release_kbnode (keyblock); + keyblock = NULL; + rc = do_check_sig (c, node, extrahash, extrahashlen, NULL, + NULL, &is_expkey, &is_revkey, &pk, &keyblock); + } else if (DBG_LOOKUP) log_debug ("lookup via %s failed: %s\n", "WKD", gpg_strerror (res)); } @@ -2180,8 +2213,13 @@ check_sig_and_print (CTX c, kbnode_t node) KEYSERVER_IMPORT_FLAG_QUICK); glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, extrahash, extrahashlen, NULL, - NULL, &is_expkey, &is_revkey, &pk); + { + release_kbnode (keyblock); + keyblock = NULL; + rc = do_check_sig (c, node, extrahash, extrahashlen, NULL, + NULL, &is_expkey, &is_revkey, &pk, + &keyblock); + } else if (DBG_LOOKUP) log_debug ("lookup via %s failed: %s\n", "KS", gpg_strerror (res)); } @@ -2192,7 +2230,7 @@ check_sig_and_print (CTX c, kbnode_t node) { /* We have checked the signature and the result is either a good * signature or a bad signature. Further examination follows. */ - kbnode_t un, keyblock; + kbnode_t un; int count = 0; int keyblock_has_pk = 0; /* For failsafe check. */ int statno; @@ -2210,18 +2248,6 @@ check_sig_and_print (CTX c, kbnode_t node) else statno = STATUS_GOODSIG; - /* FIXME: We should have the public key in PK and thus the - * keyblock has already been fetched. Thus we could use the - * fingerprint or PK itself to lookup the entire keyblock. That - * would best be done with a cache. */ - if (included_keyblock) - { - keyblock = included_keyblock; - included_keyblock = NULL; - } - else - keyblock = get_pubkeyblock_for_sig (c->ctrl, sig); - snprintf (keyid_str, sizeof keyid_str, "%08lX%08lX [uncertain] ", (ulong)sig->keyid[0], (ulong)sig->keyid[1]); @@ -2287,10 +2313,10 @@ check_sig_and_print (CTX c, kbnode_t node) * contained in the keyring.*/ } - log_assert (mainpk); - if (!keyblock_has_pk) + if (!mainpk || !keyblock_has_pk) { - log_error ("signature key lost from keyblock\n"); + log_error ("signature key lost from keyblock (%p,%p,%d)\n", + keyblock, mainpk, keyblock_has_pk); rc = gpg_error (GPG_ERR_INTERNAL); } @@ -2562,8 +2588,8 @@ check_sig_and_print (CTX c, kbnode_t node) log_error (_("Can't check signature: %s\n"), gpg_strerror (rc)); } + leave: free_public_key (pk); - release_kbnode (included_keyblock); xfree (issuer_fpr); return rc; } diff --git a/g10/packet.h b/g10/packet.h index b61c65417..d6cbef4bc 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -917,7 +917,7 @@ gpg_error_t check_signature (ctrl_t ctrl, const void *extrahash, size_t extrahashlen, PKT_public_key *forced_pk, u32 *r_expiredate, int *r_expired, int *r_revoked, - PKT_public_key **r_pk); + PKT_public_key **r_pk, kbnode_t *r_keyblock); /*-- pubkey-enc.c --*/ diff --git a/g10/sig-check.c b/g10/sig-check.c index 54db2089a..456c29320 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -131,6 +131,11 @@ check_key_verify_compliance (PKT_public_key *pk) * If R_PK is not NULL, the public key is stored at that address if it * was found; other wise NULL is stored. * + * If R_KEYBLOCK is not NULL, the entire keyblock used to verify the + * signature is stored at that address. If no key was found or on + * some other errors NULL is stored there. The callers needs to + * release the keyblock using release_kbnode (kb). + * * Returns 0 on success. An error code otherwise. */ gpg_error_t check_signature (ctrl_t ctrl, @@ -138,7 +143,7 @@ check_signature (ctrl_t ctrl, const void *extrahash, size_t extrahashlen, PKT_public_key *forced_pk, u32 *r_expiredate, int *r_expired, int *r_revoked, - PKT_public_key **r_pk) + PKT_public_key **r_pk, kbnode_t *r_keyblock) { int rc=0; PKT_public_key *pk; @@ -151,6 +156,8 @@ check_signature (ctrl_t ctrl, *r_revoked = 0; if (r_pk) *r_pk = NULL; + if (r_keyblock) + *r_keyblock = NULL; pk = xtrycalloc (1, sizeof *pk); if (!pk) @@ -181,7 +188,7 @@ check_signature (ctrl_t ctrl, log_info(_("WARNING: signature digest conflict in message\n")); rc = gpg_error (GPG_ERR_GENERAL); } - else if (get_pubkey_for_sig (ctrl, pk, sig, forced_pk)) + else if (get_pubkey_for_sig (ctrl, pk, sig, forced_pk, r_keyblock)) rc = gpg_error (GPG_ERR_NO_PUBKEY); else if ((rc = check_key_verify_compliance (pk))) ;/* Compliance failure. */ @@ -780,9 +787,9 @@ check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig) keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, pk->revkey[i].fprlen, keyid); - if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1]) - /* The signature was generated by a designated revoker. - Verify the signature. */ + /* If the signature was generated by a designated revoker + * verify the signature. */ + if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]) { gcry_md_hd_t md; @@ -790,9 +797,9 @@ check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig) BUG (); hash_public_key(md,pk); /* Note: check_signature only checks that the signature - is good. It does not fail if the key is revoked. */ + * is good. It does not fail if the key is revoked. */ rc = check_signature (ctrl, sig, md, NULL, 0, NULL, - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL); cache_sig_result(sig,rc); gcry_md_close (md); break; @@ -997,7 +1004,7 @@ check_signature_over_key_or_uid (ctrl_t ctrl, PKT_public_key *signer, if (IS_CERT (sig)) signer->req_usage = PUBKEY_USAGE_CERT; - rc = get_pubkey_for_sig (ctrl, signer, sig, NULL); + rc = get_pubkey_for_sig (ctrl, signer, sig, NULL, NULL); if (rc) { xfree (signer);