1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-07-05 03:09:21 +02:00
gnupg/sm/verify.c
Werner Koch 6bdf11f671
gpgsm: Strip trailing zeroes from detached signatures.
* common/ksba-io-support.c: Include tlv.h
(struct reader_cb_parm_s): Add new fields.
(starts_with_sequence): New.
(simple_reader_cb): Handle stripping.
* common/ksba-io-support.h (GNUPG_KSBA_IO_STRIP): New.
(gnupg_ksba_create_reader): Handle the new flag.
* sm/verify.c (gpgsm_verify): Use the new flag for detached
signatures.
--

Note that this works only if --assume-binary is given.  The use case
for the feature is PDF signature checking where the PDF specs require
that the detached signature is padded with zeroes.

(cherry picked from commit 2a13f7f9dc)
2023-09-07 16:38:35 +02:00

757 lines
24 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* verify.c - Verify a messages signature
* Copyright (C) 2001, 2002, 2003, 2007,
* 2010 Free Software Foundation, Inc.
* Copyright (C) 2001-2019 Werner Koch
* Copyright (C) 2015-2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../common/i18n.h"
#include "../common/compliance.h"
static char *
strtimestamp_r (ksba_isotime_t atime)
{
char *buffer = xmalloc (15);
if (!atime || !*atime)
strcpy (buffer, "none");
else
sprintf (buffer, "%.4s-%.2s-%.2s", atime, atime+4, atime+6);
return buffer;
}
/* Hash the data for a detached signature. Returns 0 on success. */
static gpg_error_t
hash_data (int fd, gcry_md_hd_t md)
{
gpg_error_t err = 0;
estream_t fp;
char buffer[4096];
int nread;
fp = es_fdopen_nc (fd, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("fdopen(%d) failed: %s\n", fd, gpg_strerror (err));
return err;
}
do
{
nread = es_fread (buffer, 1, DIM(buffer), fp);
gcry_md_write (md, buffer, nread);
}
while (nread);
if (es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error ("read error on fd %d: %s\n", fd, gpg_strerror (err));
}
es_fclose (fp);
return err;
}
/* Perform a verify operation. To verify detached signatures, DATA_FD
must be different than -1. With OUT_FP given and a non-detached
signature, the signed material is written to that stream. */
int
gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp)
{
int i, rc;
gnupg_ksba_io_t b64reader = NULL;
gnupg_ksba_io_t b64writer = NULL;
ksba_reader_t reader;
ksba_writer_t writer = NULL;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
ksba_cert_t cert;
KEYDB_HANDLE kh;
gcry_md_hd_t data_md = NULL;
int signer;
const char *algoid;
int algo;
int is_detached, maybe_detached;
estream_t in_fp = NULL;
char *p;
audit_set_type (ctrl->audit, AUDIT_TYPE_VERIFY);
/* Although we detect detached signatures during the parsing phase,
* we need to know it earlier and thus accept the caller's idea of
* what to verify. */
maybe_detached = (data_fd != -1);
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
in_fp = es_fdopen_nc (in_fd, "rb");
if (!in_fp)
{
rc = gpg_error_from_syserror ();
log_error ("fdopen() failed: %s\n", strerror (errno));
goto leave;
}
rc = gnupg_ksba_create_reader
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)
| (maybe_detached? GNUPG_KSBA_IO_STRIP : 0)),
in_fp, &reader);
if (rc)
{
log_error ("can't create reader: %s\n", gpg_strerror (rc));
goto leave;
}
if (out_fp)
{
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
}
gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl);
if (ctrl->input_size_hint)
gnupg_ksba_set_total (b64writer, ctrl->input_size_hint);
rc = ksba_cms_new (&cms);
if (rc)
goto leave;
rc = ksba_cms_set_reader_writer (cms, reader, writer);
if (rc)
{
log_error ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (rc));
goto leave;
}
rc = gcry_md_open (&data_md, 0, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (data_md, "vrfy.data");
audit_log (ctrl->audit, AUDIT_SETUP_READY);
is_detached = 0;
do
{
rc = ksba_cms_parse (cms, &stopreason);
if (rc)
{
log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (stopreason == KSBA_SR_NEED_HASH)
{
is_detached = 1;
audit_log (ctrl->audit, AUDIT_DETACHED_SIGNATURE);
if (opt.verbose)
log_info ("detached signature\n");
}
if (stopreason == KSBA_SR_NEED_HASH
|| stopreason == KSBA_SR_BEGIN_DATA)
{
audit_log (ctrl->audit, AUDIT_GOT_DATA);
/* We are now able to enable the hash algorithms */
for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++)
{
algo = gcry_md_map_name (algoid);
if (!algo)
{
log_error ("unknown hash algorithm '%s'\n",
algoid? algoid:"?");
if (algoid
&& ( !strcmp (algoid, "1.2.840.113549.1.1.2")
||!strcmp (algoid, "1.2.840.113549.2.2")))
log_info (_("(this is the MD2 algorithm)\n"));
audit_log_s (ctrl->audit, AUDIT_BAD_DATA_HASH_ALGO, algoid);
}
else
{
if (DBG_X509)
log_debug ("enabling hash algorithm %d (%s)\n",
algo, algoid? algoid:"");
gcry_md_enable (data_md, algo);
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo);
}
}
if (opt.extra_digest_algo)
{
if (DBG_X509)
log_debug ("enabling extra hash algorithm %d\n",
opt.extra_digest_algo);
gcry_md_enable (data_md, opt.extra_digest_algo);
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO,
opt.extra_digest_algo);
}
if (is_detached)
{
if (data_fd == -1)
{
log_info ("detached signature w/o data "
"- assuming certs-only\n");
audit_log (ctrl->audit, AUDIT_CERT_ONLY_SIG);
}
else
audit_log_ok (ctrl->audit, AUDIT_DATA_HASHING,
hash_data (data_fd, data_md));
}
else
{
ksba_cms_set_hash_function (cms, HASH_FNC, data_md);
}
}
else if (stopreason == KSBA_SR_END_DATA)
{ /* The data bas been hashed */
audit_log_ok (ctrl->audit, AUDIT_DATA_HASHING, 0);
}
}
while (stopreason != KSBA_SR_READY);
if (b64writer)
{
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
audit_log_ok (ctrl->audit, AUDIT_WRITE_ERROR, rc);
goto leave;
}
}
if (data_fd != -1 && !is_detached)
{
log_error ("data given for a non-detached signature\n");
rc = gpg_error (GPG_ERR_CONFLICT);
audit_log (ctrl->audit, AUDIT_USAGE_ERROR);
goto leave;
}
for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++)
{
/* Fixme: it might be better to check the validity of the
certificate first before entering it into the DB. This way
we would avoid cluttering the DB with invalid
certificates. */
audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert,
keydb_store_cert (ctrl, cert, 0, NULL));
ksba_cert_release (cert);
}
cert = NULL;
for (signer=0; ; signer++)
{
char *issuer = NULL;
gcry_sexp_t sigval = NULL;
ksba_isotime_t sigtime, keyexptime;
ksba_sexp_t serial;
char *msgdigest = NULL;
size_t msgdigestlen;
char *ctattr;
int sigval_hash_algo;
int info_pkalgo;
unsigned int nbits;
int pkalgo;
char *pkalgostr = NULL;
char *pkcurve = NULL;
char *pkfpr = NULL;
unsigned int pkalgoflags, verifyflags;
rc = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial);
if (!signer && gpg_err_code (rc) == GPG_ERR_NO_DATA
&& data_fd == -1 && is_detached)
{
log_info ("certs-only message accepted\n");
rc = 0;
break;
}
if (rc)
{
if (signer && rc == -1)
rc = 0;
break;
}
gpgsm_status (ctrl, STATUS_NEWSIG, NULL);
audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer);
if (DBG_X509)
{
log_debug ("signer %d - issuer: '%s'\n",
signer, issuer? issuer:"[NONE]");
log_debug ("signer %d - serial: ", signer);
gpgsm_dump_serial (serial);
log_printf ("\n");
}
if (ctrl->audit)
{
char *tmpstr = gpgsm_format_sn_issuer (serial, issuer);
audit_log_s (ctrl->audit, AUDIT_SIG_NAME, tmpstr);
xfree (tmpstr);
}
rc = ksba_cms_get_signing_time (cms, signer, sigtime);
if (gpg_err_code (rc) == GPG_ERR_NO_DATA)
*sigtime = 0;
else if (rc)
{
log_error ("error getting signing time: %s\n", gpg_strerror (rc));
*sigtime = 0; /* (we can't encode an error in the time string.) */
}
rc = ksba_cms_get_message_digest (cms, signer,
&msgdigest, &msgdigestlen);
if (!rc)
{
algoid = ksba_cms_get_digest_algo (cms, signer);
algo = gcry_md_map_name (algoid);
if (DBG_X509)
log_debug ("signer %d - digest algo: %d\n", signer, algo);
if (! gcry_md_is_enabled (data_md, algo))
{
log_error ("digest algo %d (%s) has not been enabled\n",
algo, algoid?algoid:"");
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "unsupported");
goto next_signer;
}
}
else if (gpg_err_code (rc) == GPG_ERR_NO_DATA)
{
assert (!msgdigest);
rc = 0;
algoid = NULL;
algo = 0;
}
else /* real error */
{
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error");
break;
}
rc = ksba_cms_get_sigattr_oids (cms, signer,
"1.2.840.113549.1.9.3", &ctattr);
if (!rc)
{
const char *s;
if (DBG_X509)
log_debug ("signer %d - content-type attribute: %s",
signer, ctattr);
s = ksba_cms_get_content_oid (cms, 1);
if (!s || strcmp (ctattr, s))
{
log_error ("content-type attribute does not match "
"actual content-type\n");
ksba_free (ctattr);
ctattr = NULL;
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
ksba_free (ctattr);
ctattr = NULL;
}
else if (rc != -1)
{
log_error ("error getting content-type attribute: %s\n",
gpg_strerror (rc));
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
rc = 0;
sigval = gpgsm_ksba_cms_get_sig_val (cms, signer);
if (!sigval)
{
log_error ("no signature value available\n");
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
sigval_hash_algo = gpgsm_get_hash_algo_from_sigval (sigval, &pkalgoflags);
if (DBG_X509)
{
log_debug ("signer %d - signature available (sigval hash=%d pkaf=%u)",
signer, sigval_hash_algo, pkalgoflags);
}
if (!sigval_hash_algo)
sigval_hash_algo = algo; /* Fallback used e.g. with old libksba. */
/* Find the certificate of the signer */
keydb_search_reset (kh);
rc = keydb_search_issuer_sn (ctrl, kh, issuer, serial);
if (rc)
{
if (rc == -1)
{
log_error ("certificate not found\n");
rc = gpg_error (GPG_ERR_NO_PUBKEY);
}
else
log_error ("failed to find the certificate: %s\n",
gpg_strerror(rc));
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "verify.findkey",
numbuf, NULL);
}
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "no-cert");
goto next_signer;
}
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_error ("failed to get cert: %s\n", gpg_strerror (rc));
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error");
goto next_signer;
}
pkfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
pkalgostr = gpgsm_pubkey_algo_string (cert, NULL);
pkalgo = gpgsm_get_key_algo_info2 (cert, &nbits, &pkcurve);
/* Remap the ECC algo to the algo we use. Note that EdDSA has
* already been mapped. */
if (pkalgo == GCRY_PK_ECC)
pkalgo = GCRY_PK_ECDSA;
log_info (_("Signature made "));
if (*sigtime)
{
/* We take the freedom as noted in RFC3339 to use a space
* instead of the "T" delimiter between date and time. We
* also append a separate UTC instead of a "Z" or "+00:00"
* suffix because that makes it clear to everyone what kind
* of time this is. */
dump_isotime (sigtime);
log_printf (" UTC");
}
else
log_printf (_("[date not given]"));
log_info (_(" using %s key %s\n"), pkalgostr, pkfpr);
if (opt.verbose)
{
log_info (_("algorithm:"));
log_printf (" %s + %s",
pubkey_algo_to_string (pkalgo),
gcry_md_algo_name (sigval_hash_algo));
if (algo != sigval_hash_algo)
log_printf (" (%s)", gcry_md_algo_name (algo));
log_printf ("\n");
}
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo);
/* Check compliance. */
if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION,
pkalgo, pkalgoflags, NULL, nbits, NULL))
{
char kidstr[10+1];
snprintf (kidstr, sizeof kidstr, "0x%08lX",
gpgsm_get_short_fingerprint (cert, NULL));
log_error (_("key %s may not be used for signing in %s mode\n"),
kidstr,
gnupg_compliance_option_string (opt.compliance));
goto next_signer;
}
if (!gnupg_digest_is_allowed (opt.compliance, 0, sigval_hash_algo))
{
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (sigval_hash_algo),
gnupg_compliance_option_string (opt.compliance));
goto next_signer;
}
/* Print compliance warning for the key. */
if (!opt.quiet
&& !gnupg_pk_is_compliant (opt.compliance, pkalgo, pkalgoflags,
NULL, nbits, pkcurve))
{
log_info (_("WARNING: This key is not suitable for signing"
" in %s mode\n"),
gnupg_compliance_option_string (opt.compliance));
}
/* Check compliance with CO_DE_VS. */
if (gnupg_pk_is_compliant (CO_DE_VS, pkalgo, pkalgoflags,
NULL, nbits, pkcurve)
&& gnupg_gcrypt_is_compliant (CO_DE_VS)
&& gnupg_digest_is_compliant (CO_DE_VS, sigval_hash_algo))
gpgsm_status (ctrl, STATUS_VERIFICATION_COMPLIANCE_MODE,
gnupg_status_compliance_flag (CO_DE_VS));
else if (opt.require_compliance
&& opt.compliance == CO_DE_VS)
{
log_error (_("operation forced to fail due to"
" unfulfilled compliance rules\n"));
gpgsm_errors_seen = 1;
}
/* Now we can check the signature. */
if (msgdigest)
{ /* Signed attributes are available. */
gcry_md_hd_t md;
unsigned char *s;
/* Check that the message digest in the signed attributes
matches the one we calculated on the data. */
s = gcry_md_read (data_md, algo);
if ( !s || !msgdigestlen
|| gcry_md_get_algo_dlen (algo) != msgdigestlen
|| memcmp (s, msgdigest, msgdigestlen) )
{
char *fpr;
log_error (_("invalid signature: message digest attribute "
"does not match computed one\n"));
if (DBG_X509)
{
if (msgdigest)
log_printhex (msgdigest, msgdigestlen, "message: ");
if (s)
log_printhex (s, gcry_md_get_algo_dlen (algo),
"computed: ");
}
fpr = gpgsm_fpr_and_name_for_status (cert);
gpgsm_status (ctrl, STATUS_BADSIG, fpr);
xfree (fpr);
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO, sigval_hash_algo);
rc = gcry_md_open (&md, sigval_hash_algo, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error");
goto next_signer;
}
if (DBG_HASHING)
gcry_md_debug (md, "vrfy.attr");
ksba_cms_set_hash_function (cms, HASH_FNC, md);
rc = ksba_cms_hash_signed_attrs (cms, signer);
if (rc)
{
log_error ("hashing signed attrs failed: %s\n",
gpg_strerror (rc));
gcry_md_close (md);
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error");
goto next_signer;
}
rc = gpgsm_check_cms_signature (cert, sigval, md, sigval_hash_algo,
pkalgoflags, &info_pkalgo);
gcry_md_close (md);
}
else
{
rc = gpgsm_check_cms_signature (cert, sigval, data_md,
algo, pkalgoflags, &info_pkalgo);
}
if (rc)
{
char *fpr;
log_error ("invalid signature: %s\n", gpg_strerror (rc));
fpr = gpgsm_fpr_and_name_for_status (cert);
gpgsm_status (ctrl, STATUS_BADSIG, fpr);
xfree (fpr);
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
rc = gpgsm_cert_use_verify_p (cert); /*(this displays an info message)*/
if (rc)
{
gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "verify.keyusage",
gpg_err_code (rc));
rc = 0;
}
if (DBG_X509)
log_debug ("signature okay - checking certs\n");
audit_log (ctrl->audit, AUDIT_VALIDATE_CHAIN);
rc = gpgsm_validate_chain (ctrl, cert,
*sigtime? sigtime : "19700101T000000",
keyexptime, 0,
NULL, 0, &verifyflags);
{
char *fpr, *buf, *tstr;
fpr = gpgsm_fpr_and_name_for_status (cert);
if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED)
{
gpgsm_status (ctrl, STATUS_EXPKEYSIG, fpr);
rc = 0;
}
else
gpgsm_status (ctrl, STATUS_GOODSIG, fpr);
xfree (fpr);
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
tstr = strtimestamp_r (sigtime);
buf = xasprintf ("%s %s %s %s 0 0 %d %d 00", fpr, tstr,
*sigtime? sigtime : "0",
*keyexptime? keyexptime : "0",
info_pkalgo, algo);
xfree (tstr);
xfree (fpr);
gpgsm_status (ctrl, STATUS_VALIDSIG, buf);
xfree (buf);
}
audit_log_ok (ctrl->audit, AUDIT_CHAIN_STATUS, rc);
if (rc) /* of validate_chain */
{
log_error ("invalid certification chain: %s\n", gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_BAD_CERT_CHAIN
|| gpg_err_code (rc) == GPG_ERR_BAD_CERT
|| gpg_err_code (rc) == GPG_ERR_BAD_CA_CERT
|| gpg_err_code (rc) == GPG_ERR_CERT_REVOKED)
gpgsm_status_with_err_code (ctrl, STATUS_TRUST_NEVER, NULL,
gpg_err_code (rc));
else
gpgsm_status_with_err_code (ctrl, STATUS_TRUST_UNDEFINED, NULL,
gpg_err_code (rc));
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad");
goto next_signer;
}
audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "good");
for (i=0; (p = ksba_cert_get_subject (cert, i)); i++)
{
log_info (!i? _("Good signature from")
: _(" aka"));
log_printf (" \"");
gpgsm_es_print_name (log_get_stream (), p);
log_printf ("\"\n");
ksba_free (p);
}
/* Print a note if this is a qualified signature. */
{
size_t qualbuflen;
char qualbuffer[1];
rc = ksba_cert_get_user_data (cert, "is_qualified", &qualbuffer,
sizeof (qualbuffer), &qualbuflen);
if (!rc && qualbuflen)
{
if (*qualbuffer)
{
log_info (_("This is a qualified signature\n"));
if (!opt.qualsig_approval)
log_info
(_("Note, that this software is not officially approved "
"to create or verify such signatures.\n"));
}
}
else if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (rc));
}
gpgsm_status (ctrl, STATUS_TRUST_FULLY,
(verifyflags & VALIDATE_FLAG_STEED)?
"0 steed":
(verifyflags & VALIDATE_FLAG_CHAIN_MODEL)?
"0 chain": "0 shell");
next_signer:
rc = 0;
xfree (issuer);
xfree (serial);
gcry_sexp_release (sigval);
xfree (msgdigest);
xfree (pkalgostr);
xfree (pkcurve);
xfree (pkfpr);
ksba_cert_release (cert);
cert = NULL;
}
rc = 0;
leave:
ksba_cms_release (cms);
gnupg_ksba_destroy_reader (b64reader);
gnupg_ksba_destroy_writer (b64writer);
keydb_release (kh);
gcry_md_close (data_md);
es_fclose (in_fp);
if (rc)
{
char numbuf[50];
sprintf (numbuf, "%d", rc );
gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave",
numbuf, NULL);
}
return rc;
}