diff --git a/NEWS b/NEWS index ce166874..1294e0b7 100644 --- a/NEWS +++ b/NEWS @@ -15,8 +15,10 @@ Noteworthy changes in version 1.7.0 (unreleased) [C25/A14/R_] GPGME_PK_EDDSA NEW. gpgme_set_ctx_flag NEW. gpgme_data_set_flag NEW. - gpgme_signature_t EXTENDED: New field tofu. + gpgme_signature_t EXTENDED: New field key. + gpgme_key_t EXTENDED: New field fpr. gpgme_subkey_t EXTENDED: New field keygrip. + gpgme_user_id_t EXTENDED: New field tofu. gpgme_tofu_policy_t NEW. gpgme_tofu_info_t NEW. GPGME_STATUS_KEY_CONSIDERED NEW. diff --git a/doc/gpgme.texi b/doc/gpgme.texi index b28c6cad..02551d98 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -3017,6 +3017,10 @@ This is the key ID of the subkey in hexadecimal digits. This is the fingerprint of the subkey in hexadecimal digits, if available. +@item char *keygrip +The keygrip of the subkey in hex digit form or @code{NULL} if not +availabale. + @item long int timestamp This is the creation timestamp of the subkey. This is -1 if the timestamp is invalid, and 0 if it is not available. @@ -3144,6 +3148,16 @@ This is the comment component of @code{uid}, if available. @item char *email This is the email component of @code{uid}, if available. +@item char *address; +The mail address (addr-spec from RFC-5322) of the user ID string. +This is general the same as the @code{email} part of this structure +but might be slightly different. If no mail address is available +@code{NULL} is stored. + +@item gpgme_tofu_info_t tofu +If not @code{NULL} information from the TOFU database pertaining to +this user id. + @item gpgme_key_sig_t signatures This is a linked list with the signatures on this user ID. @end table @@ -3168,8 +3182,8 @@ This is true if the key is disabled. @item unsigned int invalid : 1 This is true if the key is invalid. This might have several reasons, -for a example for the S/MIME backend, it will be set in during key -listsing if the key could not be validated due to a missing +for a example for the S/MIME backend, it will be set during key +listings if the key could not be validated due to missing certificates or unmatched policies. @item unsigned int can_encrypt : 1 @@ -3224,6 +3238,13 @@ in the list is the primary key and usually available. @item gpgme_user_id_t uids This is a linked list with the user IDs of the key. The first user ID in the list is the main (or primary) user ID. + +@item char *fpr +This field gives the fingerprint of the primary key. Note that +this is a copy of the fingerprint of the first subkey. For an +incomplete key (for example from a verification result) a subkey may +be missing but this field may be set nevertheless. + @end table @end deftp @@ -4870,6 +4891,13 @@ The hash algorithm used to create this signature. @item char *pka_address The mailbox from the PKA information or @code{NULL}. + +@item gpgme_key_t key +An object describing the key used to create the signature. This key +object may be incomplete in that it only conveys information +availabale directly with a signature. It may also be @code{NULL} if +such information is not readily available. + @end table @end deftp diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 49cea778..c07cac8d 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -624,6 +624,41 @@ struct _gpgme_engine_info typedef struct _gpgme_engine_info *gpgme_engine_info_t; +/* An object with TOFU information. */ +struct _gpgme_tofu_info +{ + struct _gpgme_tofu_info *next; + + /* The TOFU validity: + * 0 := conflict + * 1 := key without history + * 2 := key with too little history + * 3 := key with enough history for basic trust + * 4 := key with a lot of history + */ + unsigned int validity : 3; + + /* The TOFU policy (gpgme_tofu_policy_t). */ + unsigned int policy : 4; + + unsigned int _rfu : 25; + + /* Number of signatures seen for this binding. Capped at USHRT_MAX. */ + unsigned short signcount; + /* Number of encryptions done with this binding. Capped at USHRT_MAX. */ + unsigned short encrcount; + + /* Number of seconds since the first and the most recently seen + * message was verified. */ + unsigned int firstseen; + unsigned int lastseen; + + /* If non-NULL a human readable string summarizing the TOFU data. */ + char *description; +}; +typedef struct _gpgme_tofu_info *gpgme_tofu_info_t; + + /* A subkey from a key. */ struct _gpgme_subkey { @@ -807,6 +842,9 @@ struct _gpgme_user_id * might be slightly different. IF no mail address is available * NULL is stored. */ char *address; + + /* The malloced tofo information or NULL. */ + gpgme_tofu_info_t tofu; }; typedef struct _gpgme_user_id *gpgme_user_id_t; @@ -883,6 +921,11 @@ struct _gpgme_key /* The keylist mode that was active when listing the key. */ gpgme_keylist_mode_t keylist_mode; + + /* This field gives the fingerprint of the primary key. Note that + * this is a copy of the FPR of the first subkey. We need it here + * to allow for an incomplete key object. */ + char *fpr; }; typedef struct _gpgme_key *gpgme_key_t; @@ -1570,50 +1613,6 @@ typedef enum gpgme_sigsum_t; -struct _gpgme_tofu_info -{ - struct _gpgme_tofu_info *next; - - /* The mail address (addr-spec from RFC5322) of the tofu binding. - * - * If no mail address is set for a User ID this is the name used - * for the user ID. Can be ambiguous when the same mail address or - * name is used in multiple user ids. - */ - char *address; - - /* The fingerprint of the primary key. */ - char *fpr; - - /* The TOFU validity: - * 0 := conflict - * 1 := key without history - * 2 := key with too little history - * 3 := key with enough history for basic trust - * 4 := key with a lot of history - */ - unsigned int validity : 3; - - /* The TOFU policy (gpgme_tofu_policy_t). */ - unsigned int policy : 4; - - unsigned int _rfu : 25; - - /* Number of signatures seen for this binding. Capped at USHRT_MAX. */ - unsigned short signcount; - unsigned short reserved; - - /* Number of seconds since the first and the most recently seen - * message was verified. */ - unsigned int firstseen; - unsigned int lastseen; - - /* If non-NULL a human readable string summarizing the TOFU data. */ - char *description; -}; -typedef struct _gpgme_tofu_info *gpgme_tofu_info_t; - - struct _gpgme_signature { struct _gpgme_signature *next; @@ -1621,7 +1620,7 @@ struct _gpgme_signature /* A summary of the signature status. */ gpgme_sigsum_t summary; - /* The fingerprint or key ID of the signature. */ + /* The fingerprint of the signature. This can be a subkey. */ char *fpr; /* The status of the signature. */ @@ -1660,8 +1659,9 @@ struct _gpgme_signature /* The mailbox from the PKA information or NULL. */ char *pka_address; - /* If non-NULL, TOFU info for this signature are available. */ - gpgme_tofu_info_t tofu; + /* If non-NULL, a possible incomplete key object with the data + * available for the signature. */ + gpgme_key_t key; }; typedef struct _gpgme_signature *gpgme_signature_t; diff --git a/src/key.c b/src/key.c index f642501b..38acc712 100644 --- a/src/key.c +++ b/src/key.c @@ -356,6 +356,7 @@ gpgme_key_unref (gpgme_key_t key) { gpgme_user_id_t next_uid = uid->next; gpgme_key_sig_t keysig = uid->signatures; + gpgme_tofu_info_t tofu = uid->tofu; while (keysig) { @@ -373,8 +374,21 @@ gpgme_key_unref (gpgme_key_t key) free (keysig); keysig = next_keysig; } + + while (tofu) + { + /* NB: The ->next is currently not used but we are prepared + * for it. */ + gpgme_tofu_info_t tofu_next = tofu->next; + + free (tofu->description); + free (tofu); + tofu = tofu_next; + } + if (uid->address && uid->address != uid->email) free (uid->address); + free (uid); uid = next_uid; } @@ -386,10 +400,13 @@ gpgme_key_unref (gpgme_key_t key) if (key->chain_id) free (key->chain_id); + if (key->fpr) + free (key->fpr); free (key); } + /* Support functions. */ diff --git a/src/keylist.c b/src/keylist.c index 5a346ea4..38ddd0c5 100644 --- a/src/keylist.c +++ b/src/keylist.c @@ -708,6 +708,22 @@ keylist_colon_handler (void *priv, char *line) if (!subkey->fpr) return gpg_error_from_syserror (); } + /* If this is the first subkey, store the fingerprint also + in the KEY object. */ + if (subkey == key->subkeys) + { + if (key->fpr && strcmp (key->fpr, subkey->fpr)) + { + /* FPR already set but mismatch: Should never happen. */ + return trace_gpg_error (GPG_ERR_INTERNAL); + } + if (!key->fpr) + { + key->fpr = strdup (subkey->fpr); + if (!key->fpr) + return gpg_error_from_syserror (); + } + } } /* Field 13 has the gpgsm chain ID (take only the first one). */ diff --git a/src/ops.h b/src/ops.h index 9c275292..97b1019f 100644 --- a/src/ops.h +++ b/src/ops.h @@ -138,9 +138,11 @@ gpgme_error_t _gpgme_progress_status_handler (void *priv, gpgme_error_t _gpgme_key_new (gpgme_key_t *r_key); gpgme_error_t _gpgme_key_add_subkey (gpgme_key_t key, gpgme_subkey_t *r_subkey); -gpgme_error_t _gpgme_key_append_name (gpgme_key_t key, const char *src, int convert); +gpgme_error_t _gpgme_key_append_name (gpgme_key_t key, + const char *src, int convert); gpgme_key_sig_t _gpgme_key_add_sig (gpgme_key_t key, char *src); + /* From keylist.c. */ void _gpgme_op_keylist_event_cb (void *data, gpgme_event_io_t type, diff --git a/src/verify.c b/src/verify.c index 1ec09fe8..173d1cb7 100644 --- a/src/verify.c +++ b/src/verify.c @@ -49,22 +49,6 @@ typedef struct } *op_data_t; -static void -release_tofu_info (gpgme_tofu_info_t t) -{ - while (t) - { - gpgme_tofu_info_t t2 = t->next; - - free (t->address); - free (t->fpr); - free (t->description); - free (t); - t = t2; - } -} - - static void release_op_data (void *hook) { @@ -88,7 +72,8 @@ release_op_data (void *hook) free (sig->fpr); if (sig->pka_address) free (sig->pka_address); - release_tofu_info (sig->tofu); + if (sig->key) + gpgme_key_unref (sig->key); free (sig); sig = next; } @@ -690,49 +675,80 @@ parse_tofu_user (gpgme_signature_t sig, char *args) { gpg_error_t err; char *tail; - gpgme_tofu_info_t ti, ti2; + gpgme_user_id_t uid; + gpgme_tofu_info_t ti; + char *fpr = NULL; + char *address = NULL; tail = strchr (args, ' '); if (!tail || tail == args) - return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No fingerprint. */ + { + err = trace_gpg_error (GPG_ERR_INV_ENGINE); /* No fingerprint. */ + goto leave; + } *tail++ = 0; - ti = calloc (1, sizeof *ti); - if (!ti) - return gpg_error_from_syserror (); - - ti->fpr = strdup (args); - if (!ti->fpr) + fpr = strdup (args); + if (!fpr) { - free (ti); - return gpg_error_from_syserror (); + err = gpg_error_from_syserror (); + goto leave; } args = tail; tail = strchr (args, ' '); if (tail == args) - return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No addr-spec. */ + { + err = trace_gpg_error (GPG_ERR_INV_ENGINE); /* No addr-spec. */ + goto leave; + } if (tail) *tail = 0; - err = _gpgme_decode_percent_string (args, &ti->address, 0, 0); + err = _gpgme_decode_percent_string (args, &address, 0, 0); if (err) + goto leave; + + if (!sig->key) { - free (ti); - return err; + err = _gpgme_key_new (&sig->key); + if (err) + goto leave; + sig->key->fpr = fpr; + fpr = NULL; + } + else if (!sig->key->fpr) + { + err = trace_gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + else if (strcmp (sig->key->fpr, fpr)) + { + /* The engine did not emit NEWSIG before a new key. */ + err = trace_gpg_error (GPG_ERR_INV_ENGINE); + goto leave; } - /* Append to the tofu info list. */ - if (!sig->tofu) - sig->tofu = ti; - else - { - for (ti2 = sig->tofu; ti2->next; ti2 = ti2->next) - ; - ti2->next = ti; - } + err = _gpgme_key_append_name (sig->key, address, 0); + if (err) + goto leave; - return 0; + uid = sig->key->_last_uid; + assert (uid); + + ti = calloc (1, sizeof *ti); + if (!ti) + { + err = gpg_error_from_syserror (); + goto leave; + } + uid->tofu = ti; + + + leave: + free (fpr); + free (address); + return err; } @@ -749,12 +765,10 @@ parse_tofu_stats (gpgme_signature_t sig, char *args) int nfields; unsigned long uval; - if (!sig->tofu) + if (!sig->key || !sig->key->_last_uid || !(ti = sig->key->_last_uid->tofu)) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No TOFU_USER seen. */ - for (ti = sig->tofu; ti->next; ti = ti->next) - ; if (ti->firstseen || ti->signcount || ti->validity || ti->policy) - return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already seen. */ + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already set. */ nfields = _gpgme_split_fields (args, field, DIM (field)); if (nfields < 3) @@ -825,12 +839,10 @@ parse_tofu_stats_long (gpgme_signature_t sig, char *args, int raw) gpgme_tofu_info_t ti; char *p; - if (!sig->tofu) + if (!sig->key || !sig->key->_last_uid || !(ti = sig->key->_last_uid->tofu)) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No TOFU_USER seen. */ - for (ti = sig->tofu; ti->next; ti = ti->next) - ; if (ti->description) - return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already seen. */ + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already set. */ err = _gpgme_decode_percent_string (args, &ti->description, 0, 0); if (err) diff --git a/tests/run-keylist.c b/tests/run-keylist.c index cc4c3545..bae2dbb9 100644 --- a/tests/run-keylist.c +++ b/tests/run-keylist.c @@ -233,7 +233,14 @@ main (int argc, char **argv) for (nuids=0, uid=key->uids; uid; uid = uid->next, nuids++) { printf ("userid %d: %s\n", nuids, nonnull(uid->uid)); - printf ("valid %d: %s\n", nuids, + printf (" mbox %d: %s\n", nuids, nonnull(uid->address)); + if (uid->email && uid->email != uid->address) + printf (" email %d: %s\n", nuids, uid->email); + if (uid->name) + printf (" name %d: %s\n", nuids, uid->name); + if (uid->comment) + printf (" cmmnt %d: %s\n", nuids, uid->comment); + printf (" valid %d: %s\n", nuids, uid->validity == GPGME_VALIDITY_UNKNOWN? "unknown": uid->validity == GPGME_VALIDITY_UNDEFINED? "undefined": uid->validity == GPGME_VALIDITY_NEVER? "never": diff --git a/tests/run-verify.c b/tests/run-verify.c index b1745163..ef4dd32e 100644 --- a/tests/run-verify.c +++ b/tests/run-verify.c @@ -111,6 +111,7 @@ print_result (gpgme_verify_result_t result) { gpgme_signature_t sig; gpgme_sig_notation_t nt; + gpgme_user_id_t uid; gpgme_tofu_info_t ti; int count = 0; @@ -153,29 +154,34 @@ print_result (gpgme_verify_result_t result) if ((nt->value?strlen (nt->value):0) != nt->value_len) printf (" warning : value larger (%d)\n", nt->value_len); } - for (ti = sig->tofu; ti; ti = ti->next) + if (sig->key) { - printf (" tofu addr .: %s\n", ti->address); - if (!sig->fpr || strcmp (sig->fpr, ti->fpr)) - printf (" WARNING .: fpr mismatch (%s)\n", ti->fpr); - printf (" validity : %u (%s)\n", ti->validity, - ti->validity == 0? "conflict" : - ti->validity == 1? "no history" : - ti->validity == 2? "little history" : - ti->validity == 3? "enough history" : - ti->validity == 4? "lot of history" : "?"); - printf (" policy ..: %u (%s)\n", ti->policy, - ti->policy == GPGME_TOFU_POLICY_NONE? "none" : - ti->policy == GPGME_TOFU_POLICY_AUTO? "auto" : - ti->policy == GPGME_TOFU_POLICY_GOOD? "good" : - ti->policy == GPGME_TOFU_POLICY_UNKNOWN? "unknown" : - ti->policy == GPGME_TOFU_POLICY_BAD? "bad" : - ti->policy == GPGME_TOFU_POLICY_ASK? "ask" : "?"); - printf (" sigcount : %hu\n", ti->signcount); - printf (" firstseen: %u\n", ti->firstseen); - printf (" lastseen : %u\n", ti->lastseen); - printf (" desc ....: "); - print_description (nonnull (ti->description), 15); + printf (" primary fpr: %s\n", nonnull (sig->key->fpr)); + for (uid = sig->key->uids; uid; uid = uid->next) + { + printf (" tofu addr .: %s\n", nonnull (uid->address)); + ti = uid->tofu; + if (!ti) + continue; + printf (" validity : %u (%s)\n", ti->validity, + ti->validity == 0? "conflict" : + ti->validity == 1? "no history" : + ti->validity == 2? "little history" : + ti->validity == 3? "enough history" : + ti->validity == 4? "lot of history" : "?"); + printf (" policy ..: %u (%s)\n", ti->policy, + ti->policy == GPGME_TOFU_POLICY_NONE? "none" : + ti->policy == GPGME_TOFU_POLICY_AUTO? "auto" : + ti->policy == GPGME_TOFU_POLICY_GOOD? "good" : + ti->policy == GPGME_TOFU_POLICY_UNKNOWN? "unknown" : + ti->policy == GPGME_TOFU_POLICY_BAD? "bad" : + ti->policy == GPGME_TOFU_POLICY_ASK? "ask" : "?"); + printf (" sigcount : %hu\n", ti->signcount); + printf (" firstseen: %u\n", ti->firstseen); + printf (" lastseen : %u\n", ti->lastseen); + printf (" desc ....: "); + print_description (nonnull (ti->description), 15); + } } } }