+/**
+ * dedupsubkeys - Merge duplicate subkeys on a key.
+ * @key: The key to de-dup subkeys on.
+ *
+ * This function attempts to merge duplicate subkeys on a key. It returns
+ * 0 if the key is unchanged, otherwise the number of dups merged.
+ */
+int dedupsubkeys(struct openpgp_publickey *key)
+{
+ struct openpgp_signedpacket_list *cursubkey = NULL;
+ struct openpgp_signedpacket_list *dup = NULL;
+ struct openpgp_signedpacket_list *tmp = NULL;
+ int merged = 0;
+ uint64_t subkeyid;
+
+ log_assert(key != NULL);
+ cursubkey = key->subkeys;
+ while (cursubkey != NULL) {
+ dup = find_signed_packet(cursubkey->next, cursubkey->packet);
+ while (dup != NULL) {
+ get_packetid(cursubkey->packet, &subkeyid);
+ logthing(LOGTHING_INFO,
+ "Found duplicate subkey: 0x%016" PRIX64,
+ subkeyid);
+ merged++;
+ merge_packet_sigs(cursubkey, dup);
+ /*
+ * Remove the duplicate uid.
+ */
+ tmp = cursubkey;
+ while (tmp != NULL && tmp->next != dup) {
+ tmp = tmp->next;
+ }
+ log_assert(tmp != NULL);
+ tmp->next = dup->next;
+ dup->next = NULL;
+ free_signedpacket_list(dup);
+
+ dup = find_signed_packet(cursubkey->next,
+ cursubkey->packet);
+ }
+ cursubkey = cursubkey->next;
+ }
+
+ return merged;
+}
+
+/**
+ * check_sighashes - Check that sig hashes are correct.
+ * @key - the check to check the sig hashes of.
+ *
+ * Given an OpenPGP key confirm that all of the sigs on it have the
+ * appropriate 2 octet hash beginning, as stored as part of the sig.
+ * This is a simple way to remove junk sigs and, for example, catches
+ * subkey sig corruption as produced by old pksd implementations.
+ * Any sig that has an incorrect hash is removed from the key. If the
+ * hash cannot be checked (eg we don't support that hash type) we err
+ * on the side of caution and keep it.
+ */
+int clean_sighashes(struct openpgp_publickey *key,
+ struct openpgp_packet *sigdata,
+ struct openpgp_packet_list **sigs)
+{
+ struct openpgp_packet_list *tmpsig;
+ int removed = 0;
+
+ while (*sigs != NULL) {
+ if (check_packet_sighash(key, sigdata, (*sigs)->packet) == 0) {
+ tmpsig = *sigs;
+ *sigs = (*sigs)->next;
+ tmpsig->next = NULL;
+ free_packet_list(tmpsig);
+ removed++;
+ } else {
+ sigs = &(*sigs)->next;
+ }
+ }
+
+ return removed;
+}
+
+int clean_list_sighashes(struct openpgp_publickey *key,
+ struct openpgp_signedpacket_list *siglist)
+{
+ int removed = 0;
+
+ while (siglist != NULL) {
+ removed += clean_sighashes(key, siglist->packet,
+ &siglist->sigs);
+ siglist = siglist->next;
+ }
+
+ return removed;
+}
+
+int clean_key_sighashes(struct openpgp_publickey *key)
+{
+ int removed;
+
+ removed = clean_sighashes(key, NULL, &key->sigs);
+ removed += clean_list_sighashes(key, key->uids);
+ removed += clean_list_sighashes(key, key->subkeys);
+
+ return removed;
+}
+
+#define UAT_LIMIT 0xFFFF
+#define UID_LIMIT 1024
+#define PACKET_LIMIT 8383 /* Fits in 2 byte packet length */
+int clean_large_packets(struct openpgp_publickey *key)
+{
+ struct openpgp_signedpacket_list **curuid = NULL;
+ struct openpgp_signedpacket_list *tmp = NULL;
+ bool drop;
+ int dropped = 0;
+
+ log_assert(key != NULL);
+ curuid = &key->uids;
+ while (*curuid != NULL) {
+ drop = false;
+ switch ((*curuid)->packet->tag) {
+ case OPENPGP_PACKET_UID:
+ if ((*curuid)->packet->length > UID_LIMIT)
+ drop = true;
+ break;
+ case OPENPGP_PACKET_UAT:
+ if ((*curuid)->packet->length > UAT_LIMIT)
+ drop = true;
+ break;
+ default:
+ if ((*curuid)->packet->length > PACKET_LIMIT)
+ drop = true;
+ break;
+ }
+
+ if (drop) {
+ logthing(LOGTHING_INFO,
+ "Dropping large (%d) packet, type %d",
+ (*curuid)->packet->length,
+ (*curuid)->packet->tag);
+ /* Remove the entire large signed packet list */
+ tmp = *curuid;
+ *curuid = (*curuid)->next;
+ tmp->next = NULL;
+ free_signedpacket_list(tmp);
+ dropped++;
+ } else {
+ curuid = &(*curuid)->next;
+ }
+ }
+
+ return dropped;
+}
+