diff --git a/g10/gpg.c b/g10/gpg.c index 0e2a3b2dc..ac405a759 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -2391,11 +2391,15 @@ main (int argc, char **argv) opt.max_cert_depth = 5; opt.escape_from = 1; opt.flags.require_cross_cert = 1; - opt.import_options = IMPORT_REPAIR_KEYS; + opt.import_options = (IMPORT_REPAIR_KEYS + | IMPORT_COLLAPSE_UIDS + | IMPORT_COLLAPSE_SUBKEYS); opt.export_options = EXPORT_ATTRIBUTES; opt.keyserver_options.import_options = (IMPORT_REPAIR_KEYS | IMPORT_REPAIR_PKS_SUBKEY_BUG | IMPORT_SELF_SIGS_ONLY + | IMPORT_COLLAPSE_UIDS + | IMPORT_COLLAPSE_SUBKEYS | IMPORT_CLEAN); opt.keyserver_options.export_options = EXPORT_ATTRIBUTES; opt.keyserver_options.options = KEYSERVER_HONOR_PKA_RECORD; diff --git a/g10/import.c b/g10/import.c index 1e9532d38..23c162cb7 100644 --- a/g10/import.c +++ b/g10/import.c @@ -210,6 +210,11 @@ parse_import_options(char *str,unsigned int *options,int noisy) {"show-only", (IMPORT_SHOW | IMPORT_DRY_RUN), NULL, NULL}, + /* Hidden options which are enabled by default and are provided + * in case of problems with the respective implementation. */ + {"collapse-uids", IMPORT_COLLAPSE_UIDS, NULL, NULL}, + {"collapse-subkeys", IMPORT_COLLAPSE_SUBKEYS, NULL, NULL}, + /* Aliases for backward compatibility */ {"allow-local-sigs",IMPORT_LOCAL_SIGS,NULL,NULL}, {"repair-hkp-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,NULL}, @@ -403,7 +408,10 @@ read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, goto leave; } + /* We do the collapsing unconditionally although it is expected that + * clean keys are provided here. */ collapse_uids (&keyblock); + collapse_subkeys (&keyblock); clear_kbnode_flags (keyblock); if (chk_self_sigs (ctrl, keyblock, keyid, &non_self)) @@ -1947,9 +1955,12 @@ import_one_real (ctrl_t ctrl, /* Remove or collapse the user ids. */ if ((options & IMPORT_DROP_UIDS)) remove_all_uids (&keyblock); - else + else if ((options & IMPORT_COLLAPSE_UIDS)) collapse_uids (&keyblock); + if ((options & IMPORT_COLLAPSE_SUBKEYS)) + collapse_subkeys (&keyblock); + /* Clean the key that we're about to import, to cut down on things that we have to clean later. This has no practical impact on the end result, but does result in less logging which might confuse @@ -3010,7 +3021,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, _("rejected by import screener")); release_kbnode (keyblock); return 0; - } + } if (opt.verbose && !for_migration) { @@ -4100,6 +4111,115 @@ collapse_uids (kbnode_t *keyblock) } +/* + * It may happen that the imported keyblock has duplicated subkeys. + * We check this here and collapse those subkeys along with their + * binding self-signatures. + * Returns: True if the keyblock has changed. + */ +int +collapse_subkeys (kbnode_t *keyblock) +{ + kbnode_t kb1, kb2, sig1, sig2, last; + int any = 0; + + log_debug ("enter collapse_subkeys\n"); + for (kb1 = *keyblock; kb1; kb1 = kb1->next) + { + if (is_deleted_kbnode (kb1)) + continue; + + if (kb1->pkt->pkttype != PKT_PUBLIC_SUBKEY + && kb1->pkt->pkttype != PKT_SECRET_SUBKEY) + continue; + + /* We assume just a few duplicates and use a straightforward + * algorithm. */ + for (kb2 = kb1->next; kb2; kb2 = kb2->next) + { + if (is_deleted_kbnode (kb2)) + continue; + + if (kb2->pkt->pkttype != PKT_PUBLIC_SUBKEY + && kb2->pkt->pkttype != PKT_SECRET_SUBKEY) + continue; + + if (cmp_public_keys (kb1->pkt->pkt.public_key, + kb2->pkt->pkt.public_key)) + continue; + + /* We have a duplicated subkey. */ + any = 1; + + /* Take subkey-2's signatures, and attach them to subkey-1. */ + for (last = kb2; last->next; last = last->next) + { + if (is_deleted_kbnode (last)) + continue; + + if (last->next->pkt->pkttype != PKT_SIGNATURE) + break; + } + + /* Snip out subkye-2 */ + find_prev_kbnode (*keyblock, kb2, 0)->next = last->next; + + /* Put subkey-2 in place as part of subkey-1 */ + last->next = kb1->next; + kb1->next = kb2; + delete_kbnode (kb2); + + /* Now dedupe kb1 */ + for (sig1 = kb1->next; sig1; sig1 = sig1->next) + { + if (is_deleted_kbnode (sig1)) + continue; + + if (sig1->pkt->pkttype != PKT_SIGNATURE) + break; + + for (sig2 = sig1->next, last = sig1; + sig2; + last = sig2, sig2 = sig2->next) + { + if (is_deleted_kbnode (sig2)) + continue; + + if (sig2->pkt->pkttype != PKT_SIGNATURE) + break; + + if (!cmp_signatures (sig1->pkt->pkt.signature, + sig2->pkt->pkt.signature)) + { + /* We have a match, so delete the second + signature */ + delete_kbnode (sig2); + sig2 = last; + } + } + } + } + } + + commit_kbnode (keyblock); + + log_debug ("leave collapse_subkeys (any=%d)\n", any); + if (any && !opt.quiet) + { + const char *key="???"; + + if ((kb1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) ) + key = keystr_from_pk (kb1->pkt->pkt.public_key); + else if ((kb1 = find_kbnode (*keyblock, PKT_SECRET_KEY)) ) + key = keystr_from_pk (kb1->pkt->pkt.public_key); + + log_info (_("key %s: duplicated subkeys detected - merged\n"), key); + } + + return any; +} + + /* Check for a 0x20 revocation from a revocation key that is not present. This may be called without the benefit of merge_xxxx so you can't rely on pk->revkey and friends. */ diff --git a/g10/keyedit.c b/g10/keyedit.c index ac9f4688c..19fd0a1fc 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1174,6 +1174,8 @@ fix_keyblock (ctrl_t ctrl, kbnode_t *keyblockp) if (collapse_uids (keyblockp)) changed++; + if (collapse_subkeys (keyblockp)) + changed++; if (key_check_all_keysigs (ctrl, 1, *keyblockp, 0, 1)) changed++; reorder_keyblock (*keyblockp); diff --git a/g10/main.h b/g10/main.h index f7f6d0dd1..f13b1b929 100644 --- a/g10/main.h +++ b/g10/main.h @@ -394,7 +394,8 @@ gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, kbnode_t sec_keyblock, int batch, int force, int only_marked); -int collapse_uids( KBNODE *keyblock ); +int collapse_uids (kbnode_t *keyblock); +int collapse_subkeys (kbnode_t *keyblock); int get_revocation_reason (PKT_signature *sig, char **r_reason, char **r_comment, size_t *r_commentlen); diff --git a/g10/options.h b/g10/options.h index bf1bb8f50..b2bd543e6 100644 --- a/g10/options.h +++ b/g10/options.h @@ -369,6 +369,8 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define IMPORT_DRY_RUN (1<<12) #define IMPORT_DROP_UIDS (1<<13) #define IMPORT_SELF_SIGS_ONLY (1<<14) +#define IMPORT_COLLAPSE_UIDS (1<<15) +#define IMPORT_COLLAPSE_SUBKEYS (1<<16) #define EXPORT_LOCAL_SIGS (1<<0) #define EXPORT_ATTRIBUTES (1<<1)