diff --git a/g10/getkey.c b/g10/getkey.c index 75ec44c6a..81eb2d913 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -422,13 +422,21 @@ 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. */ + * 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. */ gpg_error_t -get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig) +get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig, + PKT_public_key *forced_pk) { const byte *fpr; size_t fprlen; + if (forced_pk) + { + copy_public_key (pk, forced_pk); + return 0; + } + /* First try the new ISSUER_FPR info. */ fpr = issuer_fpr_raw (sig, &fprlen); if (fpr && !get_pubkey_byfprint (ctrl, pk, NULL, fpr, fprlen)) @@ -1650,7 +1658,7 @@ get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname) kbnode_t found_key; unsigned int infoflags; - err = read_key_from_file (ctrl, fname, &keyblock); + err = read_key_from_file_or_buffer (ctrl, fname, NULL, 0, &keyblock); if (!err) { /* Warning: node flag bits 0 and 1 should be preserved by @@ -1669,6 +1677,56 @@ get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname) } +/* Return a public key from the buffer (BUFFER, BUFLEN). The key is + * onlyretruned if it matches the keyid given in WANT_KEYID. On + * success the key is stored at the caller provided PKBUF structure. + * The caller must release the content of PK by calling + * release_public_key_parts (or, if PKBUF was malloced, using + * free_public_key). If R_KEYBLOCK is not NULL the full keyblock is + * also stored there. */ +gpg_error_t +get_pubkey_from_buffer (ctrl_t ctrl, PKT_public_key *pkbuf, + const void *buffer, size_t buflen, u32 *want_keyid, + kbnode_t *r_keyblock) +{ + gpg_error_t err; + kbnode_t keyblock; + kbnode_t node; + PKT_public_key *pk; + + if (r_keyblock) + *r_keyblock = NULL; + + err = read_key_from_file_or_buffer (ctrl, NULL, buffer, buflen, &keyblock); + if (!err) + { + merge_selfsigs (ctrl, keyblock); + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + pk = node->pkt->pkt.public_key; + keyid_from_pk (pk, NULL); + if (pk->keyid[0] == want_keyid[0] + && pk->keyid[1] == want_keyid[1]) + break; + } + } + if (node) + copy_public_key (pkbuf, pk); + else + err = gpg_error (GPG_ERR_NO_PUBKEY); + } + + if (!err && r_keyblock) + *r_keyblock = keyblock; + else + release_kbnode (keyblock); + return err; +} + + /* Lookup a key with the specified fingerprint. * * If PK is not NULL, the public key of the first result is returned diff --git a/g10/gpgv.c b/g10/gpgv.c index 29324db7b..9be3eff0b 100644 --- a/g10/gpgv.c +++ b/g10/gpgv.c @@ -477,14 +477,26 @@ keyserver_import_ldap (const char *name) gpg_error_t -read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) +read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, + const void *buffer, size_t buflen, + kbnode_t *r_keyblock) { (void)ctrl; (void)fname; + (void)buffer; + (void)buflen; (void)r_keyblock; return -1; } +gpg_error_t +import_included_key_block (ctrl_t ctrl, kbnode_t keyblock) +{ + (void)ctrl; + (void)keyblock; + return -1; +} + /* Stub: * No encryption here but mainproc links to these functions. diff --git a/g10/import.c b/g10/import.c index 46a430053..7097f75e6 100644 --- a/g10/import.c +++ b/g10/import.c @@ -316,7 +316,9 @@ import_release_stats_handle (import_stats_t p) * file. */ gpg_error_t -read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) +read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, + const void *buffer, size_t buflen, + kbnode_t *r_keyblock) { gpg_error_t err; iobuf_t inp; @@ -330,35 +332,45 @@ read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) *r_keyblock = NULL; - inp = iobuf_open (fname); - if (!inp) - err = gpg_error_from_syserror (); - else if (is_secured_file (iobuf_get_fd (inp))) - { - iobuf_close (inp); - inp = NULL; - err = gpg_error (GPG_ERR_EPERM); - } - else - err = 0; - if (err) - { - log_error (_("can't open '%s': %s\n"), - iobuf_is_pipe_filename (fname)? "[stdin]": fname, - gpg_strerror (err)); - if (gpg_err_code (err) == GPG_ERR_ENOENT) - err = gpg_error (GPG_ERR_NO_PUBKEY); - goto leave; - } + log_assert (!!fname ^ !!buffer); - /* Push the armor filter. */ - { - armor_filter_context_t *afx; - afx = new_armor_context (); - afx->only_keyblocks = 1; - push_armor_filter (afx, inp); - release_armor_context (afx); - } + if (fname) + { + inp = iobuf_open (fname); + if (!inp) + err = gpg_error_from_syserror (); + else if (is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + err = gpg_error (GPG_ERR_EPERM); + } + else + err = 0; + if (err) + { + log_error (_("can't open '%s': %s\n"), + iobuf_is_pipe_filename (fname)? "[stdin]": fname, + gpg_strerror (err)); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + /* Push the armor filter. */ + { + armor_filter_context_t *afx; + afx = new_armor_context (); + afx->only_keyblocks = 1; + push_armor_filter (afx, inp); + release_armor_context (afx); + } + + } + else /* Read from buffer (No armor expected). */ + { + inp = iobuf_temp_with_content (buffer, buflen); + } /* Read the first non-v3 keyblock. */ while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys))) @@ -373,7 +385,8 @@ read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) { if (gpg_err_code (err) != GPG_ERR_INV_KEYRING) log_error (_("error reading '%s': %s\n"), - iobuf_is_pipe_filename (fname)? "[stdin]": fname, + fname? (iobuf_is_pipe_filename (fname)? "[stdin]": fname) + /* */ : "[buffer]", gpg_strerror (err)); goto leave; } @@ -409,7 +422,8 @@ read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) { iobuf_close (inp); /* Must invalidate that ugly cache to actually close the file. */ - iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); + if (fname) + iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); } release_kbnode (keyblock); /* FIXME: Do we need to free PENDING_PKT ? */ @@ -417,6 +431,38 @@ read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) } +/* Import an already checked public key which was included in a + * signature and the signature verified out using this key. */ +gpg_error_t +import_included_key_block (ctrl_t ctrl, kbnode_t keyblock) +{ + gpg_error_t err; + struct import_stats_s *stats; + import_filter_t save_filt; + int save_armor = opt.armor; + + opt.armor = 0; + stats = import_new_stats_handle (); + save_filt = save_and_clear_import_filter (); + if (!save_filt) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* FIXME: Should we introduce a dedicated KEYORG ? */ + err = import_one (ctrl, keyblock, + stats, NULL, 0, 0, 0, 0, + NULL, NULL, KEYORG_UNKNOWN, NULL, NULL); + + leave: + restore_import_filter (save_filt); + import_release_stats_handle (stats); + opt.armor = save_armor; + return err; +} + + /* * Import the public keys from the given filename. Input may be armored. diff --git a/g10/keydb.h b/g10/keydb.h index 8d956b264..f61515f42 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -315,7 +315,8 @@ 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 *pk, PKT_signature *sig, + PKT_public_key *forced_pk); /* Return the public key with the key id KEYID and store it at PK. */ int get_pubkey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid); @@ -372,6 +373,11 @@ gpg_error_t get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, gpg_error_t get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname); +/* Get a public key from a buffer. */ +gpg_error_t get_pubkey_from_buffer (ctrl_t ctrl, PKT_public_key *pkbuf, + const void *buffer, size_t buflen, + u32 *want_keyid, kbnode_t *r_keyblock); + /* Return the public key with the key id KEYID iff the secret key is * available and store it at PK. */ gpg_error_t get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid); diff --git a/g10/main.h b/g10/main.h index 17bff63b7..250467a95 100644 --- a/g10/main.h +++ b/g10/main.h @@ -351,8 +351,10 @@ gpg_error_t parse_and_set_import_filter (const char *string); import_filter_t save_and_clear_import_filter (void); void restore_import_filter (import_filter_t filt); -gpg_error_t read_key_from_file (ctrl_t ctrl, const char *fname, - kbnode_t *r_keyblock); +gpg_error_t read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, + const void *buffer, size_t buflen, + kbnode_t *r_keyblock); +gpg_error_t import_included_key_block (ctrl_t ctrl, kbnode_t keyblock); void import_keys (ctrl_t ctrl, char **fnames, int nnames, import_stats_t stats_hd, unsigned int options, int origin, const char *url); diff --git a/g10/mainproc.c b/g10/mainproc.c index e9e886f59..07366b068 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -1,6 +1,7 @@ /* mainproc.c - handle packets * Copyright (C) 1998-2009 Free Software Foundation, Inc. * Copyright (C) 2013-2014 Werner Koch + * Copyright (C) 2020 g10 Code GmbH * * This file is part of GnuPG. * @@ -956,10 +957,13 @@ proc_compressed (CTX c, PACKET *pkt) /* * Check the signature. If R_PK is not NULL a copy of the public key * used to verify the signature will be stored there, or NULL if not - * found. Returns: 0 = valid signature or an error code + * 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 */ static int -do_check_sig (CTX c, kbnode_t node, int *is_selfsig, +do_check_sig (CTX c, kbnode_t node, + PKT_public_key *forced_pk, int *is_selfsig, int *is_expkey, int *is_revkey, PKT_public_key **r_pk) { PKT_signature *sig; @@ -1045,14 +1049,18 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig, /* We only get here if we are checking the signature of a binary (0x00) or text document (0x01). */ - rc = check_signature2 (c->ctrl, sig, md, NULL, is_expkey, is_revkey, r_pk); + rc = check_signature2 (c->ctrl, sig, md, + forced_pk, + NULL, is_expkey, is_revkey, r_pk); if (! rc) md_good = md; else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2) { PKT_public_key *pk2; - rc = check_signature2 (c->ctrl, sig, md2, NULL, is_expkey, is_revkey, + rc = check_signature2 (c->ctrl, sig, md2, + forced_pk, + NULL, is_expkey, is_revkey, r_pk? &pk2 : NULL); if (!rc) { @@ -1215,7 +1223,7 @@ list_node (CTX c, kbnode_t node) if (opt.check_sigs) { fflush (stdout); - rc2 = do_check_sig (c, node, &is_selfsig, NULL, NULL, NULL); + rc2 = do_check_sig (c, node, NULL, &is_selfsig, NULL, NULL, NULL); switch (gpg_err_code (rc2)) { case 0: sigrc = '!'; break; @@ -1746,6 +1754,7 @@ check_sig_and_print (CTX c, kbnode_t node) int is_revkey = 0; char *issuer_fpr = NULL; PKT_public_key *pk = NULL; /* The public key for the signature or NULL. */ + kbnode_t included_keyblock = NULL; if (opt.skip_verify) { @@ -1894,7 +1903,39 @@ check_sig_and_print (CTX c, kbnode_t node) if (sig->signers_uid) log_info (_(" issuer \"%s\"\n"), sig->signers_uid); - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, NULL, NULL, &is_expkey, &is_revkey, &pk); + + /* If the key is not found but the signaure includes a key bnlock we + * import that key block and trry again. We keep this key block + * only if the signature verifies. */ + /* FIXME: Shall we add an option to disable it or use it only if + * --auto-key-retriueve is set? */ + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY + && sig->flags.key_block) + /* && (opt.keyserver_options.options & KEYSERVER_AUTO_KEY_RETRIEVE)) */ + { + PKT_public_key *included_pk; + const byte *kblock; + size_t kblock_len; + + included_pk = xcalloc (1, sizeof *included_pk); + kblock = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_BLOCK, &kblock_len); + if (kblock && kblock_len > 1 + && !get_pubkey_from_buffer (c->ctrl, included_pk, + kblock+1, kblock_len-1, + sig->keyid, &included_keyblock)) + { + rc = do_check_sig (c, node, included_pk, + NULL, &is_expkey, &is_revkey, &pk); + if (!rc) + { + /* The keyblock has been verified, we now import it. */ + rc = import_included_key_block (c->ctrl, included_keyblock); + } + + } + free_public_key (included_pk); + } /* If the key isn't found, check for a preferred keyserver. Note * that this is only done if honor-keyserver-url has been set. We @@ -1940,7 +1981,7 @@ check_sig_and_print (CTX c, kbnode_t node) glo_ctrl.in_auto_key_retrieve--; if (!res) rc = do_check_sig (c, node, NULL, - &is_expkey, &is_revkey, &pk); + NULL, &is_expkey, &is_revkey, &pk); else if (DBG_LOOKUP) log_debug ("lookup via %s failed: %s\n", "Pref-KS", gpg_strerror (res)); @@ -1980,7 +2021,7 @@ 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, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, NULL, NULL, &is_expkey, &is_revkey, &pk); else if (DBG_LOOKUP) log_debug ("lookup via %s failed: %s\n", "WKD", gpg_strerror (res)); } @@ -2014,7 +2055,8 @@ check_sig_and_print (CTX c, kbnode_t node) glo_ctrl.in_auto_key_retrieve--; free_keyserver_spec (spec); if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, NULL, + NULL, &is_expkey, &is_revkey, &pk); else if (DBG_LOOKUP) log_debug ("lookup via %s failed: %s\n", "PKA", gpg_strerror (res)); @@ -2046,7 +2088,8 @@ check_sig_and_print (CTX c, kbnode_t node) res = keyserver_import_fprint (c->ctrl, p, n, opt.keyserver, 1); glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, NULL, + NULL, &is_expkey, &is_revkey, &pk); else if (DBG_LOOKUP) log_debug ("lookup via %s failed: %s\n", "KS", gpg_strerror (res)); } @@ -2075,7 +2118,13 @@ check_sig_and_print (CTX c, kbnode_t node) * 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. */ - keyblock = get_pubkeyblock_for_sig (c->ctrl, sig); + 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]); @@ -2388,6 +2437,7 @@ check_sig_and_print (CTX c, kbnode_t node) } free_public_key (pk); + release_kbnode (included_keyblock); xfree (issuer_fpr); return rc; } diff --git a/g10/packet.h b/g10/packet.h index b0431d548..4c0655ca2 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -207,6 +207,7 @@ typedef struct unsigned policy_url:1; /* At least one policy URL is present */ unsigned notation:1; /* At least one notation is present */ unsigned pref_ks:1; /* At least one preferred keyserver is present */ + unsigned key_block:1; /* A key block subpacket is present. */ unsigned expired:1; unsigned pka_tried:1; /* Set if we tried to retrieve the PKA record. */ } flags; @@ -873,9 +874,10 @@ int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest); * R_PK is not NULL, it is stored at RET_PK.) DIGEST contains a * valid hash context that already includes the signed data. This * function adds the relevant meta-data to the hash before finalizing - * it and verifying the signature. */ + * it and verifying the signature. FOCRED_PK is usually NULL. */ gpg_error_t check_signature2 (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest, + PKT_public_key *forced_pk, u32 *r_expiredate, int *r_expired, int *r_revoked, PKT_public_key **r_pk); diff --git a/g10/parse-packet.c b/g10/parse-packet.c index 5e8375b98..6646becd4 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -2109,6 +2109,10 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, if (p) sig->flags.notation = 1; + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_BLOCK, NULL); + if (p) + sig->flags.key_block = 1; + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_REVOCABLE, NULL); if (p && *p == 0) sig->flags.revocable = 0; diff --git a/g10/pkclist.c b/g10/pkclist.c index 8d924ad5c..542164949 100644 --- a/g10/pkclist.c +++ b/g10/pkclist.c @@ -548,7 +548,7 @@ check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) unsigned int trustlevel = TRUST_UNKNOWN; int rc=0; - rc = get_pubkey_for_sig (ctrl, pk, sig); + 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"); diff --git a/g10/sig-check.c b/g10/sig-check.c index 5ec82470b..44e7871ee 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -69,7 +69,7 @@ sig_check_dump_stats (void) int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) { - return check_signature2 (ctrl, sig, digest, NULL, NULL, NULL, NULL); + return check_signature2 (ctrl, sig, digest, NULL, NULL, NULL, NULL, NULL); } @@ -95,6 +95,9 @@ check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) * signature data from the version number through the hashed subpacket * data (inclusive) is hashed.") * + * If FORCED_PK is not NULL this public key is used to verify the + * signature and no other public key is looked up. + * * If R_EXPIREDATE is not NULL, R_EXPIREDATE is set to the key's * expiry. * @@ -112,7 +115,9 @@ check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) * Returns 0 on success. An error code otherwise. */ gpg_error_t check_signature2 (ctrl_t ctrl, - PKT_signature *sig, gcry_md_hd_t digest, u32 *r_expiredate, + PKT_signature *sig, gcry_md_hd_t digest, + PKT_public_key *forced_pk, + u32 *r_expiredate, int *r_expired, int *r_revoked, PKT_public_key **r_pk) { int rc=0; @@ -156,7 +161,7 @@ check_signature2 (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)) + else if (get_pubkey_for_sig (ctrl, pk, sig, forced_pk)) rc = gpg_error (GPG_ERR_NO_PUBKEY); else if (!gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION, pk->pubkey_algo, pk->pkey, @@ -923,7 +928,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); + rc = get_pubkey_for_sig (ctrl, signer, sig, NULL); if (rc) { xfree (signer); diff --git a/g10/test-stubs.c b/g10/test-stubs.c index e6d0bd0f8..0ea9379c6 100644 --- a/g10/test-stubs.c +++ b/g10/test-stubs.c @@ -241,14 +241,27 @@ keyserver_import_ldap (const char *name) } gpg_error_t -read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) +read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, + const void *buffer, size_t buflen, + kbnode_t *r_keyblock) { (void)ctrl; (void)fname; + (void)buffer; + (void)buflen; (void)r_keyblock; return -1; } +gpg_error_t +import_included_key_block (ctrl_t ctrl, kbnode_t keyblock) +{ + (void)ctrl; + (void)keyblock; + return -1; +} + + /* Stub: * No encryption here but mainproc links to these functions. */