/* verify.c - Signature verification. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include "gpgme.h" #include "debug.h" #include "util.h" #include "context.h" #include "ops.h" typedef struct { struct _gpgme_op_verify_result result; gpgme_signature_t current_sig; int did_prepare_new_sig; int only_newsig_seen; int plaintext_seen; } *op_data_t; static void release_op_data (void *hook) { op_data_t opd = (op_data_t) hook; gpgme_signature_t sig = opd->result.signatures; while (sig) { gpgme_signature_t next = sig->next; gpgme_sig_notation_t notation = sig->notations; while (notation) { gpgme_sig_notation_t next_nota = notation->next; _gpgme_sig_notation_free (notation); notation = next_nota; } if (sig->fpr) free (sig->fpr); if (sig->pka_address) free (sig->pka_address); free (sig); sig = next; } if (opd->result.file_name) free (opd->result.file_name); } gpgme_verify_result_t gpgme_op_verify_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; gpgme_error_t err; gpgme_signature_t sig; TRACE_BEG (DEBUG_CTX, "gpgme_op_verify_result", ctx); err = _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook, -1, NULL); opd = hook; if (err || !opd) { TRACE_SUC0 ("result=(null)"); return NULL; } /* It is possible that we saw a new signature only followed by an ERROR line for that. In particular a missing X.509 key triggers this. In this case it is surprising that the summary field has not been updated. We fix it here by explicitly looking for this case. The real fix would be to have GPGME emit ERRSIG. */ for (sig = opd->result.signatures; sig; sig = sig->next) { if (!sig->summary) { switch (gpg_err_code (sig->status)) { case GPG_ERR_KEY_EXPIRED: sig->summary |= GPGME_SIGSUM_KEY_EXPIRED; break; case GPG_ERR_NO_PUBKEY: sig->summary |= GPGME_SIGSUM_KEY_MISSING; break; default: break; } } } /* Now for some tracing stuff. */ if (_gpgme_debug_trace ()) { int i; for (sig = opd->result.signatures, i = 0; sig; sig = sig->next, i++) { TRACE_LOG4 ("sig[%i] = fpr %s, summary 0x%x, status %s", i, sig->fpr, sig->summary, gpg_strerror (sig->status)); TRACE_LOG6 ("sig[%i] = timestamps 0x%x/0x%x flags:%s%s%s", i, sig->timestamp, sig->exp_timestamp, sig->wrong_key_usage ? "wrong key usage" : "", sig->pka_trust == 1 ? "pka bad" : (sig->pka_trust == 2 ? "pka_okay" : "pka RFU"), sig->chain_model ? "chain model" : ""); TRACE_LOG5 ("sig[%i] = validity 0x%x (%s), algos %s/%s", i, sig->validity, gpg_strerror (sig->validity_reason), gpgme_pubkey_algo_name (sig->pubkey_algo), gpgme_hash_algo_name (sig->hash_algo)); if (sig->pka_address) { TRACE_LOG2 ("sig[%i] = PKA address %s", i, sig->pka_address); } if (sig->notations) { TRACE_LOG1 ("sig[%i] = has notations (not shown)", i); } } } TRACE_SUC1 ("result=%p", &opd->result); return &opd->result; } /* Build a summary vector from RESULT. */ static void calc_sig_summary (gpgme_signature_t sig) { unsigned long sum = 0; /* Calculate the red/green flag. */ if (sig->validity == GPGME_VALIDITY_FULL || sig->validity == GPGME_VALIDITY_ULTIMATE) { if (gpg_err_code (sig->status) == GPG_ERR_NO_ERROR || gpg_err_code (sig->status) == GPG_ERR_SIG_EXPIRED || gpg_err_code (sig->status) == GPG_ERR_KEY_EXPIRED) sum |= GPGME_SIGSUM_GREEN; } else if (sig->validity == GPGME_VALIDITY_NEVER) { if (gpg_err_code (sig->status) == GPG_ERR_NO_ERROR || gpg_err_code (sig->status) == GPG_ERR_SIG_EXPIRED || gpg_err_code (sig->status) == GPG_ERR_KEY_EXPIRED) sum |= GPGME_SIGSUM_RED; } else if (gpg_err_code (sig->status) == GPG_ERR_BAD_SIGNATURE) sum |= GPGME_SIGSUM_RED; /* FIXME: handle the case when key and message are expired. */ switch (gpg_err_code (sig->status)) { case GPG_ERR_SIG_EXPIRED: sum |= GPGME_SIGSUM_SIG_EXPIRED; break; case GPG_ERR_KEY_EXPIRED: sum |= GPGME_SIGSUM_KEY_EXPIRED; break; case GPG_ERR_NO_PUBKEY: sum |= GPGME_SIGSUM_KEY_MISSING; break; case GPG_ERR_BAD_SIGNATURE: case GPG_ERR_NO_ERROR: break; default: sum |= GPGME_SIGSUM_SYS_ERROR; break; } /* Now look at the certain reason codes. */ switch (gpg_err_code (sig->validity_reason)) { case GPG_ERR_CRL_TOO_OLD: if (sig->validity == GPGME_VALIDITY_UNKNOWN) sum |= GPGME_SIGSUM_CRL_TOO_OLD; break; case GPG_ERR_CERT_REVOKED: sum |= GPGME_SIGSUM_KEY_REVOKED; break; default: break; } /* Check other flags. */ if (sig->wrong_key_usage) sum |= GPGME_SIGSUM_BAD_POLICY; /* Set the valid flag when the signature is unquestionable valid. (The test is identical to if(sum == GPGME_SIGSUM_GREEN)). */ if ((sum & GPGME_SIGSUM_GREEN) && !(sum & ~GPGME_SIGSUM_GREEN)) sum |= GPGME_SIGSUM_VALID; sig->summary = sum; } static gpgme_error_t prepare_new_sig (op_data_t opd) { gpgme_signature_t sig; if (opd->only_newsig_seen && opd->current_sig) { /* We have only seen the NEWSIG status and nothing else - we better skip this signature therefore and reuse it for the next possible signature. */ sig = opd->current_sig; memset (sig, 0, sizeof *sig); assert (opd->result.signatures == sig); } else { sig = calloc (1, sizeof (*sig)); if (!sig) return gpg_error_from_syserror (); if (!opd->result.signatures) opd->result.signatures = sig; if (opd->current_sig) opd->current_sig->next = sig; opd->current_sig = sig; } opd->did_prepare_new_sig = 1; opd->only_newsig_seen = 0; return 0; } static gpgme_error_t parse_new_sig (op_data_t opd, gpgme_status_code_t code, char *args) { gpgme_signature_t sig; char *end = strchr (args, ' '); char *tail; if (end) { *end = '\0'; end++; } if (!opd->did_prepare_new_sig) { gpg_error_t err; err = prepare_new_sig (opd); if (err) return err; } assert (opd->did_prepare_new_sig); opd->did_prepare_new_sig = 0; assert (opd->current_sig); sig = opd->current_sig; /* FIXME: We should set the source of the state. */ switch (code) { case GPGME_STATUS_GOODSIG: sig->status = gpg_error (GPG_ERR_NO_ERROR); break; case GPGME_STATUS_EXPSIG: sig->status = gpg_error (GPG_ERR_SIG_EXPIRED); break; case GPGME_STATUS_EXPKEYSIG: sig->status = gpg_error (GPG_ERR_KEY_EXPIRED); break; case GPGME_STATUS_BADSIG: sig->status = gpg_error (GPG_ERR_BAD_SIGNATURE); break; case GPGME_STATUS_REVKEYSIG: sig->status = gpg_error (GPG_ERR_CERT_REVOKED); break; case GPGME_STATUS_ERRSIG: /* Parse the pubkey algo. */ if (!end) goto parse_err_sig_fail; gpg_err_set_errno (0); sig->pubkey_algo = strtol (end, &tail, 0); if (errno || end == tail || *tail != ' ') goto parse_err_sig_fail; end = tail; while (*end == ' ') end++; /* Parse the hash algo. */ if (!*end) goto parse_err_sig_fail; gpg_err_set_errno (0); sig->hash_algo = strtol (end, &tail, 0); if (errno || end == tail || *tail != ' ') goto parse_err_sig_fail; end = tail; while (*end == ' ') end++; /* Skip the sig class. */ end = strchr (end, ' '); if (!end) goto parse_err_sig_fail; while (*end == ' ') end++; /* Parse the timestamp. */ sig->timestamp = _gpgme_parse_timestamp (end, &tail); if (sig->timestamp == -1 || end == tail || (*tail && *tail != ' ')) return gpg_error (GPG_ERR_INV_ENGINE); end = tail; while (*end == ' ') end++; /* Parse the return code. */ if (end[0] && (!end[1] || end[1] == ' ')) { switch (end[0]) { case '4': sig->status = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); break; case '9': sig->status = gpg_error (GPG_ERR_NO_PUBKEY); break; default: sig->status = gpg_error (GPG_ERR_GENERAL); } } else goto parse_err_sig_fail; goto parse_err_sig_ok; parse_err_sig_fail: sig->status = gpg_error (GPG_ERR_GENERAL); parse_err_sig_ok: break; default: return gpg_error (GPG_ERR_GENERAL); } if (*args) { sig->fpr = strdup (args); if (!sig->fpr) return gpg_error_from_syserror (); } return 0; } static gpgme_error_t parse_valid_sig (gpgme_signature_t sig, char *args) { char *end = strchr (args, ' '); if (end) { *end = '\0'; end++; } if (!*args) /* We require at least the fingerprint. */ return gpg_error (GPG_ERR_GENERAL); if (sig->fpr) free (sig->fpr); sig->fpr = strdup (args); if (!sig->fpr) return gpg_error_from_syserror (); /* Skip the creation date. */ end = strchr (end, ' '); if (end) { char *tail; sig->timestamp = _gpgme_parse_timestamp (end, &tail); if (sig->timestamp == -1 || end == tail || (*tail && *tail != ' ')) return gpg_error (GPG_ERR_INV_ENGINE); end = tail; sig->exp_timestamp = _gpgme_parse_timestamp (end, &tail); if (sig->exp_timestamp == -1 || end == tail || (*tail && *tail != ' ')) return gpg_error (GPG_ERR_INV_ENGINE); end = tail; while (*end == ' ') end++; /* Skip the signature version. */ end = strchr (end, ' '); if (end) { while (*end == ' ') end++; /* Skip the reserved field. */ end = strchr (end, ' '); if (end) { /* Parse the pubkey algo. */ gpg_err_set_errno (0); sig->pubkey_algo = strtol (end, &tail, 0); if (errno || end == tail || *tail != ' ') return gpg_error (GPG_ERR_INV_ENGINE); end = tail; while (*end == ' ') end++; if (*end) { /* Parse the hash algo. */ gpg_err_set_errno (0); sig->hash_algo = strtol (end, &tail, 0); if (errno || end == tail || *tail != ' ') return gpg_error (GPG_ERR_INV_ENGINE); end = tail; } } } } return 0; } static gpgme_error_t parse_notation (gpgme_signature_t sig, gpgme_status_code_t code, char *args) { gpgme_error_t err; gpgme_sig_notation_t *lastp = &sig->notations; gpgme_sig_notation_t notation = sig->notations; char *end = strchr (args, ' '); if (end) *end = '\0'; if (code == GPGME_STATUS_NOTATION_NAME || code == GPGME_STATUS_POLICY_URL) { /* FIXME: We could keep a pointer to the last notation in the list. */ while (notation && notation->value) { lastp = ¬ation->next; notation = notation->next; } if (notation) /* There is another notation name without data for the previous one. The crypto backend misbehaves. */ return gpg_error (GPG_ERR_INV_ENGINE); err = _gpgme_sig_notation_create (¬ation, NULL, 0, NULL, 0, 0); if (err) return err; if (code == GPGME_STATUS_NOTATION_NAME) { err = _gpgme_decode_percent_string (args, ¬ation->name, 0, 0); if (err) { _gpgme_sig_notation_free (notation); return err; } notation->name_len = strlen (notation->name); /* FIXME: For now we fake the human-readable flag. The critical flag can not be reported as it is not provided. */ notation->flags = GPGME_SIG_NOTATION_HUMAN_READABLE; notation->human_readable = 1; } else { /* This is a policy URL. */ err = _gpgme_decode_percent_string (args, ¬ation->value, 0, 0); if (err) { _gpgme_sig_notation_free (notation); return err; } notation->value_len = strlen (notation->value); } *lastp = notation; } else if (code == GPGME_STATUS_NOTATION_DATA) { int len = strlen (args) + 1; char *dest; /* FIXME: We could keep a pointer to the last notation in the list. */ while (notation && notation->next) { lastp = ¬ation->next; notation = notation->next; } if (!notation || !notation->name) /* There is notation data without a previous notation name. The crypto backend misbehaves. */ return gpg_error (GPG_ERR_INV_ENGINE); if (!notation->value) { dest = notation->value = malloc (len); if (!dest) return gpg_error_from_syserror (); } else { int cur_len = strlen (notation->value); dest = realloc (notation->value, len + strlen (notation->value)); if (!dest) return gpg_error_from_syserror (); notation->value = dest; dest += cur_len; } err = _gpgme_decode_percent_string (args, &dest, len, 0); if (err) return err; notation->value_len += strlen (dest); } else return gpg_error (GPG_ERR_INV_ENGINE); return 0; } static gpgme_error_t parse_trust (gpgme_signature_t sig, gpgme_status_code_t code, char *args) { char *end = strchr (args, ' '); if (end) *end = '\0'; switch (code) { case GPGME_STATUS_TRUST_UNDEFINED: default: sig->validity = GPGME_VALIDITY_UNKNOWN; break; case GPGME_STATUS_TRUST_NEVER: sig->validity = GPGME_VALIDITY_NEVER; break; case GPGME_STATUS_TRUST_MARGINAL: sig->validity = GPGME_VALIDITY_MARGINAL; break; case GPGME_STATUS_TRUST_FULLY: case GPGME_STATUS_TRUST_ULTIMATE: sig->validity = GPGME_VALIDITY_FULL; break; } sig->validity_reason = 0; sig->chain_model = 0; if (*args) { sig->validity_reason = atoi (args); while (*args && *args != ' ') args++; if (*args) { while (*args == ' ') args++; if (!strncmp (args, "chain", 2) && (args[2] == ' ' || !args[2])) sig->chain_model = 1; } } return 0; } /* Parse an error status line and if SET_STATUS is true update the result status as appropriate. With SET_STATUS being false, only check for an error. */ static gpgme_error_t parse_error (gpgme_signature_t sig, char *args, int set_status) { gpgme_error_t err; char *where = strchr (args, ' '); char *which; if (where) { *where = '\0'; which = where + 1; where = strchr (which, ' '); if (where) *where = '\0'; where = args; } else return gpg_error (GPG_ERR_INV_ENGINE); err = atoi (which); if (!strcmp (where, "proc_pkt.plaintext") && gpg_err_code (err) == GPG_ERR_BAD_DATA) { /* This indicates a double plaintext. The only solid way to handle this is by failing the oepration. */ return gpg_error (GPG_ERR_BAD_DATA); } else if (!set_status) ; else if (!strcmp (where, "verify.findkey")) sig->status = err; else if (!strcmp (where, "verify.keyusage") && gpg_err_code (err) == GPG_ERR_WRONG_KEY_USAGE) sig->wrong_key_usage = 1; return 0; } gpgme_error_t _gpgme_verify_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_ctx_t ctx = (gpgme_ctx_t) priv; gpgme_error_t err; void *hook; op_data_t opd; gpgme_signature_t sig; char *end; err = _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook, -1, NULL); opd = hook; if (err) return err; sig = opd->current_sig; switch (code) { case GPGME_STATUS_NEWSIG: if (sig) calc_sig_summary (sig); err = prepare_new_sig (opd); opd->only_newsig_seen = 1; return err; case GPGME_STATUS_GOODSIG: case GPGME_STATUS_EXPSIG: case GPGME_STATUS_EXPKEYSIG: case GPGME_STATUS_BADSIG: case GPGME_STATUS_ERRSIG: case GPGME_STATUS_REVKEYSIG: if (sig && !opd->did_prepare_new_sig) calc_sig_summary (sig); opd->only_newsig_seen = 0; return parse_new_sig (opd, code, args); case GPGME_STATUS_VALIDSIG: opd->only_newsig_seen = 0; return sig ? parse_valid_sig (sig, args) : gpg_error (GPG_ERR_INV_ENGINE); case GPGME_STATUS_NODATA: opd->only_newsig_seen = 0; if (!sig) return gpg_error (GPG_ERR_NO_DATA); sig->status = gpg_error (GPG_ERR_NO_DATA); break; case GPGME_STATUS_UNEXPECTED: opd->only_newsig_seen = 0; if (!sig) return gpg_error (GPG_ERR_GENERAL); sig->status = gpg_error (GPG_ERR_NO_DATA); break; case GPGME_STATUS_NOTATION_NAME: case GPGME_STATUS_NOTATION_DATA: case GPGME_STATUS_POLICY_URL: opd->only_newsig_seen = 0; return sig ? parse_notation (sig, code, args) : gpg_error (GPG_ERR_INV_ENGINE); case GPGME_STATUS_TRUST_UNDEFINED: case GPGME_STATUS_TRUST_NEVER: case GPGME_STATUS_TRUST_MARGINAL: case GPGME_STATUS_TRUST_FULLY: case GPGME_STATUS_TRUST_ULTIMATE: opd->only_newsig_seen = 0; return sig ? parse_trust (sig, code, args) : gpg_error (GPG_ERR_INV_ENGINE); case GPGME_STATUS_PKA_TRUST_BAD: case GPGME_STATUS_PKA_TRUST_GOOD: opd->only_newsig_seen = 0; /* Check that we only get one of these status codes per signature; if not the crypto backend misbehaves. */ if (!sig || sig->pka_trust || sig->pka_address) return gpg_error (GPG_ERR_INV_ENGINE); sig->pka_trust = code == GPGME_STATUS_PKA_TRUST_GOOD? 2 : 1; end = strchr (args, ' '); if (end) *end = 0; sig->pka_address = strdup (args); break; case GPGME_STATUS_ERROR: opd->only_newsig_seen = 0; /* Some error stati are informational, so we don't return an error code if we are not ready to process this status. */ return parse_error (sig, args, !!sig ); case GPGME_STATUS_EOF: if (sig && !opd->did_prepare_new_sig) calc_sig_summary (sig); if (opd->only_newsig_seen && sig) { gpgme_signature_t sig2; /* The last signature has no valid information - remove it from the list. */ assert (!sig->next); if (sig == opd->result.signatures) opd->result.signatures = NULL; else { for (sig2 = opd->result.signatures; sig2; sig2 = sig2->next) if (sig2->next == sig) { sig2->next = NULL; break; } } /* Note that there is no need to release the members of SIG because we won't be here if they have been set. */ free (sig); opd->current_sig = NULL; } opd->only_newsig_seen = 0; break; case GPGME_STATUS_PLAINTEXT: if (++opd->plaintext_seen > 1) return gpg_error (GPG_ERR_BAD_DATA); err = _gpgme_parse_plaintext (args, &opd->result.file_name); if (err) return err; default: break; } return 0; } static gpgme_error_t verify_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_error_t err; err = _gpgme_progress_status_handler (priv, code, args); if (!err) err = _gpgme_verify_status_handler (priv, code, args); return err; } gpgme_error_t _gpgme_op_verify_init_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; return _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook, sizeof (*opd), release_op_data); } static gpgme_error_t verify_start (gpgme_ctx_t ctx, int synchronous, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext) { gpgme_error_t err; err = _gpgme_op_reset (ctx, synchronous); if (err) return err; err = _gpgme_op_verify_init_result (ctx); if (err) return err; _gpgme_engine_set_status_handler (ctx->engine, verify_status_handler, ctx); if (!sig) return gpg_error (GPG_ERR_NO_DATA); if (!signed_text && !plaintext) return gpg_error (GPG_ERR_INV_VALUE); return _gpgme_engine_op_verify (ctx->engine, sig, signed_text, plaintext); } /* Decrypt ciphertext CIPHER and make a signature verification within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_verify_start (gpgme_ctx_t ctx, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext) { gpg_error_t err; TRACE_BEG3 (DEBUG_CTX, "gpgme_op_verify_start", ctx, "sig=%p, signed_text=%p, plaintext=%p", sig, signed_text, plaintext); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = verify_start (ctx, 0, sig, signed_text, plaintext); return TRACE_ERR (err); } /* Decrypt ciphertext CIPHER and make a signature verification within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_verify (gpgme_ctx_t ctx, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext) { gpgme_error_t err; TRACE_BEG3 (DEBUG_CTX, "gpgme_op_verify", ctx, "sig=%p, signed_text=%p, plaintext=%p", sig, signed_text, plaintext); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = verify_start (ctx, 1, sig, signed_text, plaintext); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } /* Compatibility interfaces. */ /* Get the key used to create signature IDX in CTX and return it in R_KEY. */ gpgme_error_t gpgme_get_sig_key (gpgme_ctx_t ctx, int idx, gpgme_key_t *r_key) { gpgme_verify_result_t result; gpgme_signature_t sig; if (!ctx) return gpg_error (GPG_ERR_INV_VALUE); result = gpgme_op_verify_result (ctx); sig = result->signatures; while (sig && idx) { sig = sig->next; idx--; } if (!sig || idx) return gpg_error (GPG_ERR_EOF); return gpgme_get_key (ctx, sig->fpr, r_key, 0); } /* Retrieve the signature status of signature IDX in CTX after a successful verify operation in R_STAT (if non-null). The creation time stamp of the signature is returned in R_CREATED (if non-null). The function returns a string containing the fingerprint. */ const char * gpgme_get_sig_status (gpgme_ctx_t ctx, int idx, _gpgme_sig_stat_t *r_stat, time_t *r_created) { gpgme_verify_result_t result; gpgme_signature_t sig; result = gpgme_op_verify_result (ctx); sig = result->signatures; while (sig && idx) { sig = sig->next; idx--; } if (!sig || idx) return NULL; if (r_stat) { switch (gpg_err_code (sig->status)) { case GPG_ERR_NO_ERROR: *r_stat = GPGME_SIG_STAT_GOOD; break; case GPG_ERR_BAD_SIGNATURE: *r_stat = GPGME_SIG_STAT_BAD; break; case GPG_ERR_NO_PUBKEY: *r_stat = GPGME_SIG_STAT_NOKEY; break; case GPG_ERR_NO_DATA: *r_stat = GPGME_SIG_STAT_NOSIG; break; case GPG_ERR_SIG_EXPIRED: *r_stat = GPGME_SIG_STAT_GOOD_EXP; break; case GPG_ERR_KEY_EXPIRED: *r_stat = GPGME_SIG_STAT_GOOD_EXPKEY; break; default: *r_stat = GPGME_SIG_STAT_ERROR; break; } } if (r_created) *r_created = sig->timestamp; return sig->fpr; } /* Retrieve certain attributes of a signature. IDX is the index number of the signature after a successful verify operation. WHAT is an attribute where GPGME_ATTR_EXPIRE is probably the most useful one. WHATIDX is to be passed as 0 for most attributes . */ unsigned long gpgme_get_sig_ulong_attr (gpgme_ctx_t ctx, int idx, _gpgme_attr_t what, int whatidx) { gpgme_verify_result_t result; gpgme_signature_t sig; result = gpgme_op_verify_result (ctx); sig = result->signatures; while (sig && idx) { sig = sig->next; idx--; } if (!sig || idx) return 0; switch (what) { case GPGME_ATTR_CREATED: return sig->timestamp; case GPGME_ATTR_EXPIRE: return sig->exp_timestamp; case GPGME_ATTR_VALIDITY: return (unsigned long) sig->validity; case GPGME_ATTR_SIG_STATUS: switch (gpg_err_code (sig->status)) { case GPG_ERR_NO_ERROR: return GPGME_SIG_STAT_GOOD; case GPG_ERR_BAD_SIGNATURE: return GPGME_SIG_STAT_BAD; case GPG_ERR_NO_PUBKEY: return GPGME_SIG_STAT_NOKEY; case GPG_ERR_NO_DATA: return GPGME_SIG_STAT_NOSIG; case GPG_ERR_SIG_EXPIRED: return GPGME_SIG_STAT_GOOD_EXP; case GPG_ERR_KEY_EXPIRED: return GPGME_SIG_STAT_GOOD_EXPKEY; default: return GPGME_SIG_STAT_ERROR; } case GPGME_ATTR_SIG_SUMMARY: return sig->summary; default: break; } return 0; } const char * gpgme_get_sig_string_attr (gpgme_ctx_t ctx, int idx, _gpgme_attr_t what, int whatidx) { gpgme_verify_result_t result; gpgme_signature_t sig; result = gpgme_op_verify_result (ctx); sig = result->signatures; while (sig && idx) { sig = sig->next; idx--; } if (!sig || idx) return NULL; switch (what) { case GPGME_ATTR_FPR: return sig->fpr; case GPGME_ATTR_ERRTOK: if (whatidx == 1) return sig->wrong_key_usage ? "Wrong_Key_Usage" : ""; else return ""; default: break; } return NULL; }