/* verify.c - Verify a messages signature * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "i18n.h" static char * strtimestamp_r (time_t atime) { char *buffer = xmalloc (15); if (atime < 0) strcpy (buffer, "????" "-??" "-??"); else if (!atime) strcpy (buffer, "none"); else { struct tm *tp; tp = gmtime( &atime ); sprintf (buffer, "%04d-%02d-%02d", 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday); } return buffer; } /* Hash the data for a detached signature */ static void hash_data (int fd, gcry_md_hd_t md) { FILE *fp; char buffer[4096]; int nread; fp = fdopen ( dup (fd), "rb"); if (!fp) { log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); return; } do { nread = fread (buffer, 1, DIM(buffer), fp); gcry_md_write (md, buffer, nread); } while (nread); if (ferror (fp)) log_error ("read error on fd %d: %s\n", fd, strerror (errno)); fclose (fp); } /* 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 ctrl, int in_fd, int data_fd, FILE *out_fp) { int i, rc; Base64Context b64reader = NULL; Base64Context b64writer = NULL; KsbaError err; KsbaReader reader; KsbaWriter writer = NULL; KsbaCMS cms = NULL; KsbaStopReason stopreason; KsbaCert cert; KEYDB_HANDLE kh; gcry_md_hd_t data_md = NULL; int signer; const char *algoid; int algo; int is_detached; FILE *fp = NULL; char *p; kh = keydb_new (0); if (!kh) { log_error (_("failed to allocated keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } fp = fdopen ( dup (in_fd), "rb"); if (!fp) { rc = gpg_error (gpg_err_code_from_errno (errno)); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } rc = gpgsm_create_reader (&b64reader, ctrl, fp, &reader); if (rc) { log_error ("can't create reader: %s\n", gpg_strerror (rc)); goto leave; } if (out_fp) { rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } } cms = ksba_cms_new (); if (!cms) { rc = gpg_error (GPG_ERR_ENOMEM); goto leave; } err = ksba_cms_set_reader_writer (cms, reader, writer); if (err) { log_error ("ksba_cms_set_reader_writer failed: %s\n", ksba_strerror (err)); rc = map_ksba_err (err); 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_start_debug (data_md, "vrfy.data"); is_detached = 0; do { err = ksba_cms_parse (cms, &stopreason); if (err) { log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (err)); rc = map_ksba_err (err); goto leave; } if (stopreason == KSBA_SR_NEED_HASH) { is_detached = 1; if (opt.verbose) log_info ("detached signature\n"); } if (stopreason == KSBA_SR_NEED_HASH || stopreason == KSBA_SR_BEGIN_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:"?"); else gcry_md_enable (data_md, algo); } if (is_detached) { if (data_fd == -1) log_info ("detached signature w/o data " "- assuming certs-only\n"); else 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 */ } } while (stopreason != KSBA_SR_READY); if (b64writer) { rc = gpgsm_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (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); 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. */ keydb_store_cert (cert, 0, NULL); ksba_cert_release (cert); } cert = NULL; err = 0; for (signer=0; ; signer++) { char *issuer = NULL; KsbaSexp sigval = NULL; time_t sigtime, keyexptime; KsbaSexp serial; char *msgdigest = NULL; size_t msgdigestlen; char *ctattr; err = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial); if (!signer && err == KSBA_No_Data && data_fd == -1 && is_detached) { log_info ("certs-only message accepted\n"); err = 0; break; } if (err) { if (signer && err == -1) err = 0; break; } 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"); } err = ksba_cms_get_signing_time (cms, signer, &sigtime); if (err == KSBA_No_Data) sigtime = 0; else if (err) { log_error ("error getting signing time: %s\n", ksba_strerror (err)); sigtime = (time_t)-1; } err = ksba_cms_get_message_digest (cms, signer, &msgdigest, &msgdigestlen); if (!err) { 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_info (data_md, GCRYCTL_IS_ALGO_ENABLED, &algo, NULL) ) { log_error ("digest algo %d has not been enabled\n", algo); goto next_signer; } } else if (err == KSBA_No_Data) { assert (!msgdigest); err = 0; algoid = NULL; algo = 0; } else /* real error */ break; err = ksba_cms_get_sigattr_oids (cms, signer, "1.2.840.113549.1.9.3",&ctattr); if (!err) { 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; goto next_signer; } ksba_free (ctattr); ctattr = NULL; } else if (err != -1) { log_error ("error getting content-type attribute: %s\n", ksba_strerror (err)); goto next_signer; } err = 0; sigval = ksba_cms_get_sig_val (cms, signer); if (!sigval) { log_error ("no signature value available\n"); goto next_signer; } if (DBG_X509) log_debug ("signer %d - signature available", signer); /* Find the certificate of the signer */ keydb_search_reset (kh); rc = keydb_search_issuer_sn (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); } /* fixme: we might want to append the issuer and serial using our standard notation */ goto next_signer; } rc = keydb_get_cert (kh, &cert); if (rc) { log_error ("failed to get cert: %s\n", gpg_strerror (rc)); goto next_signer; } log_info (_("Signature made ")); if (sigtime) gpgsm_dump_time (sigtime); else log_printf (_("[date not given]")); log_printf (_(" using certificate ID %08lX\n"), gpgsm_get_short_fingerprint (cert)); 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 || !s || memcmp (s, msgdigest, msgdigestlen) ) { char *fpr; log_error ("invalid signature: message digest attribute " "does not match calculated one\n"); fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); gpgsm_status (ctrl, STATUS_BADSIG, fpr); xfree (fpr); goto next_signer; } rc = gcry_md_open (&md, algo, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto next_signer; } if (DBG_HASHING) gcry_md_start_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", ksba_strerror (rc)); gcry_md_close (md); goto next_signer; } rc = gpgsm_check_cms_signature (cert, sigval, md, algo); gcry_md_close (md); } else { rc = gpgsm_check_cms_signature (cert, sigval, data_md, algo); } if (rc) { char *fpr; log_error ("invalid signature: %s\n", gpg_strerror (rc)); fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); gpgsm_status (ctrl, STATUS_BADSIG, fpr); xfree (fpr); 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"); rc = gpgsm_validate_chain (ctrl, cert, &keyexptime); if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED) { gpgsm_status (ctrl, STATUS_EXPKEYSIG, NULL); rc = 0; } else gpgsm_status (ctrl, STATUS_GOODSIG, NULL); { char *buf, *fpr, *tstr; fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); tstr = strtimestamp_r (sigtime); buf = xmalloc ( strlen(fpr) + strlen (tstr) + 120); sprintf (buf, "%s %s %lu %lu", fpr, tstr, (unsigned long)sigtime, (unsigned long)keyexptime ); xfree (tstr); xfree (fpr); gpgsm_status (ctrl, STATUS_VALIDSIG, buf); xfree (buf); } 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)); goto next_signer; } for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) { log_info (!i? _("Good signature from") : _(" aka")); log_printf (" \""); gpgsm_print_name (log_get_stream (), p); log_printf ("\"\n"); ksba_free (p); } gpgsm_status (ctrl, STATUS_TRUST_FULLY, NULL); next_signer: rc = 0; xfree (issuer); xfree (serial); xfree (sigval); xfree (msgdigest); ksba_cert_release (cert); cert = NULL; } rc = 0; if (err) { log_error ("ksba error: %s\n", ksba_strerror (err)); rc = map_ksba_err (rc); } leave: ksba_cms_release (cms); gpgsm_destroy_reader (b64reader); gpgsm_destroy_writer (b64writer); keydb_release (kh); gcry_md_close (data_md); if (fp) fclose (fp); if (rc) { char numbuf[50]; sprintf (numbuf, "%d", rc ); gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave", numbuf, NULL); } return rc; }